Merge branch 'main' of github.com:abacus-network/abacus-monorepo into trevor/read-txs

trevor/read-txs-nov-8
Trevor Porter 3 weeks ago
commit bceb934367
  1. 5
      .changeset/cold-dingos-give.md
  2. 6
      .changeset/cuddly-baboons-drive.md
  3. 5
      .changeset/dirty-olives-talk.md
  4. 6
      .changeset/dirty-swans-drum.md
  5. 7
      .changeset/dry-ties-approve.md
  6. 7
      .changeset/empty-needles-cough.md
  7. 5
      .changeset/fresh-pigs-work.md
  8. 5
      .changeset/happy-suits-double.md
  9. 5
      .changeset/hip-mugs-fold.md
  10. 7
      .changeset/lazy-carpets-nail.md
  11. 6
      .changeset/long-queens-deny.md
  12. 5
      .changeset/neat-sloths-agree.md
  13. 5
      .changeset/new-olives-applaud.md
  14. 11
      .changeset/plenty-pens-peel.md
  15. 2
      .changeset/shy-taxis-suffer.md
  16. 5
      .changeset/silent-berries-attend.md
  17. 5
      .changeset/sixty-eggs-smoke.md
  18. 5
      .changeset/sweet-houses-type.md
  19. 5
      .changeset/thin-tips-explain.md
  20. 5
      .changeset/thirty-actors-wonder.md
  21. 5
      .changeset/tidy-meals-add.md
  22. 5
      .changeset/unlucky-pillows-clap.md
  23. 2
      .github/actions/yarn-build-with-cache/action.yml
  24. 4
      .github/workflows/static-analysis.yml
  25. 4
      .github/workflows/storage-analysis.yml
  26. 6
      .github/workflows/test.yml
  27. 2
      .registryrc
  28. 893
      .yarn/releases/yarn-4.0.2.cjs
  29. 934
      .yarn/releases/yarn-4.5.1.cjs
  30. 2
      .yarnrc.yml
  31. 2
      Dockerfile
  32. 2
      package.json
  33. 2
      rust/main/Cargo.lock
  34. 1
      rust/main/Cargo.toml
  35. 77
      rust/main/agents/relayer/src/relayer.rs
  36. 2
      rust/main/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs
  37. 2
      rust/main/agents/scraper/src/db/generated/domain.rs
  38. 31
      rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs
  39. 4
      rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs
  40. 13
      rust/main/chains/hyperlane-cosmos/src/trait_builder.rs
  41. 1
      rust/main/chains/hyperlane-sealevel/Cargo.toml
  42. 17
      rust/main/chains/hyperlane-sealevel/src/error.rs
  43. 174
      rust/main/chains/hyperlane-sealevel/src/mailbox.rs
  44. 181
      rust/main/chains/hyperlane-sealevel/src/provider.rs
  45. 3
      rust/main/chains/hyperlane-sealevel/src/rpc/client.rs
  46. 4
      rust/main/chains/hyperlane-sealevel/src/trait_builder.rs
  47. 65
      rust/main/chains/hyperlane-sealevel/src/transaction.rs
  48. 230
      rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs
  49. 2078
      rust/main/config/mainnet_config.json
  50. 340
      rust/main/config/testnet_config.json
  51. 1
      rust/main/hyperlane-base/Cargo.toml
  52. 7
      rust/main/hyperlane-base/src/agent.rs
  53. 82
      rust/main/hyperlane-base/src/metrics/agent_metrics.rs
  54. 70
      rust/main/hyperlane-base/src/settings/parser/connection_parser.rs
  55. 2
      rust/main/hyperlane-core/src/types/mod.rs
  56. 8
      rust/main/hyperlane-core/src/types/native_token.rs
  57. 13
      rust/main/hyperlane-core/src/utils.rs
  58. 4
      rust/main/utils/run-locally/src/cosmos/types.rs
  59. 8
      rust/sealevel/client/src/warp_route.rs
  60. 542
      rust/sealevel/environments/mainnet3/chain-config.json
  61. 149
      rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json
  62. 10
      rust/sealevel/environments/mainnet3/warp-routes/ORCA-eclipse-solana/program-ids.json
  63. 17
      rust/sealevel/environments/mainnet3/warp-routes/ORCA-eclipse-solana/token-config.json
  64. 14
      rust/sealevel/environments/mainnet3/warp-routes/USDT-eclipse-ethereum-solana/program-ids.json
  65. 23
      rust/sealevel/environments/mainnet3/warp-routes/USDT-eclipse-ethereum-solana/token-config.json
  66. 10
      rust/sealevel/environments/mainnet3/warp-routes/WBTC-eclipse-ethereum-solana/program-ids.json
  67. 16
      rust/sealevel/environments/mainnet3/warp-routes/WBTC-eclipse-ethereum-solana/token-config.json
  68. 10
      rust/sealevel/environments/mainnet3/warp-routes/weETHs-eclipsemainnet-ethereum/program-ids.json
  69. 17
      rust/sealevel/environments/mainnet3/warp-routes/weETHs-eclipsemainnet-ethereum/token-config.json
  70. 23
      solidity/CHANGELOG.md
  71. 3
      solidity/contracts/Mailbox.sol
  72. 2
      solidity/contracts/PackageVersioned.sol
  73. 9
      solidity/contracts/interfaces/IThresholdAddressFactory.sol
  74. 3
      solidity/contracts/isms/aggregation/AbstractAggregationIsm.sol
  75. 2
      solidity/contracts/isms/aggregation/StaticAggregationIsm.sol
  76. 89
      solidity/contracts/isms/aggregation/StorageAggregationIsm.sol
  77. 2
      solidity/contracts/isms/multisig/AbstractMultisigIsm.sol
  78. 143
      solidity/contracts/isms/multisig/StorageMultisigIsm.sol
  79. 10
      solidity/contracts/libs/StaticAddressSetFactory.sol
  80. 4
      solidity/contracts/mock/MockMailbox.sol
  81. 4
      solidity/package.json
  82. 2
      solidity/test/hooks/AggregationHook.t.sol
  83. 30
      solidity/test/isms/AggregationIsm.t.sol
  84. 111
      solidity/test/isms/MultisigIsm.t.sol
  85. 16
      solidity/test/isms/WeightedMultisigIsm.t.sol
  86. 4
      typescript/ccip-server/CHANGELOG.md
  87. 2
      typescript/ccip-server/package.json
  88. 41
      typescript/cli/CHANGELOG.md
  89. 1
      typescript/cli/examples/submit/strategy/impersonated-account-strategy.yaml
  90. 2
      typescript/cli/examples/submit/strategy/json-rpc-chain-strategy.yaml
  91. 1
      typescript/cli/examples/submit/strategy/json-rpc-strategy.yaml
  92. 6
      typescript/cli/package.json
  93. 25
      typescript/cli/src/commands/warp.ts
  94. 27
      typescript/cli/src/config/core.ts
  95. 16
      typescript/cli/src/config/hooks.ts
  96. 72
      typescript/cli/src/config/ism.ts
  97. 28
      typescript/cli/src/config/submit.ts
  98. 21
      typescript/cli/src/config/warp.ts
  99. 16
      typescript/cli/src/context/context.ts
  100. 5
      typescript/cli/src/deploy/core.ts
  101. Some files were not shown because too many files have changed in this diff Show More

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

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

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Redeploy to alephzeroevmmainnet, chilizmainnet, flowmainnet, immutablezkevmmainnet, metal, polynomialfi, rarichain, rootstockmainnet, superpositionmainnet. Deploy to flame, prom.

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

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add support for updating the mailbox proxy admin owner

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/utils': major
'@hyperlane-xyz/sdk': major
---
Upgrade Viem to 2.2 and Solana Web3 to 1.9
Rename `chainMetadataToWagmiChain` to `chainMetadataToViemChain`

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

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

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to alephzeroevmtestnet, update deployment for arcadiatestnet2.

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Add storage based multisig ISM types

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

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

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---
feat: use message context in hook reader IGP derivation

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

@ -2,4 +2,4 @@
'@hyperlane-xyz/utils': patch '@hyperlane-xyz/utils': patch
--- ---
fix median utils func + add test Added a mustGet helper

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

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

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

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

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Added coinGeckoId as an optional property of the TokenConfigSchema

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

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': major
---
Remove getCoingeckoTokenPrices (use CoinGeckoTokenPriceGetter instead)

@ -16,7 +16,7 @@ runs:
path: | path: |
**/node_modules **/node_modules
.yarn .yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
# Typically, the cache will be hit, but if there's a network error when # Typically, the cache will be hit, but if there's a network error when
# restoring the cache, let's run the install step ourselves. # restoring the cache, let's run the install step ourselves.

@ -29,9 +29,9 @@ jobs:
path: | path: |
**/node_modules **/node_modules
.yarn .yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn-cache- ${{ runner.os }}-yarn-4.5.1.-cache-
- name: yarn-install - name: yarn-install
run: yarn install run: yarn install

@ -29,9 +29,9 @@ jobs:
path: | path: |
**/node_modules **/node_modules
.yarn .yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn-cache- ${{ runner.os }}-yarn-4.5.1-cache-
- name: yarn-install - name: yarn-install
run: yarn install run: yarn install

@ -40,9 +40,9 @@ jobs:
path: | path: |
**/node_modules **/node_modules
.yarn .yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn-cache- ${{ runner.os }}-yarn-4.5.1-cache-
- name: yarn-install - name: yarn-install
run: | run: |
@ -70,7 +70,7 @@ jobs:
path: | path: |
**/node_modules **/node_modules
.yarn .yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
fail-on-cache-miss: true fail-on-cache-miss: true
- name: lint - name: lint

@ -1 +1 @@
302be4817c063629cec70c0b02322b250df71122 d71eb5f42616998f77ce01079fd06a8e118966f7

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -10,4 +10,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs - path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
spec: "https://mskelton.dev/yarn-outdated/v3" spec: "https://mskelton.dev/yarn-outdated/v3"
yarnPath: .yarn/releases/yarn-4.0.2.cjs yarnPath: .yarn/releases/yarn-4.5.1.cjs

@ -4,7 +4,7 @@ WORKDIR /hyperlane-monorepo
RUN apk add --update --no-cache git g++ make py3-pip jq RUN apk add --update --no-cache git g++ make py3-pip jq
RUN yarn set version 4.0.2 RUN yarn set version 4.5.1
# Copy package.json and friends # Copy package.json and friends
COPY package.json yarn.lock .yarnrc.yml ./ COPY package.json yarn.lock .yarnrc.yml ./

@ -17,7 +17,7 @@
"dependencies": { "dependencies": {
"@changesets/cli": "^2.26.2" "@changesets/cli": "^2.26.2"
}, },
"packageManager": "yarn@4.0.2", "packageManager": "yarn@4.5.1",
"private": true, "private": true,
"scripts": { "scripts": {
"agent-configs": "yarn --cwd typescript/infra/ update-agent-config:mainnet3 && yarn --cwd typescript/infra/ update-agent-config:testnet4 && yarn prettier", "agent-configs": "yarn --cwd typescript/infra/ update-agent-config:mainnet3 && yarn --cwd typescript/infra/ update-agent-config:testnet4 && yarn prettier",

@ -4417,7 +4417,6 @@ dependencies = [
"console-subscriber", "console-subscriber",
"convert_case 0.6.0", "convert_case 0.6.0",
"derive-new", "derive-new",
"derive_builder",
"ed25519-dalek 1.0.1", "ed25519-dalek 1.0.1",
"ethers", "ethers",
"ethers-prometheus", "ethers-prometheus",
@ -4630,6 +4629,7 @@ dependencies = [
"hyperlane-sealevel-multisig-ism-message-id", "hyperlane-sealevel-multisig-ism-message-id",
"hyperlane-sealevel-validator-announce", "hyperlane-sealevel-validator-announce",
"jsonrpc-core", "jsonrpc-core",
"lazy_static",
"multisig-ism", "multisig-ism",
"num-traits", "num-traits",
"reqwest", "reqwest",

@ -83,6 +83,7 @@ itertools = "*"
jobserver = "=0.1.26" jobserver = "=0.1.26"
jsonrpc-core = "18.0" jsonrpc-core = "18.0"
k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] } k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] }
lazy_static = "1.5.0"
log = "0.4" log = "0.4"
macro_rules_attribute = "0.2" macro_rules_attribute = "0.2"
maplit = "1.0" maplit = "1.0"

@ -12,11 +12,12 @@ use hyperlane_base::{
broadcast::BroadcastMpscSender, broadcast::BroadcastMpscSender,
db::{HyperlaneRocksDB, DB}, db::{HyperlaneRocksDB, DB},
metrics::{AgentMetrics, MetricsUpdater}, metrics::{AgentMetrics, MetricsUpdater},
settings::ChainConf, settings::{ChainConf, IndexSettings},
AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics, AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics,
HyperlaneAgentCore, SyncOptions, HyperlaneAgentCore, SyncOptions,
}; };
use hyperlane_core::{ use hyperlane_core::{
rpc_clients::call_and_retry_n_times, ChainCommunicationError, ContractSyncCursor,
HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation, HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation,
H512, U256, H512, U256,
}; };
@ -50,6 +51,9 @@ use crate::{
}; };
use crate::{processor::Processor, server::ENDPOINT_MESSAGES_QUEUE_SIZE}; use crate::{processor::Processor, server::ENDPOINT_MESSAGES_QUEUE_SIZE};
const CURSOR_BUILDING_ERROR: &str = "Error building cursor for origin";
const CURSOR_INSTANTIATION_ATTEMPTS: usize = 10;
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
struct ContextKey { struct ContextKey {
origin: u32, origin: u32,
@ -354,6 +358,7 @@ impl BaseAgent for Relayer {
} }
for origin in &self.origin_chains { for origin in &self.origin_chains {
self.chain_metrics.set_critical_error(origin.name(), false);
let maybe_broadcaster = self let maybe_broadcaster = self
.message_syncs .message_syncs
.get(origin) .get(origin)
@ -412,6 +417,34 @@ impl BaseAgent for Relayer {
} }
impl Relayer { impl Relayer {
fn record_critical_error(
&self,
origin: &HyperlaneDomain,
err: ChainCommunicationError,
message: &str,
) {
error!(?err, origin=?origin, "{message}");
self.chain_metrics.set_critical_error(origin.name(), true);
}
async fn instantiate_cursor_with_retries<T: 'static>(
contract_sync: Arc<dyn ContractSyncer<T>>,
index_settings: IndexSettings,
) -> Result<Box<dyn ContractSyncCursor<T>>, ChainCommunicationError> {
call_and_retry_n_times(
|| {
let contract_sync = contract_sync.clone();
let index_settings = index_settings.clone();
Box::pin(async move {
let cursor = contract_sync.cursor(index_settings).await?;
Ok(cursor)
})
},
CURSOR_INSTANTIATION_ATTEMPTS,
)
.await
}
async fn run_message_sync( async fn run_message_sync(
&self, &self,
origin: &HyperlaneDomain, origin: &HyperlaneDomain,
@ -419,10 +452,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> { ) -> Instrumented<JoinHandle<()>> {
let index_settings = self.as_ref().settings.chains[origin.name()].index_settings(); let index_settings = self.as_ref().settings.chains[origin.name()].index_settings();
let contract_sync = self.message_syncs.get(origin).unwrap().clone(); let contract_sync = self.message_syncs.get(origin).unwrap().clone();
let cursor = contract_sync let cursor_instantiation_result =
.cursor(index_settings) Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await .await;
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}")); let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("MessageSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync contract_sync
.clone() .clone()
@ -444,10 +483,16 @@ impl Relayer {
.get(origin) .get(origin)
.unwrap() .unwrap()
.clone(); .clone();
let cursor = contract_sync let cursor_instantiation_result =
.cursor(index_settings) Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await .await;
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}")); let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("IgpSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync contract_sync
.clone() .clone()
@ -468,10 +513,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> { ) -> Instrumented<JoinHandle<()>> {
let index_settings = self.as_ref().settings.chains[origin.name()].index.clone(); let index_settings = self.as_ref().settings.chains[origin.name()].index.clone();
let contract_sync = self.merkle_tree_hook_syncs.get(origin).unwrap().clone(); let contract_sync = self.merkle_tree_hook_syncs.get(origin).unwrap().clone();
let cursor = contract_sync let cursor_instantiation_result =
.cursor(index_settings) Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await .await;
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}")); let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("MerkleTreeHookSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync contract_sync
.clone() .clone()

@ -490,7 +490,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Domain::TimeUpdated).timestamp().not_null()) .col(ColumnDef::new(Domain::TimeUpdated).timestamp().not_null())
.col(ColumnDef::new(Domain::Name).text().not_null()) .col(ColumnDef::new(Domain::Name).text().not_null())
.col(ColumnDef::new(Domain::NativeToken).text().not_null()) .col(ColumnDef::new(Domain::NativeToken).text().not_null())
.col(ColumnDef::new(Domain::ChainId).big_unsigned().unique_key()) .col(ColumnDef::new(Domain::ChainId).big_unsigned())
.col(ColumnDef::new(Domain::IsTestNet).boolean().not_null()) .col(ColumnDef::new(Domain::IsTestNet).boolean().not_null())
.col(ColumnDef::new(Domain::IsDeprecated).boolean().not_null()) .col(ColumnDef::new(Domain::IsDeprecated).boolean().not_null())
.to_owned(), .to_owned(),

@ -65,7 +65,7 @@ impl ColumnTrait for Column {
Self::TimeUpdated => ColumnType::DateTime.def(), Self::TimeUpdated => ColumnType::DateTime.def(),
Self::Name => ColumnType::Text.def(), Self::Name => ColumnType::Text.def(),
Self::NativeToken => ColumnType::Text.def(), Self::NativeToken => ColumnType::Text.def(),
Self::ChainId => ColumnType::BigInteger.def().null().unique(), Self::ChainId => ColumnType::BigInteger.def().null(),
Self::IsTestNet => ColumnType::Boolean.def(), Self::IsTestNet => ColumnType::Boolean.def(),
Self::IsDeprecated => ColumnType::Boolean.def(), Self::IsDeprecated => ColumnType::Boolean.def(),
} }

@ -18,9 +18,10 @@ use tracing::{error, warn};
use crypto::decompress_public_key; use crypto::decompress_public_key;
use hyperlane_core::{ use hyperlane_core::{
bytes_to_h512, h512_to_bytes, AccountAddressType, BlockInfo, ChainCommunicationError, bytes_to_h512, h512_to_bytes, utils::to_atto, AccountAddressType, BlockInfo,
ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain,
HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256, HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256,
H512, U256,
}; };
use crate::grpc::{WasmGrpcProvider, WasmProvider}; use crate::grpc::{WasmGrpcProvider, WasmProvider};
@ -33,9 +34,6 @@ use crate::{
mod parse; mod parse;
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Injective public key type URL for protobuf Any /// Injective public key type URL for protobuf Any
const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey"; const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey";
@ -320,26 +318,25 @@ impl CosmosProvider {
/// `OSMO` and it will keep fees expressed in `inj` as is. /// `OSMO` and it will keep fees expressed in `inj` as is.
/// ///
/// If fees are expressed in an unsupported denomination, they will be ignored. /// If fees are expressed in an unsupported denomination, they will be ignored.
fn convert_fee(&self, coin: &Coin) -> U256 { fn convert_fee(&self, coin: &Coin) -> ChainResult<U256> {
let native_token = self.connection_conf.get_native_token(); let native_token = self.connection_conf.get_native_token();
if coin.denom.as_ref() != native_token.denom { if coin.denom.as_ref() != native_token.denom {
return U256::zero(); return Ok(U256::zero());
} }
let exponent = ATTO_EXPONENT - native_token.decimals;
let coefficient = U256::from(10u128.pow(exponent));
let amount_in_native_denom = U256::from(coin.amount); let amount_in_native_denom = U256::from(coin.amount);
amount_in_native_denom * coefficient to_atto(amount_in_native_denom, native_token.decimals).ok_or(
ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()),
)
} }
fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> U256 { fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> ChainResult<U256> {
// TODO support multiple denominations for amount // TODO support multiple denominations for amount
let supported = self.report_unsupported_denominations(tx, hash); let supported = self.report_unsupported_denominations(tx, hash);
if supported.is_err() { if supported.is_err() {
return U256::max_value(); return Ok(U256::max_value());
} }
let gas_limit = U256::from(tx.auth_info.fee.gas_limit); let gas_limit = U256::from(tx.auth_info.fee.gas_limit);
@ -349,13 +346,13 @@ impl CosmosProvider {
.amount .amount
.iter() .iter()
.map(|c| self.convert_fee(c)) .map(|c| self.convert_fee(c))
.fold(U256::zero(), |acc, v| acc + v); .fold_ok(U256::zero(), |acc, v| acc + v)?;
if fee < gas_limit { if fee < gas_limit {
warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price"); warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price");
} }
fee / gas_limit Ok(fee / gas_limit)
} }
} }
@ -425,7 +422,7 @@ impl HyperlaneProvider for CosmosProvider {
let contract = Self::contract(&tx, &hash)?; let contract = Self::contract(&tx, &hash)?;
let (sender, nonce) = self.sender_and_nonce(&tx)?; let (sender, nonce) = self.sender_and_nonce(&tx)?;
let gas_price = self.calculate_gas_price(&hash, &tx); let gas_price = self.calculate_gas_price(&hash, &tx)?;
let tx_info = TxnInfo { let tx_info = TxnInfo {
hash: hash.into(), hash: hash.into(),

@ -3,10 +3,10 @@ use std::str::FromStr;
use url::Url; use url::Url;
use hyperlane_core::config::OperationBatchConfig; use hyperlane_core::config::OperationBatchConfig;
use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain}; use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, NativeToken};
use crate::grpc::{WasmGrpcProvider, WasmProvider}; use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::{ConnectionConf, CosmosAddress, CosmosAmount, NativeToken, RawCosmosAmount}; use crate::{ConnectionConf, CosmosAddress, CosmosAmount, RawCosmosAmount};
#[ignore] #[ignore]
#[tokio::test] #[tokio::test]

@ -3,7 +3,9 @@ use std::str::FromStr;
use derive_new::new; use derive_new::new;
use url::Url; use url::Url;
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber}; use hyperlane_core::{
config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber, NativeToken,
};
/// Cosmos connection configuration /// Cosmos connection configuration
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -60,15 +62,6 @@ impl TryFrom<RawCosmosAmount> for CosmosAmount {
} }
} }
/// Chain native token denomination and number of decimal places
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct NativeToken {
/// The number of decimal places in token which can be expressed by denomination
pub decimals: u32,
/// Denomination of the token
pub denom: String,
}
/// An error type when parsing a connection configuration. /// An error type when parsing a connection configuration.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ConnectionConfError { pub enum ConnectionConfError {

@ -11,6 +11,7 @@ bincode.workspace = true
borsh.workspace = true borsh.workspace = true
derive-new.workspace = true derive-new.workspace = true
jsonrpc-core.workspace = true jsonrpc-core.workspace = true
lazy_static.workspace = true
num-traits.workspace = true num-traits.workspace = true
reqwest.workspace = true reqwest.workspace = true
serde.workspace = true serde.workspace = true

@ -1,7 +1,7 @@
use hyperlane_core::{ChainCommunicationError, H512}; use hyperlane_core::{ChainCommunicationError, H512};
use solana_client::client_error::ClientError; use solana_client::client_error::ClientError;
use solana_sdk::pubkey::ParsePubkeyError; use solana_sdk::pubkey::ParsePubkeyError;
use solana_transaction_status::EncodedTransaction; use solana_transaction_status::{EncodedTransaction, UiMessage};
/// Errors from the crates specific to the hyperlane-sealevel /// Errors from the crates specific to the hyperlane-sealevel
/// implementation. /// implementation.
@ -27,12 +27,27 @@ pub enum HyperlaneSealevelError {
/// Unsupported transaction encoding /// Unsupported transaction encoding
#[error("{0:?}")] #[error("{0:?}")]
UnsupportedTransactionEncoding(EncodedTransaction), UnsupportedTransactionEncoding(EncodedTransaction),
/// Unsupported message encoding
#[error("{0:?}")]
UnsupportedMessageEncoding(UiMessage),
/// Unsigned transaction /// Unsigned transaction
#[error("{0}")] #[error("{0}")]
UnsignedTransaction(H512), UnsignedTransaction(H512),
/// Incorrect transaction /// Incorrect transaction
#[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")] #[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")]
IncorrectTransaction(Box<H512>, Box<H512>), IncorrectTransaction(Box<H512>, Box<H512>),
/// Empty metadata
#[error("received empty metadata in transaction")]
EmptyMetadata,
/// Empty compute units consumed
#[error("received empty compute units consumed in transaction")]
EmptyComputeUnitsConsumed,
/// Too many non-native programs
#[error("transaction contains too many non-native programs, hash: {0:?}")]
TooManyNonNativePrograms(H512),
/// No non-native programs
#[error("transaction contains no non-native programs, hash: {0:?}")]
NoNonNativePrograms(H512),
} }
impl From<HyperlaneSealevelError> for ChainCommunicationError { impl From<HyperlaneSealevelError> for ChainCommunicationError {

@ -33,6 +33,7 @@ use solana_client::{
use solana_sdk::{ use solana_sdk::{
account::Account, account::Account,
bs58, bs58,
clock::Slot,
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
compute_budget::ComputeBudgetInstruction, compute_budget::ComputeBudgetInstruction,
hash::Hash, hash::Hash,
@ -61,7 +62,9 @@ use hyperlane_core::{
use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; use crate::account::{search_accounts_by_discriminator, search_and_validate_account};
use crate::error::HyperlaneSealevelError; use crate::error::HyperlaneSealevelError;
use crate::transaction::search_dispatched_message_transactions; use crate::transaction::{
is_message_delivery_instruction, is_message_dispatch_instruction, search_message_transactions,
};
use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::utils::{decode_h256, decode_h512, from_base58};
use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient};
@ -694,44 +697,15 @@ impl SealevelMailboxIndexer {
let hyperlane_message = let hyperlane_message =
HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?;
let block = self let log_meta = self
.mailbox .dispatch_message_log_meta(
.provider U256::from(nonce),
.rpc()
.get_block(dispatched_message_account.slot)
.await?;
let block_hash = decode_h256(&block.blockhash)?;
let transactions =
block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?;
let transaction_hashes = search_dispatched_message_transactions(
&self.mailbox.program_id,
&valid_message_storage_pda_pubkey, &valid_message_storage_pda_pubkey,
transactions, &dispatched_message_account.slot,
); )
.await?;
// We expect to see that there is only one message dispatch transaction
if transaction_hashes.len() > 1 {
Err(HyperlaneSealevelError::TooManyTransactions("Block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned()))?
}
let (transaction_index, transaction_hash) = transaction_hashes
.into_iter()
.next()
.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?;
Ok(( Ok((hyperlane_message.into(), log_meta))
hyperlane_message.into(),
LogMeta {
address: self.mailbox.program_id.to_bytes().into(),
block_number: dispatched_message_account.slot,
block_hash,
transaction_id: transaction_hash,
transaction_index: transaction_index as u64,
log_index: U256::from(nonce),
},
))
} }
fn dispatched_message_account(&self, account: &Account) -> ChainResult<Pubkey> { fn dispatched_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
@ -748,6 +722,28 @@ impl SealevelMailboxIndexer {
Ok(expected_pubkey) Ok(expected_pubkey)
} }
async fn dispatch_message_log_meta(
&self,
log_index: U256,
message_storage_pda_pubkey: &Pubkey,
message_account_slot: &Slot,
) -> ChainResult<LogMeta> {
let error_msg_no_txn = "block which should contain message dispatch transaction does not contain any transaction".to_owned();
let error_msg_too_many_txns = "block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned();
let error_msg_no_txn_after_filtering = "block which should contain message dispatch transaction does not contain any after filtering".to_owned();
self.log_meta(
log_index,
message_storage_pda_pubkey,
message_account_slot,
&is_message_dispatch_instruction,
error_msg_no_txn,
error_msg_too_many_txns,
error_msg_no_txn_after_filtering,
)
.await
}
async fn get_delivered_message_with_nonce( async fn get_delivered_message_with_nonce(
&self, &self,
nonce: u32, nonce: u32,
@ -782,19 +778,15 @@ impl SealevelMailboxIndexer {
.into_inner(); .into_inner();
let message_id = delivered_message_account.message_id; let message_id = delivered_message_account.message_id;
Ok(( let log_meta = self
message_id.into(), .delivered_message_log_meta(
LogMeta { U256::from(nonce),
address: self.mailbox.program_id.to_bytes().into(), &valid_message_storage_pda_pubkey,
block_number: delivered_message_account.slot, &delivered_message_account.slot,
// TODO: get these when building out scraper support. )
// It's inconvenient to get these :| .await?;
block_hash: H256::zero(),
transaction_id: H512::zero(), Ok((message_id.into(), log_meta))
transaction_index: 0,
log_index: U256::zero(),
},
))
} }
fn delivered_message_account(&self, account: &Account) -> ChainResult<Pubkey> { fn delivered_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
@ -808,6 +800,88 @@ impl SealevelMailboxIndexer {
})?; })?;
Ok(expected_pubkey) Ok(expected_pubkey)
} }
async fn delivered_message_log_meta(
&self,
log_index: U256,
message_storage_pda_pubkey: &Pubkey,
message_account_slot: &Slot,
) -> ChainResult<LogMeta> {
let error_msg_no_txn = "block which should contain message delivery transaction does not contain any transaction".to_owned();
let error_msg_too_many_txns = "block contains more than one deliver message transaction operating on the same delivery message store PDA".to_owned();
let error_msg_no_txn_after_filtering = "block which should contain message delivery transaction does not contain any after filtering".to_owned();
self.log_meta(
log_index,
message_storage_pda_pubkey,
message_account_slot,
&is_message_delivery_instruction,
error_msg_no_txn,
error_msg_too_many_txns,
error_msg_no_txn_after_filtering,
)
.await
}
async fn log_meta<F>(
&self,
log_index: U256,
message_storage_pda_pubkey: &Pubkey,
message_account_slot: &Slot,
is_message_instruction: &F,
error_msg_no_txn: String,
error_msg_too_many_txns: String,
error_msg_no_txn_after_filtering: String,
) -> ChainResult<LogMeta>
where
F: Fn(instruction::Instruction) -> bool,
{
let block = self
.mailbox
.provider
.rpc()
.get_block(*message_account_slot)
.await?;
let block_hash = decode_h256(&block.blockhash)?;
let transactions = block
.transactions
.ok_or(HyperlaneSealevelError::NoTransactions(error_msg_no_txn))?;
let transaction_hashes = search_message_transactions(
&self.mailbox.program_id,
&message_storage_pda_pubkey,
transactions,
&is_message_instruction,
);
// We expect to see that there is only one message dispatch transaction
if transaction_hashes.len() > 1 {
Err(HyperlaneSealevelError::TooManyTransactions(
error_msg_too_many_txns,
))?
}
let (transaction_index, transaction_hash) =
transaction_hashes
.into_iter()
.next()
.ok_or(HyperlaneSealevelError::NoTransactions(
error_msg_no_txn_after_filtering,
))?;
let log_meta = LogMeta {
address: self.mailbox.program_id.to_bytes().into(),
block_number: *message_account_slot,
block_hash,
transaction_id: transaction_hash,
transaction_index: transaction_index as u64,
log_index,
};
Ok(log_meta)
}
} }
#[async_trait] #[async_trait]

@ -1,23 +1,45 @@
use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use lazy_static::lazy_static;
use solana_sdk::signature::Signature; use solana_sdk::signature::Signature;
use solana_transaction_status::EncodedTransaction; use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransaction, EncodedTransactionWithStatusMeta,
UiInstruction, UiMessage, UiParsedInstruction, UiParsedMessage, UiTransaction,
UiTransactionStatusMeta,
};
use tracing::warn;
use hyperlane_core::{ use hyperlane_core::{
BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, utils::to_atto, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain,
HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256, HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, NativeToken, TxnInfo,
TxnReceiptInfo, H256, H512, U256,
}; };
use crate::error::HyperlaneSealevelError; use crate::error::HyperlaneSealevelError;
use crate::utils::{decode_h256, decode_h512, decode_pubkey}; use crate::utils::{decode_h256, decode_h512, decode_pubkey};
use crate::{ConnectionConf, SealevelRpcClient}; use crate::{ConnectionConf, SealevelRpcClient};
lazy_static! {
static ref NATIVE_PROGRAMS: HashSet<String> = HashSet::from([
solana_sdk::bpf_loader_upgradeable::ID.to_string(),
solana_sdk::compute_budget::ID.to_string(),
solana_sdk::config::program::ID.to_string(),
solana_sdk::ed25519_program::ID.to_string(),
solana_sdk::secp256k1_program::ID.to_string(),
solana_sdk::stake::program::ID.to_string(),
solana_sdk::system_program::ID.to_string(),
solana_sdk::vote::program::ID.to_string(),
]);
}
/// A wrapper around a Sealevel provider to get generic blockchain information. /// A wrapper around a Sealevel provider to get generic blockchain information.
#[derive(Debug)] #[derive(Debug)]
pub struct SealevelProvider { pub struct SealevelProvider {
domain: HyperlaneDomain, domain: HyperlaneDomain,
rpc_client: Arc<SealevelRpcClient>, rpc_client: Arc<SealevelRpcClient>,
native_token: NativeToken,
} }
impl SealevelProvider { impl SealevelProvider {
@ -25,14 +47,120 @@ impl SealevelProvider {
pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self { pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self {
// Set the `processed` commitment at rpc level // Set the `processed` commitment at rpc level
let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string())); let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string()));
let native_token = conf.native_token.clone();
SealevelProvider { domain, rpc_client } Self {
domain,
rpc_client,
native_token,
}
} }
/// Get an rpc client /// Get an rpc client
pub fn rpc(&self) -> &SealevelRpcClient { pub fn rpc(&self) -> &SealevelRpcClient {
&self.rpc_client &self.rpc_client
} }
fn validate_transaction(hash: &H512, txn: &UiTransaction) -> ChainResult<()> {
let received_signature = txn
.signatures
.first()
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?;
let received_hash = decode_h512(received_signature)?;
if &received_hash != hash {
Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::IncorrectTransaction(
Box::new(*hash),
Box::new(received_hash),
),
))?;
}
Ok(())
}
fn sender(hash: &H512, txn: &UiTransaction) -> ChainResult<H256> {
let message = Self::parsed_message(txn)?;
let signer = message
.account_keys
.first()
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?;
let pubkey = decode_pubkey(&signer.pubkey)?;
let sender = H256::from_slice(&pubkey.to_bytes());
Ok(sender)
}
fn recipient(hash: &H512, txn: &UiTransaction) -> ChainResult<H256> {
let message = Self::parsed_message(txn)?;
let programs = message
.instructions
.iter()
.filter_map(|ii| {
if let UiInstruction::Parsed(iii) = ii {
Some(iii)
} else {
None
}
})
.map(|ii| match ii {
UiParsedInstruction::Parsed(iii) => &iii.program_id,
UiParsedInstruction::PartiallyDecoded(iii) => &iii.program_id,
})
.filter(|program_id| !NATIVE_PROGRAMS.contains(*program_id))
.collect::<Vec<&String>>();
if programs.len() > 1 {
Err(HyperlaneSealevelError::TooManyNonNativePrograms(*hash))?;
}
let program_id = programs
.first()
.ok_or(HyperlaneSealevelError::NoNonNativePrograms(*hash))?;
let pubkey = decode_pubkey(program_id)?;
let recipient = H256::from_slice(&pubkey.to_bytes());
Ok(recipient)
}
fn gas(meta: &UiTransactionStatusMeta) -> ChainResult<U256> {
let OptionSerializer::Some(gas) = meta.compute_units_consumed else {
Err(HyperlaneSealevelError::EmptyComputeUnitsConsumed)?
};
Ok(U256::from(gas))
}
/// Extracts and converts fees into atto (10^-18) units.
///
/// We convert fees into atto units since otherwise a compute unit price (gas price)
/// becomes smaller than 1 lamport (or 1 unit of native token) and the price is rounded
/// to zero. We normalise the gas price for all the chain to be expressed in atto units.
fn fee(&self, meta: &UiTransactionStatusMeta) -> ChainResult<U256> {
let amount_in_native_denom = U256::from(meta.fee);
to_atto(amount_in_native_denom, self.native_token.decimals).ok_or(
ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()),
)
}
fn meta(txn: &EncodedTransactionWithStatusMeta) -> ChainResult<&UiTransactionStatusMeta> {
let meta = txn
.meta
.as_ref()
.ok_or(HyperlaneSealevelError::EmptyMetadata)?;
Ok(meta)
}
fn parsed_message(txn: &UiTransaction) -> ChainResult<&UiParsedMessage> {
Ok(match &txn.message {
UiMessage::Parsed(m) => m,
m => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()),
))?,
})
}
} }
impl HyperlaneChain for SealevelProvider { impl HyperlaneChain for SealevelProvider {
@ -44,6 +172,7 @@ impl HyperlaneChain for SealevelProvider {
Box::new(SealevelProvider { Box::new(SealevelProvider {
domain: self.domain.clone(), domain: self.domain.clone(),
rpc_client: self.rpc_client.clone(), rpc_client: self.rpc_client.clone(),
native_token: self.native_token.clone(),
}) })
} }
} }
@ -76,45 +205,45 @@ impl HyperlaneProvider for SealevelProvider {
/// for all chains, not only Ethereum-like chains. /// for all chains, not only Ethereum-like chains.
async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult<TxnInfo> { async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult<TxnInfo> {
let signature = Signature::new(hash.as_bytes()); let signature = Signature::new(hash.as_bytes());
let transaction = self.rpc_client.get_transaction(&signature).await?;
let ui_transaction = match transaction.transaction.transaction { let txn_confirmed = self.rpc_client.get_transaction(&signature).await?;
let txn_with_meta = &txn_confirmed.transaction;
let txn = match &txn_with_meta.transaction {
EncodedTransaction::Json(t) => t, EncodedTransaction::Json(t) => t,
t => Err(Into::<ChainCommunicationError>::into( t => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedTransactionEncoding(t), HyperlaneSealevelError::UnsupportedTransactionEncoding(t.clone()),
))?, ))?,
}; };
let received_signature = ui_transaction Self::validate_transaction(hash, txn)?;
.signatures let sender = Self::sender(hash, txn)?;
.first() let recipient = Self::recipient(hash, txn)?;
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?; let meta = Self::meta(txn_with_meta)?;
let received_hash = decode_h512(received_signature)?; let gas_used = Self::gas(meta)?;
let fee = self.fee(meta)?;
if &received_hash != hash { if fee < gas_used {
Err(Into::<ChainCommunicationError>::into( warn!(tx_hash = ?hash, ?fee, ?gas_used, "calculated fee is less than gas used. it will result in zero gas price");
HyperlaneSealevelError::IncorrectTransaction(
Box::new(*hash),
Box::new(received_hash),
),
))?;
} }
let gas_price = Some(fee / gas_used);
let receipt = TxnReceiptInfo { let receipt = TxnReceiptInfo {
gas_used: Default::default(), gas_used,
cumulative_gas_used: Default::default(), cumulative_gas_used: gas_used,
effective_gas_price: None, effective_gas_price: gas_price,
}; };
Ok(TxnInfo { Ok(TxnInfo {
hash: *hash, hash: *hash,
gas_limit: Default::default(), gas_limit: gas_used,
max_priority_fee_per_gas: None, max_priority_fee_per_gas: None,
max_fee_per_gas: None, max_fee_per_gas: None,
gas_price: None, gas_price,
nonce: 0, nonce: 0,
sender: Default::default(), sender,
recipient: None, recipient: Some(recipient),
receipt: Some(receipt), receipt: Some(receipt),
raw_input_data: None, raw_input_data: None,
}) })

@ -17,7 +17,7 @@ use solana_sdk::{
}; };
use solana_transaction_status::{ use solana_transaction_status::{
EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock,
UiReturnDataEncoding, UiTransactionReturnData, UiReturnDataEncoding, UiTransactionEncoding, UiTransactionReturnData,
}; };
use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; use hyperlane_core::{ChainCommunicationError, ChainResult, U256};
@ -188,6 +188,7 @@ impl SealevelRpcClient {
signature: &Signature, signature: &Signature,
) -> ChainResult<EncodedConfirmedTransactionWithStatusMeta> { ) -> ChainResult<EncodedConfirmedTransactionWithStatusMeta> {
let config = RpcTransactionConfig { let config = RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::JsonParsed),
commitment: Some(CommitmentConfig::finalized()), commitment: Some(CommitmentConfig::finalized()),
..Default::default() ..Default::default()
}; };

@ -1,4 +1,4 @@
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError}; use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, NativeToken};
use url::Url; use url::Url;
/// Sealevel connection configuration /// Sealevel connection configuration
@ -8,6 +8,8 @@ pub struct ConnectionConf {
pub url: Url, pub url: Url,
/// Operation batching configuration /// Operation batching configuration
pub operation_batch: OperationBatchConfig, pub operation_batch: OperationBatchConfig,
/// Native token and its denomination
pub native_token: NativeToken,
} }
/// An error type when parsing a connection configuration. /// An error type when parsing a connection configuration.

@ -13,27 +13,35 @@ use hyperlane_core::H512;
use crate::utils::{decode_h512, from_base58}; use crate::utils::{decode_h512, from_base58};
/// This function searches for a transaction which dispatches Hyperlane message and returns /// This function searches for a transaction which specified instruction on Hyperlane message and
/// list of hashes of such transactions. /// returns list of hashes of such transactions.
/// ///
/// This function takes the mailbox program identifier and the identifier for PDA for storing /// This function takes the mailbox program identifier and the identifier for PDA for storing
/// a dispatched message and searches a message dispatch transaction in a list of transaction. /// a dispatched or delivered message and searches a message dispatch or delivery transaction
/// in a list of transactions.
///
/// The list of transaction is usually comes from a block. The function returns list of hashes /// The list of transaction is usually comes from a block. The function returns list of hashes
/// of such transactions. /// of such transactions and their relative index in the block.
/// ///
/// The transaction will be searched with the following criteria: /// The transaction will be searched with the following criteria:
/// 1. Transaction contains Mailbox program id in the list of accounts. /// 1. Transaction contains Mailbox program id in the list of accounts.
/// 2. Transaction contains dispatched message PDA in the list of accounts. /// 2. Transaction contains dispatched/delivered message PDA in the list of accounts.
/// 3. Transaction is performing message dispatch (OutboxDispatch). /// 3. Transaction is performing the specified message instruction.
/// ///
/// * `mailbox_program_id` - Identifier of Mailbox program /// * `mailbox_program_id` - Identifier of Mailbox program
/// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA /// * `message_storage_pda_pubkey` - Identifier for message store PDA
/// * `transactions` - List of transactions /// * `transactions` - List of transactions
pub fn search_dispatched_message_transactions( /// * `is_specified_message_instruction` - Function which returns `true` for specified message
/// instruction
pub fn search_message_transactions<F>(
mailbox_program_id: &Pubkey, mailbox_program_id: &Pubkey,
message_storage_pda_pubkey: &Pubkey, message_storage_pda_pubkey: &Pubkey,
transactions: Vec<EncodedTransactionWithStatusMeta>, transactions: Vec<EncodedTransactionWithStatusMeta>,
) -> Vec<(usize, H512)> { is_specified_message_instruction: &F,
) -> Vec<(usize, H512)>
where
F: Fn(Instruction) -> bool,
{
transactions transactions
.into_iter() .into_iter()
.enumerate() .enumerate()
@ -43,38 +51,43 @@ pub fn search_dispatched_message_transactions(
.map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions)) .map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions))
}) })
.filter_map(|(index, hash, account_keys, instructions)| { .filter_map(|(index, hash, account_keys, instructions)| {
filter_not_relevant( filter_by_relevancy(
mailbox_program_id, mailbox_program_id,
message_storage_pda_pubkey, message_storage_pda_pubkey,
hash, hash,
account_keys, account_keys,
instructions, instructions,
is_specified_message_instruction,
) )
.map(|hash| (index, hash)) .map(|hash| (index, hash))
}) })
.collect::<Vec<(usize, H512)>>() .collect::<Vec<(usize, H512)>>()
} }
fn filter_not_relevant( fn filter_by_relevancy<F>(
mailbox_program_id: &Pubkey, mailbox_program_id: &Pubkey,
message_storage_pda_pubkey: &Pubkey, message_storage_pda_pubkey: &Pubkey,
hash: H512, hash: H512,
account_keys: Vec<String>, account_keys: Vec<String>,
instructions: Vec<UiCompiledInstruction>, instructions: Vec<UiCompiledInstruction>,
) -> Option<H512> { is_specified_message_instruction: &F,
) -> Option<H512>
where
F: Fn(Instruction) -> bool,
{
let account_index_map = account_index_map(account_keys); let account_index_map = account_index_map(account_keys);
let mailbox_program_id_str = mailbox_program_id.to_string(); let mailbox_program_id_str = mailbox_program_id.to_string();
let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) { let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) {
Some(i) => *i as u8, Some(i) => *i as u8,
None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch/delivery.
}; };
let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string(); let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string();
let dispatch_message_pda_account_index = let message_storage_pda_account_index =
match account_index_map.get(&message_storage_pda_pubkey_str) { match account_index_map.get(&message_storage_pda_pubkey_str) {
Some(i) => *i as u8, Some(i) => *i as u8,
None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. None => return None, // If account keys do not contain dispatch/delivery message store PDA account, transaction is not message dispatch/delivery.
}; };
let mailbox_program_maybe = instructions let mailbox_program_maybe = instructions
@ -83,35 +96,43 @@ fn filter_not_relevant(
let mailbox_program = match mailbox_program_maybe { let mailbox_program = match mailbox_program_maybe {
Some(p) => p, Some(p) => p,
None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch/delivery.
}; };
// If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch. // If Mailbox program does not operate on dispatch/delivery message store PDA account, transaction is not message dispatch/delivery.
if !mailbox_program if !mailbox_program
.accounts .accounts
.contains(&dispatch_message_pda_account_index) .contains(&message_storage_pda_account_index)
{ {
return None; return None;
} }
let instruction_data = match from_base58(&mailbox_program.data) { let instruction_data = match from_base58(&mailbox_program.data) {
Ok(d) => d, Ok(d) => d,
Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch. Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch/delivery.
}; };
let instruction = match Instruction::from_instruction_data(&instruction_data) { let instruction = match Instruction::from_instruction_data(&instruction_data) {
Ok(ii) => ii, Ok(ii) => ii,
Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch. Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch/delivery.
}; };
// If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch. // If the call into Mailbox program is not OutboxDispatch/InboxProcess, transaction is not message dispatch/delivery.
if !matches!(instruction, Instruction::OutboxDispatch(_)) { if is_specified_message_instruction(instruction) {
return None; return None;
} }
Some(hash) Some(hash)
} }
pub fn is_message_dispatch_instruction(instruction: Instruction) -> bool {
!matches!(instruction, Instruction::OutboxDispatch(_))
}
pub fn is_message_delivery_instruction(instruction: Instruction) -> bool {
!matches!(instruction, Instruction::InboxProcess(_))
}
fn filter_by_validity( fn filter_by_validity(
tx: UiTransaction, tx: UiTransaction,
meta: UiTransactionStatusMeta, meta: UiTransactionStatusMeta,

@ -1,29 +1,57 @@
use solana_sdk::pubkey::Pubkey;
use solana_transaction_status::EncodedTransactionWithStatusMeta; use solana_transaction_status::EncodedTransactionWithStatusMeta;
use crate::transaction::search_dispatched_message_transactions; use crate::transaction::{
is_message_delivery_instruction, is_message_dispatch_instruction, search_message_transactions,
};
use crate::utils::decode_pubkey; use crate::utils::decode_pubkey;
#[test] #[test]
pub fn test_search_dispatched_message_transaction() { pub fn test_search_dispatched_message_transaction() {
// given // given
let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap();
let dispatched_message_pda_account = let dispatched_message_pda_account =
decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap(); decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap();
let transaction = serde_json::from_str::<EncodedTransactionWithStatusMeta>(JSON).unwrap(); let (mailbox_program_id, transactions) = transactions(DISPATCH_TXN_JSON);
let transactions = vec![transaction];
// when // when
let transaction_hashes = search_dispatched_message_transactions( let transaction_hashes = search_message_transactions(
&mailbox_program_id, &mailbox_program_id,
&dispatched_message_pda_account, &dispatched_message_pda_account,
transactions, transactions,
&is_message_dispatch_instruction,
); );
// then // then
assert!(!transaction_hashes.is_empty()); assert!(!transaction_hashes.is_empty());
} }
const JSON: &str = r#" #[test]
pub fn test_search_delivered_message_transaction() {
// given
let delivered_message_pda_account =
decode_pubkey("Dj7jk47KKXvw4nseNGdyHtNHtjPes2XSfByhF8xymrtS").unwrap();
let (mailbox_program_id, transactions) = transactions(DELIVERY_TXN_JSON);
// when
let transaction_hashes = search_message_transactions(
&mailbox_program_id,
&delivered_message_pda_account,
transactions,
&is_message_delivery_instruction,
);
// then
assert!(!transaction_hashes.is_empty());
}
fn transactions(json: &str) -> (Pubkey, Vec<EncodedTransactionWithStatusMeta>) {
let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap();
let transaction = serde_json::from_str::<EncodedTransactionWithStatusMeta>(json).unwrap();
let transactions = vec![transaction];
(mailbox_program_id, transactions)
}
const DISPATCH_TXN_JSON: &str = r#"
{ {
"blockTime": 1729865514, "blockTime": 1729865514,
"meta": { "meta": {
@ -327,3 +355,193 @@ const JSON: &str = r#"
} }
} }
"#; "#;
const DELIVERY_TXN_JSON: &str = r#"
{
"blockTime": 1726514134,
"meta": {
"computeUnitsConsumed": 200654,
"err": null,
"fee": 5000,
"innerInstructions": [
{
"index": 1,
"instructions": [
{
"accounts": [
10
],
"data": "8YGwT5LUTP4",
"programIdIndex": 9,
"stackHeight": 2
},
{
"accounts": [
12
],
"data": "2848tnNKZjKzgKikguTY4s5nESn7KLYUbLsrp6Z1FYq4BmM31xRwBXnJU5RW9rEvRUjJfJa58kXdgQYEQpg4sDrRfx5HnGsgXfitkxJw5NKVcFAYLSqKvpkYxer2tAn3a8ZzPvuDD9iqyLkvJnRZ3TbcoAHNisFfvBeWK95YL8zxsyzDS9ZBMaoYrLKQx9b915xj9oijw2UNk7FF5qxThZDKwF8rwckb6t2o6ypzFEqYeQCsRW5quayYsLBjHi8RdY18NDkcnPVkQbdR7FmfrncV4H5ZYZaayMtgAs6kHxRgeuuBEtrYG1UbGjWTQAss9zmeXcKipqS3S2bee96U5w9Cd981e8dkakCtKR7KusjE9nhsFTfXoxcwkRhi3TzqDicrqt7Erf78K",
"programIdIndex": 8,
"stackHeight": 2
},
{
"accounts": [
0,
3
],
"data": "11117UpxCJ2YqmddN2ykgdMGRXkyPgnqEtj5XYrnk1iC4P1xrvXq2zvZQkj3uNaitHEw2k",
"programIdIndex": 5,
"stackHeight": 2
},
{
"accounts": [
11,
5,
10,
1,
5,
2
],
"data": "7MHiQP8ahsZcB5cM9ZXGa2foMYQENm7GnrFaV4AmfgKNzSndaXhrcqbVNRgN2kGmrrsfTi8bNEGkAJn6MWjY95PnakaF2HAchXrUUBzQrWKQdRp8VbKjDsnH1tEUiAWm439Y12TpWTW3uSphh1oycpTJP",
"programIdIndex": 9,
"stackHeight": 2
},
{
"accounts": [
2,
1
],
"data": "3Bxs4ThwQbE4vyj5",
"programIdIndex": 5,
"stackHeight": 3
}
]
}
],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program ComputeBudget111111111111111111111111111111 invoke [1]",
"Program ComputeBudget111111111111111111111111111111 success",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi invoke [1]",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg invoke [2]",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg consumed 4402 of 1363482 compute units",
"Program return: 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg AA==",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg success",
"Program 372D5YP7jMYUgYBXTVJ7BZtzKv1mq1J6wvjSFLNTRreC invoke [2]",
"Program 372D5YP7jMYUgYBXTVJ7BZtzKv1mq1J6wvjSFLNTRreC consumed 106563 of 1353660 compute units",
"Program 372D5YP7jMYUgYBXTVJ7BZtzKv1mq1J6wvjSFLNTRreC success",
"Program 11111111111111111111111111111111 invoke [2]",
"Program 11111111111111111111111111111111 success",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg invoke [2]",
"Program 11111111111111111111111111111111 invoke [3]",
"Program 11111111111111111111111111111111 success",
"Program log: Warp route transfer completed from origin: 1408864445, recipient: 528MctBmY7rXqufM3r8k7t9DTfVNuB4K1rr8xVU4naJM, remote_amount: 100000",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg consumed 28117 of 1240216 compute units",
"Program 4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg success",
"Program log: Hyperlane inbox processed message 0x34ed0705362554568a1a2d24aef6bfde71894dd1bb2f0457fb4bd66016074fcc",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi consumed 200504 of 1399850 compute units",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi success"
],
"postBalances": [
338367600,
199691000,
891880,
1287600,
1211040,
1,
1,
1141440,
1141440,
1141440,
2686560,
0,
8017920,
1141440
],
"postTokenBalances": [],
"preBalances": [
339660200,
199591000,
991880,
0,
1211040,
1,
1,
1141440,
1141440,
1141440,
2686560,
0,
8017920,
1141440
],
"preTokenBalances": [],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 290198208,
"transaction": {
"message": {
"accountKeys": [
"G5FM3UKwcBJ47PwLWLLY1RQpqNtTMgnqnd6nZGcJqaBp",
"528MctBmY7rXqufM3r8k7t9DTfVNuB4K1rr8xVU4naJM",
"5H4cmX5ybSqK6Ro6nvr9eiR8G8ATTYRwVsZ42VRRW3wa",
"Dj7jk47KKXvw4nseNGdyHtNHtjPes2XSfByhF8xymrtS",
"H3EgdESu59M4hn5wrbeyi9VjmFiLYM7iUAbGtrA5uHNE",
"11111111111111111111111111111111",
"ComputeBudget111111111111111111111111111111",
"noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV",
"372D5YP7jMYUgYBXTVJ7BZtzKv1mq1J6wvjSFLNTRreC",
"4UMNyNWW75zo69hxoJaRX5iXNUa5FdRPZZa9vDVCiESg",
"A2nmLy86tmraneRMEZ5yWbDGq6YsPKNcESGaTZKkRWZU",
"DmU32nL975xAshVYgLLdyMoaUzHa2aCzHJyfLyKRdz3M",
"E2jimXLCtTiuZ6jbXP8B7SyZ5vVc1PKYYnMeho9yJ1en",
"E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi"
],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 9,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [],
"data": "K1FDJ7",
"programIdIndex": 6,
"stackHeight": null
},
{
"accounts": [
0,
5,
4,
11,
3,
10,
7,
8,
12,
9,
5,
10,
1,
5,
2
],
"data": "3RwSrioTudpACxczi2EejzKoZCPVuzq6qWLCQYAWoZoTcRPBobUn7tB5SFvMPNHGJ551rmjXDyKdaQLuzX3d5bjHSrSsquwHqWgM6L2kMEEJZjtygNyx3RhJD9GyZqekDuK19cfYfn1dyLuo7SSqswV3t6yptLhnCv8DhxBLRuXhV2GdNy9PLU3VNc9PvPWxg1Grtr9UZ5GnmdKDeqRvonM9AqmuN6mnv3UaqjjAEX8yDKPhWHm6w1HRzfgbjkXQVL5aSqdgJeF3EVBKJCzvMKbUVjTRgD6iHQyUVrSYvrHpKZxc6EctBHN6tyeZrW5RD1M6giasnm4WqrjDwUyz9xwvk31srJrZp7W7D6i2tTajmBbiKjpNo75iaHj4dycf1H",
"programIdIndex": 13,
"stackHeight": null
}
],
"recentBlockhash": "AzQN8x5uKk7ExXW4eUu2FiqRG1BX73uvfHcQeBDHcu8a"
},
"signatures": [
"5pBEVfDD3siir1CBf9taeWuee44GspA7EixYkKnzN1hkeYXLxtKYrbe3aE6hxswbY3hhDRVPDor1ZsSXUorC7bcR"
]
}
}
"#;

File diff suppressed because it is too large Load Diff

@ -31,7 +31,7 @@
"interchainAccountIsm": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", "interchainAccountIsm": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E",
"interchainAccountRouter": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", "interchainAccountRouter": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b",
"interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297",
"interchainSecurityModule": "0xC8513429105955cf01669bfD1ac5396Faf0748a5", "interchainSecurityModule": "0xDf1d3c37FfA6134767911B8876305afc187dA207",
"isTestnet": true, "isTestnet": true,
"mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59",
"merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa",
@ -94,7 +94,7 @@
"from": 49690504 "from": 49690504
}, },
"interchainGasPaymaster": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", "interchainGasPaymaster": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8",
"interchainSecurityModule": "0xA6D6d30c37434b142618eF97AB15a71871d721C6", "interchainSecurityModule": "0x69a84432Ba4FaD95FC5850aCD613C6daD286908C",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8",
"merkleTreeHook": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", "merkleTreeHook": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C",
@ -162,7 +162,7 @@
"from": 13851043 "from": 13851043
}, },
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x3E857CB33b76f680F3dB557Ce3BBf2591A98d92d", "interchainSecurityModule": "0x2945eCB46AE83B1A37b589A9c1219061522A73aD",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -232,7 +232,7 @@
"interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE", "interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE",
"interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2", "interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2",
"interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949",
"interchainSecurityModule": "0x2B3bEc44051C3A0c26360Ae513e98A947E9939b7", "interchainSecurityModule": "0x0c29F3Ce8995b41eB8c2b3E1eC33c3fa10C8cf91",
"isTestnet": true, "isTestnet": true,
"mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D",
"merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f",
@ -301,7 +301,7 @@
"from": 4950 "from": 4950
}, },
"interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", "interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450",
"interchainSecurityModule": "0x1e58386A3f012D69568B3E1aB5f8E41169Ba69A9", "interchainSecurityModule": "0xa25786D36B5a5eDeCAf75142dD056B1Ed1473f44",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17", "merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
@ -402,7 +402,7 @@
"from": 1606754 "from": 1606754
}, },
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x4c8A96b43fD59a4171b7c79d657AD9FedFb2d7B5", "interchainSecurityModule": "0x1AFC8F84cAE294C3E6d3Ddb031946B93f08272b5",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -468,7 +468,7 @@
"interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311", "interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311",
"interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b", "interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b",
"interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E",
"interchainSecurityModule": "0xe412A2d273c02d6f837532946d1B05A6EAB72B04", "interchainSecurityModule": "0x7b5AD17cdAbdED0C29B161c647DA32dCE51AB13B",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", "mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0",
"merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", "merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612",
@ -534,7 +534,7 @@
"from": 1543015 "from": 1543015
}, },
"interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", "interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9",
"interchainSecurityModule": "0xA4bFAA24c14f0398903E59344F4a36334F47AA50", "interchainSecurityModule": "0x16738b80D39Fa0652F2D853c3E6235Afb7cA6dDc",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", "mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc",
"merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", "merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE",
@ -599,7 +599,7 @@
"from": 15833917 "from": 15833917
}, },
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x4c8A96b43fD59a4171b7c79d657AD9FedFb2d7B5", "interchainSecurityModule": "0x81B81B3b296ecf99d5bAC0DE4e5fF7a3ceECf08b",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -727,7 +727,7 @@
"from": 10634605 "from": 10634605
}, },
"interchainGasPaymaster": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", "interchainGasPaymaster": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"interchainSecurityModule": "0xC5117582A9b64B5b3071B7f11943b21A515A84C6", "interchainSecurityModule": "0x428a2384F6013A9c561737E9A58288e071470192",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x54148470292C24345fb828B003461a9444414517", "mailbox": "0x54148470292C24345fb828B003461a9444414517",
"merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", "merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
@ -802,7 +802,7 @@
"interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350", "interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350",
"interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF", "interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF",
"interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"interchainSecurityModule": "0x69873c153380149e901b4aD031025Bc195ee1CB8", "interchainSecurityModule": "0xb9A0fa05Fcce52605f0142c1A5Aca32f223eB961",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68",
"merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0",
@ -880,7 +880,7 @@
"interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02", "interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02",
"interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d", "interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d",
"interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56",
"interchainSecurityModule": "0x43b6a311BF787241BB71b7aE2a29ef639932b9b8", "interchainSecurityModule": "0x5bC248C8010848067919fA73F4555AeE95Df38a4",
"isTestnet": true, "isTestnet": true,
"mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766",
"merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d",
@ -990,7 +990,7 @@
"from": 3111622 "from": 3111622
}, },
"interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", "interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450",
"interchainSecurityModule": "0x1e58386A3f012D69568B3E1aB5f8E41169Ba69A9", "interchainSecurityModule": "0x8a833B1230A75712a767e81556F1Ad612A0F76F3",
"isTestnet": true, "isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17", "merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
@ -1078,7 +1078,7 @@
"interchainAccountIsm": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "interchainAccountIsm": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainAccountRouter": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", "interchainAccountRouter": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainGasPaymaster": "0x04438ef7622f5412f82915F59caD4f704C61eA48", "interchainGasPaymaster": "0x04438ef7622f5412f82915F59caD4f704C61eA48",
"interchainSecurityModule": "0xDabB212640f59026a861202ca82CDcD8181aD723", "interchainSecurityModule": "0x5e39968B0435332dE915C2FF62fD373fBbbb7C75",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", "merkleTreeHook": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"pausableHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "pausableHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
@ -1138,7 +1138,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3", "interchainSecurityModule": "0x97b1e5FBF33d82fAD9C3Aa0fF901e9B9d63090E3",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1206,7 +1206,7 @@
"interchainAccountIsm": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", "interchainAccountIsm": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72",
"interchainAccountRouter": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", "interchainAccountRouter": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c",
"interchainGasPaymaster": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", "interchainGasPaymaster": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"interchainSecurityModule": "0x792F905736703DCb511066f2E0C4b97504CD2728", "interchainSecurityModule": "0xAfDF88EB9447e412c89304F34813c5564307709b",
"mailbox": "0xB08d78F439e55D02C398519eef61606A5926245F", "mailbox": "0xB08d78F439e55D02C398519eef61606A5926245F",
"merkleTreeHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "merkleTreeHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
"pausableHook": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c", "pausableHook": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c",
@ -1267,7 +1267,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3", "interchainSecurityModule": "0xe5B27D2198EEacf4FeC7d558CdE3b4fDCe4d37f9",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1395,7 +1395,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3", "interchainSecurityModule": "0x8714Caec7020c611DA73811E897A879C22d7C396",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1459,7 +1459,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3", "interchainSecurityModule": "0x0511f73F8D734e0fc9df1E6C0937592a3A371B18",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1673,7 +1673,7 @@
"from": 111 "from": 111
} }
}, },
"arcadiatestnet": { "arcadiatestnet2": {
"blockExplorers": [ "blockExplorers": [
{ {
"apiUrl": "https://explorer.khalani.network/api", "apiUrl": "https://explorer.khalani.network/api",
@ -1688,10 +1688,10 @@
"reorgPeriod": 1 "reorgPeriod": 1
}, },
"chainId": 1098411886, "chainId": 1098411886,
"displayName": "Arcadia Testnet", "displayName": "Arcadia Testnet v2",
"domainId": 1098411886, "domainId": 1098411886,
"isTestnet": true, "isTestnet": true,
"name": "arcadiatestnet", "name": "arcadiatestnet2",
"nativeToken": { "nativeToken": {
"decimals": 18, "decimals": 18,
"name": "Ether", "name": "Ether",
@ -1703,33 +1703,33 @@
"http": "https://rpc.khalani.network" "http": "https://rpc.khalani.network"
} }
], ],
"aggregationHook": "0x862Ce2De59C13a0406c104d317CfaEf6B672D638", "aggregationHook": "0x602160148F2e2A40bc42BADD5f5936aCFd431a35",
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", "domainRoutingIsm": "0x2589992a07E664c20123c6232620Af479F9ba7DC",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", "domainRoutingIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"fallbackRoutingHook": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", "fallbackRoutingHook": "0x7483faD0Bc297667664A43A064bA7c9911659f57",
"interchainAccountIsm": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", "interchainAccountIsm": "0x39c85C84876479694A2470c0E8075e9d68049aFc",
"interchainAccountRouter": "0xBF2C366530C1269d531707154948494D3fF4AcA7", "interchainAccountRouter": "0x80fE4Cb8c70fc60B745d4ffD4403c27a8cBC9e02",
"interchainGasPaymaster": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", "interchainGasPaymaster": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb",
"interchainSecurityModule": "0xd89063A7e8Eaee25dA8D3b7eBcbAeF9869702A80", "interchainSecurityModule": "0x3865c419335B36d9CE240b515124Ab1c33927004",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065",
"merkleTreeHook": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", "merkleTreeHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"pausableHook": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", "pausableHook": "0x4fE19d49F45854Da50b6009258929613EC92C147",
"pausableIsm": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", "pausableIsm": "0xc76E477437065093D353b7d56c81ff54D167B0Ab",
"protocolFee": "0x01812D60958798695391dacF092BAc4a715B1718", "protocolFee": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643",
"proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", "proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B",
"staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "staticAggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"staticAggregationIsm": "0x206789B0d838568eaFDcCa1e551FCF5c00bF99E2", "staticAggregationIsm": "0x0b071Eb80757Dd347B3B8736C7ba9e5324c37D77",
"staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "staticAggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", "staticMerkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "staticMerkleRootWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "staticMessageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "staticMessageIdWeightedMultisigIsmFactory": "0x54148470292C24345fb828B003461a9444414517",
"storageGasOracle": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", "storageGasOracle": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"testRecipient": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", "testRecipient": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16",
"timelockController": "0x0000000000000000000000000000000000000000", "timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "validatorAnnounce": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC",
"index": { "index": {
"from": 5243565 "from": 251532
}, },
"deployer": { "deployer": {
"name": "Abacus Works", "name": "Abacus Works",
@ -1773,7 +1773,7 @@
"interchainAccountIsm": "0xc08675806BA844467E559E45E4bB59e66778bDcd", "interchainAccountIsm": "0xc08675806BA844467E559E45E4bB59e66778bDcd",
"interchainAccountRouter": "0x39c85C84876479694A2470c0E8075e9d68049aFc", "interchainAccountRouter": "0x39c85C84876479694A2470c0E8075e9d68049aFc",
"interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", "interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainSecurityModule": "0x8a5D09753Ab5571fa78131EF839C70AFa3c45bFd", "interchainSecurityModule": "0x4B310268158B842cbE65A216714C6A7D1d087155",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", "merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE",
"pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", "pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F",
@ -1836,7 +1836,7 @@
"interchainAccountIsm": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606", "interchainAccountIsm": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606",
"interchainAccountRouter": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", "interchainAccountRouter": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44",
"interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", "interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainSecurityModule": "0x4B2e8f63E345Db18973E46cE70972cE3D76585Bf", "interchainSecurityModule": "0xAd6172DA241CE5DC38a32E0E375FD0A1889b9E48",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", "merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE",
"pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", "pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F",
@ -1899,7 +1899,7 @@
"interchainAccountIsm": "0xBF2C366530C1269d531707154948494D3fF4AcA7", "interchainAccountIsm": "0xBF2C366530C1269d531707154948494D3fF4AcA7",
"interchainAccountRouter": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", "interchainAccountRouter": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680",
"interchainGasPaymaster": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "interchainGasPaymaster": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainSecurityModule": "0x9e71cC1A91E48CfFA2F7D2956eB5c3b730bD8605", "interchainSecurityModule": "0xa6570241124A6534801d1eba13F46078Dc7d1974",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", "merkleTreeHook": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72",
"pausableHook": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", "pausableHook": "0xc76E477437065093D353b7d56c81ff54D167B0Ab",
@ -1924,6 +1924,246 @@
"name": "Abacus Works", "name": "Abacus Works",
"url": "https://www.hyperlane.xyz" "url": "https://www.hyperlane.xyz"
} }
},
"alephzeroevmtestnet": {
"blockExplorers": [
{
"apiUrl": "https://evm-explorer-testnet.alephzero.org/api",
"family": "blockscout",
"name": "Aleph Zero EVM Testnet Explorer",
"url": "https://evm-explorer-testnet.alephzero.org"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 5
},
"chainId": 2039,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Aleph Zero EVM Testnet",
"domainId": 2039,
"index": {
"from": 1380870
},
"isTestnet": true,
"name": "alephzeroevmtestnet",
"nativeToken": {
"decimals": 18,
"name": "Testnet AZERO",
"symbol": "TZERO"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.alephzero-testnet.gelato.digital"
}
],
"technicalStack": "arbitrumnitro",
"aggregationHook": "0xf63f12A71d730794F8de247c65a40E5BF8fA590A",
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"fallbackRoutingHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"interchainAccountIsm": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c",
"interchainAccountRouter": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670",
"interchainGasPaymaster": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainSecurityModule": "0xbe84F098eE49c32395edA629737AD3f4c0542ADA",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c",
"pausableHook": "0x7483faD0Bc297667664A43A064bA7c9911659f57",
"pausableIsm": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"protocolFee": "0x5e65279Fb7293a058776e37587398fcc3E9184b1",
"proxyAdmin": "0x54148470292C24345fb828B003461a9444414517",
"staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticAggregationIsm": "0x1897d03A682C0AA04e2C018B8Edc33A379bf9610",
"staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"storageGasOracle": "0x4fE19d49F45854Da50b6009258929613EC92C147",
"testRecipient": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0xBF2C366530C1269d531707154948494D3fF4AcA7"
},
"inksepolia": {
"blockExplorers": [
{
"apiUrl": "https://explorer-sepolia.inkonchain.com/api",
"family": "blockscout",
"name": "https://explorer-sepolia.inkonchain.com",
"url": "https://explorer-sepolia.inkonchain.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 1
},
"chainId": 763373,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Ink Sepolia",
"domainId": 763373,
"isTestnet": true,
"name": "inksepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc-qnd-sepolia.inkonchain.com"
}
],
"technicalStack": "opstack",
"aggregationHook": "0xDd77EFE606DD4e9601D8E13CF3caAcCcacD6bb3c",
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"fallbackRoutingHook": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainGasPaymaster": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38",
"interchainSecurityModule": "0x3490059390DDc4de38822488A1D63B4e131D0Aaf",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x4fE19d49F45854Da50b6009258929613EC92C147",
"pausableHook": "0x01812D60958798695391dacF092BAc4a715B1718",
"pausableIsm": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"protocolFee": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC",
"proxyAdmin": "0x54148470292C24345fb828B003461a9444414517",
"staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticAggregationIsm": "0x16977B194B3d61aA30F70A5521ac6bbfaa4CF460",
"staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"storageGasOracle": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c",
"testRecipient": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868",
"validatorAnnounce": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680",
"index": {
"from": 1915290
}
},
"abstracttestnet": {
"blockExplorers": [
{
"apiUrl": "https://api-explorer-verify.testnet.abs.xyz/contract_verification",
"family": "etherscan",
"name": "Abstract Block Explorer",
"url": "https://explorer.testnet.abs.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 11124,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Abstract Testnet",
"domainId": 11124,
"isTestnet": true,
"name": "abstracttestnet",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://api.testnet.abs.xyz"
}
],
"technicalStack": "zksync",
"domainRoutingIsm": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8",
"domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000",
"fallbackDomainRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a",
"fallbackRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a",
"interchainGasPaymaster": "0xbAaE1B4e953190b05C757F69B2F6C46b9548fa4f",
"interchainSecurityModule": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8",
"mailbox": "0x28f448885bEaaF662f8A9A6c9aF20fAd17A5a1DC",
"merkleTreeHook": "0x7fa6009b59F139813eA710dB5496976eE8D80E64",
"proxyAdmin": "0xfbA0c57A6BA24B5440D3e2089222099b4663B98B",
"staticAggregationHookFactory": "0x0000000000000000000000000000000000000000",
"staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"storageGasOracle": "0x5F1bADC7e28B9b4C98f58dB4e5841e5bf63A7A52",
"testRecipient": "0x9EC79CA89DeF61BFa2f38cD4fCC137b9e49d60dD",
"validatorAnnounce": "0xfE9a467831a28Ec3D54deCCf0A2A41fa77dDD1D7",
"index": {
"from": 964305
}
},
"treasuretopaz": {
"blockExplorers": [
{
"apiUrl": "https://rpc-explorer-verify.topaz.treasure.lol/contract_verification",
"family": "etherscan",
"name": "Treasure Topaz Block Explorer",
"url": "https://topaz.treasurescan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 978658,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Treasure Topaz Testnet",
"displayNameShort": "Treasure Testnet",
"domainId": 978658,
"isTestnet": true,
"name": "treasuretopaz",
"nativeToken": {
"decimals": 18,
"name": "MAGIC",
"symbol": "MAGIC"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.topaz.treasure.lol"
}
],
"technicalStack": "zksync",
"domainRoutingIsm": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8",
"domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000",
"fallbackDomainRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a",
"fallbackRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a",
"interchainGasPaymaster": "0xbAaE1B4e953190b05C757F69B2F6C46b9548fa4f",
"interchainSecurityModule": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8",
"mailbox": "0x28f448885bEaaF662f8A9A6c9aF20fAd17A5a1DC",
"merkleTreeHook": "0x7fa6009b59F139813eA710dB5496976eE8D80E64",
"proxyAdmin": "0xfbA0c57A6BA24B5440D3e2089222099b4663B98B",
"staticAggregationHookFactory": "0x0000000000000000000000000000000000000000",
"staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"storageGasOracle": "0x5F1bADC7e28B9b4C98f58dB4e5841e5bf63A7A52",
"testRecipient": "0x9EC79CA89DeF61BFa2f38cD4fCC137b9e49d60dD",
"validatorAnnounce": "0xfE9a467831a28Ec3D54deCCf0A2A41fa77dDD1D7",
"index": {
"from": 86008
}
} }
}, },
"defaultRpcConsensusType": "fallback" "defaultRpcConsensusType": "fallback"

@ -15,7 +15,6 @@ color-eyre = { workspace = true, optional = true }
config.workspace = true config.workspace = true
console-subscriber.workspace = true console-subscriber.workspace = true
convert_case.workspace = true convert_case.workspace = true
derive_builder.workspace = true
derive-new.workspace = true derive-new.workspace = true
ed25519-dalek.workspace = true ed25519-dalek.workspace = true
ethers.workspace = true ethers.workspace = true

@ -8,8 +8,7 @@ use hyperlane_core::config::*;
use tracing::info; use tracing::info;
use crate::{ use crate::{
create_chain_metrics, metrics::{AgentMetrics, CoreMetrics},
metrics::{create_agent_metrics, AgentMetrics, CoreMetrics},
settings::Settings, settings::Settings,
ChainMetrics, ChainMetrics,
}; };
@ -88,8 +87,8 @@ pub async fn agent_main<A: BaseAgent>() -> Result<()> {
let metrics = settings.as_ref().metrics(A::AGENT_NAME)?; let metrics = settings.as_ref().metrics(A::AGENT_NAME)?;
let tokio_server = core_settings.tracing.start_tracing(&metrics)?; let tokio_server = core_settings.tracing.start_tracing(&metrics)?;
let agent_metrics = create_agent_metrics(&metrics)?; let agent_metrics = AgentMetrics::new(&metrics)?;
let chain_metrics = create_chain_metrics(&metrics)?; let chain_metrics = ChainMetrics::new(&metrics)?;
let agent = A::from_settings( let agent = A::from_settings(
agent_metadata, agent_metadata,
settings, settings,

@ -4,7 +4,6 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use derive_builder::Builder;
use eyre::Result; use eyre::Result;
use hyperlane_core::metrics::agent::decimals_by_protocol; use hyperlane_core::metrics::agent::decimals_by_protocol;
use hyperlane_core::metrics::agent::u256_as_scaled_f64; use hyperlane_core::metrics::agent::u256_as_scaled_f64;
@ -45,8 +44,14 @@ pub const GAS_PRICE_LABELS: &[&str] = &["chain"];
pub const GAS_PRICE_HELP: &str = pub const GAS_PRICE_HELP: &str =
"Tracks the current gas price of the chain, in the lowest denomination (e.g. wei)"; "Tracks the current gas price of the chain, in the lowest denomination (e.g. wei)";
/// Expected label names for the `critical_error` metric.
pub const CRITICAL_ERROR_LABELS: &[&str] = &["chain"];
/// Help string for the metric.
pub const CRITICAL_ERROR_HELP: &str =
"Boolean marker for critical errors on a chain, signalling loss of liveness";
/// Agent-specific metrics /// Agent-specific metrics
#[derive(Clone, Builder, Debug)] #[derive(Clone, Debug)]
pub struct AgentMetrics { pub struct AgentMetrics {
/// Current balance of native tokens for the /// Current balance of native tokens for the
/// wallet address. /// wallet address.
@ -57,27 +62,28 @@ pub struct AgentMetrics {
/// - `token_address`: Address of the token. /// - `token_address`: Address of the token.
/// - `token_symbol`: Symbol of the token. /// - `token_symbol`: Symbol of the token.
/// - `token_name`: Full name of the token. /// - `token_name`: Full name of the token.
#[builder(setter(into, strip_option), default)]
wallet_balance: Option<GaugeVec>, wallet_balance: Option<GaugeVec>,
} }
pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result<AgentMetrics> { impl AgentMetrics {
Ok(AgentMetricsBuilder::default() pub(crate) fn new(metrics: &CoreMetrics) -> Result<AgentMetrics> {
.wallet_balance(metrics.new_gauge( let agent_metrics = AgentMetrics {
wallet_balance: Some(metrics.new_gauge(
"wallet_balance", "wallet_balance",
WALLET_BALANCE_HELP, WALLET_BALANCE_HELP,
WALLET_BALANCE_LABELS, WALLET_BALANCE_LABELS,
)?) )?),
.build()?) };
Ok(agent_metrics)
}
} }
/// Chain-specific metrics /// Chain-specific metrics
#[derive(Clone, Builder, Debug)] #[derive(Clone, Debug)]
pub struct ChainMetrics { pub struct ChainMetrics {
/// Tracks the current block height of the chain. /// Tracks the current block height of the chain.
/// - `chain`: the chain name (or ID if the name is unknown) of the chain /// - `chain`: the chain name (or ID if the name is unknown) of the chain
/// the block number refers to. /// the block number refers to.
#[builder(setter(into))]
pub block_height: IntGaugeVec, pub block_height: IntGaugeVec,
/// Tracks the current gas price of the chain. Uses the base_fee_per_gas if /// Tracks the current gas price of the chain. Uses the base_fee_per_gas if
@ -85,19 +91,45 @@ pub struct ChainMetrics {
/// TODO: use the median of the transactions. /// TODO: use the median of the transactions.
/// - `chain`: the chain name (or chain ID if the name is unknown) of the /// - `chain`: the chain name (or chain ID if the name is unknown) of the
/// chain the gas price refers to. /// chain the gas price refers to.
#[builder(setter(into, strip_option), default)]
pub gas_price: Option<GaugeVec>, pub gas_price: Option<GaugeVec>,
/// Boolean marker for critical errors on a chain, signalling loss of liveness.
critical_error: IntGaugeVec,
} }
pub(crate) fn create_chain_metrics(metrics: &CoreMetrics) -> Result<ChainMetrics> { impl ChainMetrics {
Ok(ChainMetricsBuilder::default() pub(crate) fn new(metrics: &CoreMetrics) -> Result<ChainMetrics> {
.block_height(metrics.new_int_gauge( let block_height_metrics =
"block_height", metrics.new_int_gauge("block_height", BLOCK_HEIGHT_HELP, BLOCK_HEIGHT_LABELS)?;
BLOCK_HEIGHT_HELP, let gas_price_metrics = metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?;
BLOCK_HEIGHT_LABELS, let critical_error_metrics =
)?) metrics.new_int_gauge("critical_error", CRITICAL_ERROR_HELP, CRITICAL_ERROR_LABELS)?;
.gas_price(metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?) let chain_metrics = ChainMetrics {
.build()?) block_height: block_height_metrics,
gas_price: Some(gas_price_metrics),
critical_error: critical_error_metrics,
};
Ok(chain_metrics)
}
pub(crate) fn set_gas_price(&self, chain: &str, price: f64) {
if let Some(gas_price) = &self.gas_price {
gas_price.with(&hashmap! { "chain" => chain }).set(price);
}
}
pub(crate) fn set_block_height(&self, chain: &str, height: i64) {
self.block_height
.with(&hashmap! { "chain" => chain })
.set(height);
}
/// Flag that a critical error has occurred on the chain
pub fn set_critical_error(&self, chain: &str, is_critical: bool) {
self.critical_error
.with(&hashmap! { "chain" => chain })
.set(is_critical as i64);
}
} }
/// Configuration for the prometheus middleware. This can be loaded via serde. /// Configuration for the prometheus middleware. This can be loaded via serde.
@ -174,8 +206,6 @@ impl MetricsUpdater {
} }
async fn update_block_details(&self) { async fn update_block_details(&self) {
let block_height = self.chain_metrics.block_height.clone();
let gas_price = self.chain_metrics.gas_price.clone();
if let HyperlaneDomain::Unknown { .. } = self.conf.domain { if let HyperlaneDomain::Unknown { .. } = self.conf.domain {
return; return;
}; };
@ -195,10 +225,8 @@ impl MetricsUpdater {
let height = chain_metrics.latest_block.number as i64; let height = chain_metrics.latest_block.number as i64;
trace!(chain, height, "Fetched block height for metrics"); trace!(chain, height, "Fetched block height for metrics");
block_height self.chain_metrics.set_block_height(chain, height);
.with(&hashmap! { "chain" => chain }) if self.chain_metrics.gas_price.is_some() {
.set(height);
if let Some(gas_price) = gas_price {
let protocol = self.conf.domain.domain_protocol(); let protocol = self.conf.domain.domain_protocol();
let decimals_scale = 10f64.powf(decimals_by_protocol(protocol).into()); let decimals_scale = 10f64.powf(decimals_by_protocol(protocol).into());
let gas = u256_as_scaled_f64(chain_metrics.min_gas_price.unwrap_or_default(), protocol) let gas = u256_as_scaled_f64(chain_metrics.min_gas_price.unwrap_or_default(), protocol)
@ -208,7 +236,7 @@ impl MetricsUpdater {
gas = format!("{gas:.2}"), gas = format!("{gas:.2}"),
"Gas price updated for chain (using lowest denomination)" "Gas price updated for chain (using lowest denomination)"
); );
gas_price.with(&hashmap! { "chain" => chain }).set(gas); self.chain_metrics.set_gas_price(chain, gas);
} }
} }

@ -2,9 +2,9 @@ use eyre::eyre;
use url::Url; use url::Url;
use h_eth::TransactionOverrides; use h_eth::TransactionOverrides;
use hyperlane_core::config::{ConfigErrResultExt, OperationBatchConfig}; use hyperlane_core::config::{ConfigErrResultExt, OperationBatchConfig};
use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol}; use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol, NativeToken};
use hyperlane_cosmos::NativeToken;
use crate::settings::envs::*; use crate::settings::envs::*;
use crate::settings::ChainConnectionConf; use crate::settings::ChainConnectionConf;
@ -137,24 +137,7 @@ pub fn build_cosmos_connection_conf(
.parse_u64() .parse_u64()
.end(); .end();
let native_token_decimals = chain let native_token = parse_native_token(chain, err, 18);
.chain(err)
.get_key("nativeToken")
.get_key("decimals")
.parse_u32()
.unwrap_or(18);
let native_token_denom = chain
.chain(err)
.get_key("nativeToken")
.get_key("denom")
.parse_string()
.unwrap_or("");
let native_token = NativeToken {
decimals: native_token_decimals,
denom: native_token_denom.to_owned(),
};
if !local_err.is_ok() { if !local_err.is_ok() {
err.merge(local_err); err.merge(local_err);
@ -174,6 +157,45 @@ pub fn build_cosmos_connection_conf(
} }
} }
fn build_sealevel_connection_conf(
url: &Url,
chain: &ValueParser,
err: &mut ConfigParsingError,
operation_batch: OperationBatchConfig,
) -> h_sealevel::ConnectionConf {
let native_token = parse_native_token(chain, err, 9);
h_sealevel::ConnectionConf {
url: url.clone(),
operation_batch,
native_token,
}
}
fn parse_native_token(
chain: &ValueParser,
err: &mut ConfigParsingError,
default_decimals: u32,
) -> NativeToken {
let native_token_decimals = chain
.chain(err)
.get_opt_key("nativeToken")
.get_opt_key("decimals")
.parse_u32()
.unwrap_or(default_decimals);
let native_token_denom = chain
.chain(err)
.get_opt_key("nativeToken")
.get_opt_key("denom")
.parse_string()
.unwrap_or("");
NativeToken {
decimals: native_token_decimals,
denom: native_token_denom.to_owned(),
}
}
pub fn build_connection_conf( pub fn build_connection_conf(
domain_protocol: HyperlaneDomainProtocol, domain_protocol: HyperlaneDomainProtocol,
rpcs: &[Url], rpcs: &[Url],
@ -195,10 +217,12 @@ pub fn build_connection_conf(
.next() .next()
.map(|url| ChainConnectionConf::Fuel(h_fuel::ConnectionConf { url: url.clone() })), .map(|url| ChainConnectionConf::Fuel(h_fuel::ConnectionConf { url: url.clone() })),
HyperlaneDomainProtocol::Sealevel => rpcs.iter().next().map(|url| { HyperlaneDomainProtocol::Sealevel => rpcs.iter().next().map(|url| {
ChainConnectionConf::Sealevel(h_sealevel::ConnectionConf { ChainConnectionConf::Sealevel(build_sealevel_connection_conf(
url: url.clone(), url,
chain,
err,
operation_batch, operation_batch,
}) ))
}), }),
HyperlaneDomainProtocol::Cosmos => { HyperlaneDomainProtocol::Cosmos => {
build_cosmos_connection_conf(rpcs, chain, err, operation_batch) build_cosmos_connection_conf(rpcs, chain, err, operation_batch)

@ -17,6 +17,7 @@ pub use indexing::*;
pub use log_metadata::*; pub use log_metadata::*;
pub use merkle_tree::*; pub use merkle_tree::*;
pub use message::*; pub use message::*;
pub use native_token::NativeToken;
pub use reorg::*; pub use reorg::*;
pub use transaction::*; pub use transaction::*;
@ -33,6 +34,7 @@ mod indexing;
mod log_metadata; mod log_metadata;
mod merkle_tree; mod merkle_tree;
mod message; mod message;
mod native_token;
mod reorg; mod reorg;
mod serialize; mod serialize;
mod transaction; mod transaction;

@ -0,0 +1,8 @@
/// Chain native token denomination and number of decimal places
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct NativeToken {
/// The number of decimal places in token which can be expressed by denomination
pub decimals: u32,
/// Denomination of the token
pub denom: String,
}

@ -5,7 +5,7 @@ use std::str::FromStr;
#[cfg(feature = "float")] #[cfg(feature = "float")]
use std::time::Duration; use std::time::Duration;
use crate::{KnownHyperlaneDomain, H160, H256}; use crate::{KnownHyperlaneDomain, H160, H256, U256};
/// Converts a hex or base58 string to an H256. /// Converts a hex or base58 string to an H256.
pub fn hex_or_base58_to_h256(string: &str) -> Result<H256> { pub fn hex_or_base58_to_h256(string: &str) -> Result<H256> {
@ -62,6 +62,17 @@ pub fn bytes_to_hex(bytes: &[u8]) -> String {
format!("0x{}", hex::encode(bytes)) format!("0x{}", hex::encode(bytes))
} }
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Converts `value` expressed with `decimals` into `atto` (`10^-18`) decimals.
pub fn to_atto(value: U256, decimals: u32) -> Option<U256> {
assert!(decimals <= ATTO_EXPONENT);
let exponent = ATTO_EXPONENT - decimals;
let coefficient = U256::from(10u128.pow(exponent));
value.checked_mul(coefficient)
}
/// Format a domain id as a name if it is known or just the number if not. /// Format a domain id as a name if it is known or just the number if not.
pub fn fmt_domain(domain: u32) -> String { pub fn fmt_domain(domain: u32) -> String {
#[cfg(feature = "strum")] #[cfg(feature = "strum")]

@ -1,8 +1,10 @@
use std::{collections::BTreeMap, path::PathBuf}; use std::{collections::BTreeMap, path::PathBuf};
use hyperlane_cosmos::{NativeToken, RawCosmosAmount};
use hyperlane_cosmwasm_interface::types::bech32_decode; use hyperlane_cosmwasm_interface::types::bech32_decode;
use hyperlane_core::NativeToken;
use hyperlane_cosmos::RawCosmosAmount;
use super::{cli::OsmosisCLI, CosmosNetwork}; use super::{cli::OsmosisCLI, CosmosNetwork};
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]

@ -192,6 +192,7 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
app_config: &TokenConfig, app_config: &TokenConfig,
program_id: Pubkey, program_id: Pubkey,
) { ) {
let try_fund_ata_payer = |ctx: &mut Context, client: &RpcClient| {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount { if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!( if matches!(
app_config.token_type, app_config.token_type,
@ -200,11 +201,16 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount); fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
} }
} }
};
let (token_pda, _token_bump) = let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id); Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
if account_exists(client, &token_pda).unwrap() { if account_exists(client, &token_pda).unwrap() {
println!("Warp route token already exists, skipping init"); println!("Warp route token already exists, skipping init");
// Fund the ATA payer up to the specified amount.
try_fund_ata_payer(ctx, client);
return; return;
} }
@ -398,6 +404,8 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
println!("Set the {authority} authority to the mint account. Status: {status}"); println!("Set the {authority} authority to the mint account. Status: {status}");
} }
} }
try_fund_ata_payer(ctx, client);
} }
/// Sets gas router configs on all deployable chains. /// Sets gas router configs on all deployable chains.

@ -80,6 +80,45 @@
], ],
"technicalStack": "arbitrumnitro" "technicalStack": "arbitrumnitro"
}, },
"apechain": {
"blockExplorers": [
{
"apiUrl": "https://apechain.calderaexplorer.xyz/api",
"family": "blockscout",
"name": "ApeChain Explorer",
"url": "https://apechain.calderaexplorer.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.2,
"reorgPeriod": 5
},
"chainId": 33139,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "ApeChain",
"domainId": 33139,
"gasCurrencyCoinGeckoId": "apecoin",
"index": {
"from": 1759561
},
"name": "apechain",
"nativeToken": {
"decimals": 18,
"name": "ApeCoin",
"symbol": "APE"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.apechain.com/http"
}
],
"technicalStack": "arbitrumnitro"
},
"arbitrum": { "arbitrum": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -126,6 +165,45 @@
], ],
"technicalStack": "arbitrumnitro" "technicalStack": "arbitrumnitro"
}, },
"arbitrumnova": {
"blockExplorers": [
{
"apiUrl": "https://api-nova.arbiscan.io/api",
"family": "etherscan",
"name": "Arbiscan Nova",
"url": "https://nova.arbiscan.io/"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 42170,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Arbitrum Nova",
"domainId": 42170,
"gasCurrencyCoinGeckoId": "ethereum",
"index": {
"from": 78794208
},
"name": "arbitrumnova",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://nova.arbitrum.io/rpc"
}
],
"technicalStack": "arbitrumnitro"
},
"astar": { "astar": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -138,7 +216,7 @@
"blocks": { "blocks": {
"confirmations": 1, "confirmations": 1,
"estimateBlockTime": 13, "estimateBlockTime": 13,
"reorgPeriod": 32 "reorgPeriod": "finalized"
}, },
"chainId": 592, "chainId": 592,
"deployer": { "deployer": {
@ -245,6 +323,42 @@
], ],
"technicalStack": "other" "technicalStack": "other"
}, },
"b3": {
"blockExplorers": [
{
"apiUrl": "https://explorer.b3.fun/api",
"family": "blockscout",
"name": "B3 Explorer",
"url": "https://explorer.b3.fun"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 8333,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "B3",
"domainId": 8333,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "b3",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet-rpc.b3.fun"
}
],
"technicalStack": "opstack"
},
"base": { "base": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -419,7 +533,7 @@
"blocks": { "blocks": {
"confirmations": 1, "confirmations": 1,
"estimateBlockTime": 3, "estimateBlockTime": 3,
"reorgPeriod": 15 "reorgPeriod": "finalized"
}, },
"chainId": 56, "chainId": 56,
"deployer": { "deployer": {
@ -899,6 +1013,57 @@
], ],
"technicalStack": "arbitrumnitro" "technicalStack": "arbitrumnitro"
}, },
"fantom": {
"blockExplorers": [
{
"apiUrl": "https://api.ftmscan.com/api",
"family": "etherscan",
"name": "FTMScan",
"url": "https://ftmscan.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 250,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Fantom Opera",
"domainId": 250,
"gasCurrencyCoinGeckoId": "fantom",
"name": "fantom",
"nativeToken": {
"decimals": 18,
"name": "Fantom",
"symbol": "FTM"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpcapi.fantom.network"
},
{
"http": "https://fantom-rpc.publicnode.com"
},
{
"http": "https://fantom-pokt.nodies.app"
},
{
"http": "https://rpc.fantom.network"
},
{
"http": "https://rpc2.fantom.network"
},
{
"http": "https://rpc3.fantom.network"
}
],
"technicalStack": "other"
},
"flare": { "flare": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -1111,6 +1276,91 @@
], ],
"technicalStack": "other" "technicalStack": "other"
}, },
"gravity": {
"blockExplorers": [
{
"apiUrl": "https://explorer.gravity.xyz/api",
"family": "blockscout",
"name": "Gravity Alpha Explorer",
"url": "https://explorer.gravity.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 1625,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Gravity Alpha Mainnet",
"displayNameShort": "Gravity",
"domainId": 1625,
"gasCurrencyCoinGeckoId": "g-token",
"index": {
"from": 13374779
},
"name": "gravity",
"nativeToken": {
"decimals": 18,
"name": "Gravity",
"symbol": "G"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.gravity.xyz"
}
],
"technicalStack": "arbitrumnitro"
},
"harmony": {
"blockExplorers": [
{
"apiUrl": "https://explorer.harmony.one/api",
"family": "blockscout",
"name": "Harmony Explorer",
"url": "https://explorer.harmony.one"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 1666600000,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Harmony One",
"domainId": 1666600000,
"gasCurrencyCoinGeckoId": "harmony",
"name": "harmony",
"nativeToken": {
"decimals": 18,
"name": "ONE",
"symbol": "ONE"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://api.harmony.one"
},
{
"http": "https://api.s0.t.hmny.io"
},
{
"http": "https://1rpc.io/one"
},
{
"http": "https://rpc.ankr.com/harmony"
}
],
"technicalStack": "other"
},
"immutablezkevm": { "immutablezkevm": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -1199,13 +1449,22 @@
}, },
"injective": { "injective": {
"bech32Prefix": "inj", "bech32Prefix": "inj",
"blockExplorers": [], "blockExplorers": [
{
"apiUrl": "https://www.mintscan.io/injective",
"family": "other",
"name": "Mintscan",
"url": "https://www.mintscan.io/injective"
}
],
"blocks": { "blocks": {
"confirmations": 1, "confirmations": 1,
"estimateBlockTime": 1, "estimateBlockTime": 1,
"reorgPeriod": 10 "reorgPeriod": 10
}, },
"canonicalAsset": "inj",
"chainId": "injective-1", "chainId": "injective-1",
"contractAddressBytes": 20,
"deployer": { "deployer": {
"name": "Abacus Works", "name": "Abacus Works",
"url": "https://www.hyperlane.xyz" "url": "https://www.hyperlane.xyz"
@ -1213,11 +1472,19 @@
"displayName": "Injective", "displayName": "Injective",
"domainId": 6909546, "domainId": 6909546,
"gasCurrencyCoinGeckoId": "injective-protocol", "gasCurrencyCoinGeckoId": "injective-protocol",
"gasPrice": {
"amount": "700000000",
"denom": "inj"
},
"grpcUrls": [ "grpcUrls": [
{ {
"http": "sentry.chain.grpc.injective.network:443" "http": "https://injective-grpc.goldenratiostaking.net:443"
} }
], ],
"index": {
"chunk": 25,
"from": 58419500
},
"name": "injective", "name": "injective",
"nativeToken": { "nativeToken": {
"decimals": 18, "decimals": 18,
@ -1239,6 +1506,42 @@
"slip44": 118, "slip44": 118,
"technicalStack": "other" "technicalStack": "other"
}, },
"kaia": {
"blockExplorers": [
{
"apiUrl": "https://api-cypress.klaytnscope.com/api",
"family": "etherscan",
"name": "Kaiascope",
"url": "https://kaiascope.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 8217,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Kaia",
"domainId": 8217,
"gasCurrencyCoinGeckoId": "kaia",
"name": "kaia",
"nativeToken": {
"decimals": 18,
"name": "Kaia",
"symbol": "KLAY"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://public-en.node.kaia.io"
}
],
"technicalStack": "other"
},
"kroma": { "kroma": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -1766,7 +2069,7 @@
"blocks": { "blocks": {
"confirmations": 2, "confirmations": 2,
"estimateBlockTime": 12, "estimateBlockTime": 12,
"reorgPeriod": 10 "reorgPeriod": "finalized"
}, },
"chainId": 1284, "chainId": 1284,
"deployer": { "deployer": {
@ -1795,6 +2098,42 @@
"maxPriorityFeePerGas": 50000000000 "maxPriorityFeePerGas": 50000000000
} }
}, },
"morph": {
"blockExplorers": [
{
"apiUrl": "https://explorer-api.morphl2.io/api",
"family": "blockscout",
"name": "Morph Explorer",
"url": "https://explorer.morphl2.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 4,
"reorgPeriod": 5
},
"chainId": 2818,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Morph",
"domainId": 2818,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "morph",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.morphl2.io"
}
],
"technicalStack": "other"
},
"neutron": { "neutron": {
"bech32Prefix": "neutron", "bech32Prefix": "neutron",
"blockExplorers": [ "blockExplorers": [
@ -1810,7 +2149,9 @@
"estimateBlockTime": 3, "estimateBlockTime": 3,
"reorgPeriod": 1 "reorgPeriod": 1
}, },
"canonicalAsset": "untrn",
"chainId": "neutron-1", "chainId": "neutron-1",
"contractAddressBytes": 32,
"deployer": { "deployer": {
"name": "Abacus Works", "name": "Abacus Works",
"url": "https://www.hyperlane.xyz" "url": "https://www.hyperlane.xyz"
@ -1818,12 +2159,19 @@
"displayName": "Neutron", "displayName": "Neutron",
"domainId": 1853125230, "domainId": 1853125230,
"gasCurrencyCoinGeckoId": "neutron-3", "gasCurrencyCoinGeckoId": "neutron-3",
"gasPrice": {
"amount": "0.0053",
"denom": "untrn"
},
"grpcUrls": [ "grpcUrls": [
{ {
"http": "grpc-kralum.neutron-1.neutron.org:80" "http": "http://grpc-kralum.neutron-1.neutron.org:80"
} }
], ],
"isTestnet": false, "index": {
"chunk": 5,
"from": 4000000
},
"name": "neutron", "name": "neutron",
"nativeToken": { "nativeToken": {
"decimals": 6, "decimals": 6,
@ -1924,6 +2272,45 @@
], ],
"technicalStack": "opstack" "technicalStack": "opstack"
}, },
"orderly": {
"blockExplorers": [
{
"apiUrl": "https://explorer.orderly.network/api",
"family": "blockscout",
"name": "Orderly L2 Explorer",
"url": "https://explorer.orderly.network"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 291,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Orderly L2",
"domainId": 291,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "orderly",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.orderly.network"
},
{
"http": "https://l2-orderly-mainnet-0.t.conduit.xyz"
}
],
"technicalStack": "opstack"
},
"osmosis": { "osmosis": {
"bech32Prefix": "osmo", "bech32Prefix": "osmo",
"blockExplorers": [ "blockExplorers": [
@ -1939,7 +2326,9 @@
"estimateBlockTime": 3, "estimateBlockTime": 3,
"reorgPeriod": 1 "reorgPeriod": 1
}, },
"canonicalAsset": "uosmo",
"chainId": "osmosis-1", "chainId": "osmosis-1",
"contractAddressBytes": 32,
"deployer": { "deployer": {
"name": "Mitosis", "name": "Mitosis",
"url": "https://mitosis.org" "url": "https://mitosis.org"
@ -1947,11 +2336,19 @@
"displayName": "Osmosis", "displayName": "Osmosis",
"domainId": 875, "domainId": 875,
"gasCurrencyCoinGeckoId": "osmosis", "gasCurrencyCoinGeckoId": "osmosis",
"gasPrice": {
"amount": "0.025",
"denom": "uosmo"
},
"grpcUrls": [ "grpcUrls": [
{ {
"http": "osmosis-grpc.publicnode.com:443" "http": "https://osmosis-grpc.publicnode.com:443"
} }
], ],
"index": {
"chunk": 10,
"from": 14389169
},
"isTestnet": false, "isTestnet": false,
"name": "osmosis", "name": "osmosis",
"nativeToken": { "nativeToken": {
@ -1989,7 +2386,7 @@
"blocks": { "blocks": {
"confirmations": 3, "confirmations": 3,
"estimateBlockTime": 2, "estimateBlockTime": 2,
"reorgPeriod": 256 "reorgPeriod": "finalized"
}, },
"chainId": 137, "chainId": 137,
"deployer": { "deployer": {
@ -2434,7 +2831,7 @@
"blocks": { "blocks": {
"confirmations": 1, "confirmations": 1,
"estimateBlockTime": 5, "estimateBlockTime": 5,
"reorgPeriod": 5 "reorgPeriod": "finalized"
}, },
"chainId": 109, "chainId": 109,
"deployer": { "deployer": {
@ -2461,6 +2858,42 @@
], ],
"technicalStack": "other" "technicalStack": "other"
}, },
"snaxchain": {
"blockExplorers": [
{
"apiUrl": "https://explorer.snaxchain.io/api",
"family": "blockscout",
"name": "Snaxchain Mainnet Explorer",
"url": "https://explorer.snaxchain.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 2192,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "SnaxChain",
"domainId": 2192,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "snaxchain",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet.snaxchain.io"
}
],
"technicalStack": "opstack"
},
"solanamainnet": { "solanamainnet": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -2512,7 +2945,9 @@
"estimateBlockTime": 5, "estimateBlockTime": 5,
"reorgPeriod": 1 "reorgPeriod": 1
}, },
"canonicalAsset": "ustrd",
"chainId": "stride-1", "chainId": "stride-1",
"contractAddressBytes": 32,
"deployer": { "deployer": {
"name": "Stride Labs", "name": "Stride Labs",
"url": "https://www.stride.zone" "url": "https://www.stride.zone"
@ -2520,12 +2955,19 @@
"displayName": "Stride", "displayName": "Stride",
"domainId": 745, "domainId": 745,
"gasCurrencyCoinGeckoId": "stride", "gasCurrencyCoinGeckoId": "stride",
"gasPrice": {
"amount": "0.025",
"denom": "ustrd"
},
"grpcUrls": [ "grpcUrls": [
{ {
"http": "stride-grpc.polkachu.com:12290" "http": "https://stride-grpc.publicnode.com:443"
} }
], ],
"isTestnet": false, "index": {
"chunk": 5,
"from": 9152000
},
"name": "stride", "name": "stride",
"nativeToken": { "nativeToken": {
"decimals": 6, "decimals": 6,
@ -2637,7 +3079,7 @@
"blocks": { "blocks": {
"confirmations": 1, "confirmations": 1,
"estimateBlockTime": 6, "estimateBlockTime": 6,
"reorgPeriod": 10 "reorgPeriod": "finalized"
}, },
"chainId": 5845, "chainId": 5845,
"deployer": { "deployer": {
@ -2732,7 +3174,7 @@
"protocol": "ethereum", "protocol": "ethereum",
"rpcUrls": [ "rpcUrls": [
{ {
"http": "https://raas-backend.alchemy.com/rpc/worldchain-mainnet/rollup" "http": "https://worldchain-mainnet.g.alchemy.com/public"
} }
], ],
"technicalStack": "opstack" "technicalStack": "opstack"
@ -2817,6 +3259,42 @@
], ],
"technicalStack": "polygoncdk" "technicalStack": "polygoncdk"
}, },
"zeronetwork": {
"blockExplorers": [
{
"apiUrl": "https://zero-network-api.calderaexplorer.xyz/api",
"family": "etherscan",
"name": "Zero Network Explorer",
"url": "https://zerion-explorer.vercel.app"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 543210,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Zero Network",
"domainId": 543210,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "zeronetwork",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://zero-network.calderachain.xyz"
}
],
"technicalStack": "zksync"
},
"zetachain": { "zetachain": {
"blockExplorers": [ "blockExplorers": [
{ {
@ -2900,6 +3378,42 @@
], ],
"technicalStack": "opstack" "technicalStack": "opstack"
}, },
"zksync": {
"blockExplorers": [
{
"apiUrl": "https://block-explorer-api.mainnet.zksync.io/api",
"family": "etherscan",
"name": "zkSync Explorer",
"url": "https://explorer.zksync.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 324,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "zkSync",
"domainId": 324,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "zksync",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet.era.zksync.io"
}
],
"technicalStack": "zksync"
},
"zoramainnet": { "zoramainnet": {
"blockExplorers": [ "blockExplorers": [
{ {

@ -17,6 +17,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"apechain": {
"threshold": 2,
"validators": [
"0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"arbitrum": { "arbitrum": {
"threshold": 3, "threshold": 3,
"validators": [ "validators": [
@ -28,6 +37,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"arbitrumnova": {
"threshold": 2,
"validators": [
"0xd2a5e9123308d187383c87053811a2c21bd8af1f",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"astar": { "astar": {
"threshold": 2, "threshold": 2,
"validators": [ "validators": [
@ -55,6 +73,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"b3": {
"threshold": 2,
"validators": [
"0xd77b516730a836fc41934e7d5864e72c165b934e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"base": { "base": {
"threshold": 3, "threshold": 3,
"validators": [ "validators": [
@ -118,7 +145,8 @@
"threshold": 2, "threshold": 2,
"validators": [ "validators": [
"0x478fb53c6860ae8fc35235ba0d38d49b13128226", "0x478fb53c6860ae8fc35235ba0d38d49b13128226",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x101cE77261245140A0871f9407d6233C8230Ec47"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -198,6 +226,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"fantom": {
"threshold": 2,
"validators": [
"0xa779572028e634e16f26af5dfd4fa685f619457d",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"flare": { "flare": {
"threshold": 2, "threshold": 2,
"validators": [ "validators": [
@ -244,6 +281,24 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"gravity": {
"threshold": 2,
"validators": [
"0x23d549bf757a02a6f6068e9363196ecd958c974e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"harmony": {
"threshold": 2,
"validators": [
"0xd677803a67651974b1c264171b5d7ca8838db8d5",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"immutablezkevm": { "immutablezkevm": {
"threshold": 2, "threshold": 2,
"validators": [ "validators": [
@ -271,6 +326,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"kaia": {
"threshold": 2,
"validators": [
"0x9de0b3abb221d19719882fa4d61f769fdc2be9a4",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"kroma": { "kroma": {
"threshold": 2, "threshold": 2,
"validators": [ "validators": [
@ -290,11 +354,14 @@
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"lisk": { "lisk": {
"threshold": 2, "threshold": 4,
"validators": [ "validators": [
"0xc0b282aa5bac43fee83cf71dc3dd1797c1090ea5", "0xc0b282aa5bac43fee83cf71dc3dd1797c1090ea5",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" "0x3DA4ee2801Ec6CC5faD73DBb94B10A203ADb3d9e",
"0x4df6e8878992c300e7bfe98cac6bf7d3408b9cbf",
"0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0xf0da628f3fb71652d48260bad4691054045832ce"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -375,12 +442,14 @@
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"mode": { "mode": {
"threshold": 3, "threshold": 4,
"validators": [ "validators": [
"0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7", "0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0x7e29608c6e5792bbf9128599ca309be0728af7b4", "0x65C140e3a05F33192384AffEF985696Fe3cDDE42",
"0x101cE77261245140A0871f9407d6233C8230Ec47" "0x20eade18ea2af6dfd54d72b3b5366b40fcb47f4b",
"0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0x485a4f0009d9afbbf44521016f9b8cdd718e36ea"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -403,6 +472,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"morph": {
"threshold": 2,
"validators": [
"0x4884535f393151ec419add872100d352f71af380",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"neutron": { "neutron": {
"threshold": 4, "threshold": 4,
"validators": [ "validators": [
@ -426,13 +504,23 @@
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"optimism": { "optimism": {
"threshold": 3, "threshold": 4,
"validators": [ "validators": [
"0x20349eadc6c72e94ce38268b96692b1a5c20de4f", "0x20349eadc6c72e94ce38268b96692b1a5c20de4f",
"0x5b7d47b76c69740462432f6a5a0ca5005e014157", "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xd8c1cCbfF28413CE6c6ebe11A3e29B0D8384eDbB",
"0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", "0x1b9e5f36c4bfdb0e3f0df525ef5c888a4459ef99",
"0x5450447aee7b544c462c9352bef7cad049b0c2dc" "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0xf9dfaa5c20ae1d84da4b2696b8dc80c919e48b12"
],
"type": "messageIdMultisigIsm"
},
"orderly": {
"threshold": 2,
"validators": [
"0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -499,11 +587,12 @@
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"redstone": { "redstone": {
"threshold": 2, "threshold": 3,
"validators": [ "validators": [
"0x1400b9737007f7978d8b4bbafb4a69c83f0641a7", "0x1400b9737007f7978d8b4bbafb4a69c83f0641a7",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36",
"0x101cE77261245140A0871f9407d6233C8230Ec47"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -554,6 +643,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"snaxchain": {
"threshold": 2,
"validators": [
"0x2c25829ae32a772d2a49f6c4b34f8b01fd03ef9e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"solanamainnet": { "solanamainnet": {
"threshold": 3, "threshold": 3,
"validators": [ "validators": [
@ -577,7 +675,7 @@
"0x87460dcEd16a75AECdBffD4189111d30B099f5b0", "0x87460dcEd16a75AECdBffD4189111d30B099f5b0",
"0xf54982134e52Eb7253236943FBffE0886C5bde0C", "0xf54982134e52Eb7253236943FBffE0886C5bde0C",
"0x5937b7cE1029C3Ec4bD8e1AaCc0C0f9422654D7d", "0x5937b7cE1029C3Ec4bD8e1AaCc0C0f9422654D7d",
"0x3a446ed2923c08445af06e53f0acb558c0e0413c" "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b"
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
@ -645,6 +743,15 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"zeronetwork": {
"threshold": 2,
"validators": [
"0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"zetachain": { "zetachain": {
"threshold": 3, "threshold": 3,
"validators": [ "validators": [
@ -665,6 +772,16 @@
], ],
"type": "messageIdMultisigIsm" "type": "messageIdMultisigIsm"
}, },
"zksync": {
"threshold": 3,
"validators": [
"0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36",
"0x75237d42ce8ea27349a0254ada265db94157e0c1"
],
"type": "messageIdMultisigIsm"
},
"zoramainnet": { "zoramainnet": {
"threshold": 3, "threshold": 3,
"validators": [ "validators": [

@ -0,0 +1,10 @@
{
"solanamainnet": {
"hex": "0x709e5cd98def7e0acd1cd2f6b7b56a783f1ec212b34a6903c33fc6e2823a9ee9",
"base58": "8acihSm2QTGswniKgdgr4JBvJihZ1cakfvbqWCPBLoSp"
},
"eclipsemainnet": {
"hex": "0x6b0fb225a14358827459d705f4c98e87a7b243b3638f111779b6fb605c5d8306",
"base58": "8CvWJS7SPtauAXinJUURkBDLsGUXWiiTdENkEFUPjQ9j"
}
}

@ -0,0 +1,17 @@
{
"solanamainnet": {
"type": "collateral",
"decimals": 6,
"interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF",
"token": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
"splTokenProgram": "token"
},
"eclipsemainnet": {
"type": "synthetic",
"decimals": 6,
"name": "Orca",
"symbol": "ORCA",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/ORCA/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
}
}

@ -0,0 +1,14 @@
{
"solanamainnet": {
"hex": "0x9f9fcd70f9772f30e4cd5646890b640e88c6b82a8417f813aaf81dac0cd468b9",
"base58": "Bk79wMjvpPCh5iQcCEjPWFcG1V2TfgdwaBsWBEYFYSNU"
},
"eclipsemainnet": {
"hex": "0x4571f343c286dc844237b550560b90bf5e748a551d60721599b1ac0408d9c08d",
"base58": "5g5ujyYUNvdydwyDVCpZwPpgYRqH5RYJRi156cxyE3me"
},
"ethereum": {
"hex": "0x000000000000000000000000647c621ceb36853ef6a907e397adf18568e70543",
"base58": "1111111111112QCPXWTcdXUC6jAEEnqQtpRo5TXG"
}
}

@ -0,0 +1,23 @@
{
"solanamainnet": {
"type": "collateral",
"decimals": 6,
"interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF",
"token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"splTokenProgram": "token"
},
"eclipsemainnet": {
"type": "synthetic",
"decimals": 6,
"name": "Tether USD",
"symbol": "USDT",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/USDT/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 6,
"token": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"foreignDeployment": "0x647C621CEb36853Ef6A907E397Adf18568E70543"
}
}

@ -0,0 +1,10 @@
{
"eclipsemainnet": {
"hex": "0x8751a99baf21a9cbc78e891d99ad8ca04bca545f5b668671a07cd0ec107923d2",
"base58": "A7EGCDYFw5R7Jfm6cYtKvY8dmkrYMgwRCJFkyQwpHTYu"
},
"ethereum": {
"hex": "0x0000000000000000000000005b4e223de74ef8c3218e66eecc541003cab3121a",
"base58": "1111111111112Gn8JRuL53pFjhHVdUdsv2Rzocp9"
}
}

@ -0,0 +1,16 @@
{
"eclipsemainnet": {
"type": "synthetic",
"decimals": 8,
"name": "Wrapped BTC",
"symbol": "WBTC",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/WBTC/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 8,
"token": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"foreignDeployment": "0x5B4e223DE74ef8c3218e66EEcC541003CAB3121A"
}
}

@ -0,0 +1,10 @@
{
"eclipsemainnet": {
"hex": "0x6196f9127d2caa0f4265d0870d077e6bf979c585b87cceb7537fc6d2c81729b7",
"base58": "7Zx4wU1QAw98MfvnPFqRh1oyumek7G5VAX6TKB3U1tcn"
},
"ethereum": {
"hex": "0x000000000000000000000000ef899e92da472e014be795ecce948308958e25a2",
"base58": "1111111111114LZDAaWurQERWF32Wqjijy9NWQRX"
}
}

@ -0,0 +1,17 @@
{
"eclipsemainnet": {
"type": "synthetic",
"decimals": 9,
"remoteDecimals": 18,
"name": "Super Symbiotic LRT",
"symbol": "weETHs",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/6ce62e9330ae1af0786f60d9fe6da8541b6b2988/deployments/warp_routes/weETHs/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 18,
"token": "0x917cee801a67f933f2e6b33fc0cd1ed2d5909d88",
"foreignDeployment": "0xef899e92DA472E014bE795Ecce948308958E25A2"
}
}

@ -1,5 +1,28 @@
# @hyperlane-xyz/core # @hyperlane-xyz/core
## 5.7.1
### Patch Changes
- Updated dependencies [e3b97c455]
- @hyperlane-xyz/utils@6.0.0
## 5.7.0
### Minor Changes
- 469f2f340: Checking for sufficient fees in `AbstractMessageIdAuthHook` and refund surplus
- f26453ee5: Added msg.value to preverifyMessage to commit it as part of external hook payload
- 0640f837c: disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user.
- a82b4b4cb: Made processInboundMessage payable to send value via mailbox.process
### Patch Changes
- Updated dependencies [e104cf6aa]
- Updated dependencies [04108155d]
- Updated dependencies [39a9b2038]
- @hyperlane-xyz/utils@5.7.0
## 5.6.1 ## 5.6.1
### Patch Changes ### Patch Changes

@ -10,7 +10,7 @@ import {IInterchainSecurityModule, ISpecifiesInterchainSecurityModule} from "./i
import {IPostDispatchHook} from "./interfaces/hooks/IPostDispatchHook.sol"; import {IPostDispatchHook} from "./interfaces/hooks/IPostDispatchHook.sol";
import {IMessageRecipient} from "./interfaces/IMessageRecipient.sol"; import {IMessageRecipient} from "./interfaces/IMessageRecipient.sol";
import {IMailbox} from "./interfaces/IMailbox.sol"; import {IMailbox} from "./interfaces/IMailbox.sol";
import {PackageVersioned} from "contracts/PackageVersioned.sol"; import {PackageVersioned} from "./PackageVersioned.sol";
// ============ External Imports ============ // ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol";
@ -56,6 +56,7 @@ contract Mailbox is
address processor; address processor;
uint48 blockNumber; uint48 blockNumber;
} }
mapping(bytes32 => Delivery) internal deliveries; mapping(bytes32 => Delivery) internal deliveries;
// ============ Events ============ // ============ Events ============

@ -7,5 +7,5 @@ pragma solidity >=0.6.11;
**/ **/
abstract contract PackageVersioned { abstract contract PackageVersioned {
// GENERATED CODE - DO NOT EDIT // GENERATED CODE - DO NOT EDIT
string public constant PACKAGE_VERSION = "5.6.1"; string public constant PACKAGE_VERSION = "5.7.1";
} }

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
interface IThresholdAddressFactory {
function deploy(
address[] calldata _values,
uint8 _threshold
) external returns (address);
}

@ -8,13 +8,14 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol"; import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IAggregationIsm} from "../../interfaces/isms/IAggregationIsm.sol"; import {IAggregationIsm} from "../../interfaces/isms/IAggregationIsm.sol";
import {AggregationIsmMetadata} from "../../isms/libs/AggregationIsmMetadata.sol"; import {AggregationIsmMetadata} from "../../isms/libs/AggregationIsmMetadata.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
/** /**
* @title AggregationIsm * @title AggregationIsm
* @notice Manages per-domain m-of-n ISM sets that are used to verify * @notice Manages per-domain m-of-n ISM sets that are used to verify
* interchain messages. * interchain messages.
*/ */
abstract contract AbstractAggregationIsm is IAggregationIsm { abstract contract AbstractAggregationIsm is IAggregationIsm, PackageVersioned {
// ============ Constants ============ // ============ Constants ============
// solhint-disable-next-line const-name-snakecase // solhint-disable-next-line const-name-snakecase

@ -12,7 +12,7 @@ import {PackageVersioned} from "contracts/PackageVersioned.sol";
* @notice Manages per-domain m-of-n ISM sets that are used to verify * @notice Manages per-domain m-of-n ISM sets that are used to verify
* interchain messages. * interchain messages.
*/ */
contract StaticAggregationIsm is AbstractAggregationIsm, PackageVersioned { contract StaticAggregationIsm is AbstractAggregationIsm {
// ============ Public Functions ============ // ============ Public Functions ============
/** /**

@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {AbstractAggregationIsm} from "./AbstractAggregationIsm.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IThresholdAddressFactory} from "../../interfaces/IThresholdAddressFactory.sol";
import {MinimalProxy} from "../../libs/MinimalProxy.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
// ============ External Imports ============
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
contract StorageAggregationIsm is
AbstractAggregationIsm,
Ownable2StepUpgradeable
{
address[] public modules;
uint8 public threshold;
event ModulesAndThresholdSet(address[] modules, uint8 threshold);
constructor(
address[] memory _modules,
uint8 _threshold
) Ownable2StepUpgradeable() {
modules = _modules;
threshold = _threshold;
_disableInitializers();
}
function initialize(
address _owner,
address[] memory _modules,
uint8 _threshold
) external initializer {
__Ownable2Step_init();
setModulesAndThreshold(_modules, _threshold);
_transferOwnership(_owner);
}
function setModulesAndThreshold(
address[] memory _modules,
uint8 _threshold
) public onlyOwner {
require(
0 < _threshold && _threshold <= _modules.length,
"Invalid threshold"
);
modules = _modules;
threshold = _threshold;
emit ModulesAndThresholdSet(_modules, _threshold);
}
function modulesAndThreshold(
bytes calldata /* _message */
) public view override returns (address[] memory, uint8) {
return (modules, threshold);
}
}
contract StorageAggregationIsmFactory is
IThresholdAddressFactory,
PackageVersioned
{
address public immutable implementation;
constructor() {
implementation = address(
new StorageAggregationIsm(new address[](1), 1)
);
}
/**
* @notice Emitted when a multisig module is deployed
* @param module The deployed ISM
*/
event ModuleDeployed(address module);
// ============ External Functions ============
function deploy(
address[] calldata _modules,
uint8 _threshold
) external returns (address ism) {
ism = MinimalProxy.create(implementation);
emit ModuleDeployed(ism);
StorageAggregationIsm(ism).initialize(msg.sender, _modules, _threshold);
}
}

@ -68,7 +68,7 @@ abstract contract AbstractMultisig is PackageVersioned {
* @notice Manages per-domain m-of-n Validator sets of AbstractMultisig that are used to verify * @notice Manages per-domain m-of-n Validator sets of AbstractMultisig that are used to verify
* interchain messages. * interchain messages.
*/ */
abstract contract AbstractMultisigIsm is AbstractMultisig { abstract contract AbstractMultisigIsm is AbstractMultisig, IMultisigIsm {
// ============ Virtual Functions ============ // ============ Virtual Functions ============
// ======= OVERRIDE THESE TO IMPLEMENT ======= // ======= OVERRIDE THESE TO IMPLEMENT =======

@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {AbstractMerkleRootMultisigIsm} from "./AbstractMerkleRootMultisigIsm.sol";
import {AbstractMessageIdMultisigIsm} from "./AbstractMessageIdMultisigIsm.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IThresholdAddressFactory} from "../../interfaces/IThresholdAddressFactory.sol";
import {MinimalProxy} from "../../libs/MinimalProxy.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
// ============ External Imports ============
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
abstract contract AbstractStorageMultisigIsm is
AbstractMultisigIsm,
Ownable2StepUpgradeable
{
address[] public validators;
uint8 public threshold;
event ValidatorsAndThresholdSet(address[] validators, uint8 threshold);
constructor(
address[] memory _validators,
uint8 _threshold
) Ownable2StepUpgradeable() {
validators = _validators;
threshold = _threshold;
_disableInitializers();
}
function initialize(
address _owner,
address[] memory _validators,
uint8 _threshold
) external initializer {
__Ownable2Step_init();
setValidatorsAndThreshold(_validators, _threshold);
_transferOwnership(_owner);
}
function setValidatorsAndThreshold(
address[] memory _validators,
uint8 _threshold
) public onlyOwner {
require(
0 < _threshold && _threshold <= _validators.length,
"Invalid threshold"
);
validators = _validators;
threshold = _threshold;
emit ValidatorsAndThresholdSet(_validators, _threshold);
}
function validatorsAndThreshold(
bytes calldata /* _message */
) public view override returns (address[] memory, uint8) {
return (validators, threshold);
}
}
contract StorageMerkleRootMultisigIsm is
AbstractMerkleRootMultisigIsm,
AbstractStorageMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MERKLE_ROOT_MULTISIG);
constructor(
address[] memory _validators,
uint8 _threshold
) AbstractStorageMultisigIsm(_validators, _threshold) {}
}
contract StorageMessageIdMultisigIsm is
AbstractMessageIdMultisigIsm,
AbstractStorageMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MESSAGE_ID_MULTISIG);
constructor(
address[] memory _validators,
uint8 _threshold
) AbstractStorageMultisigIsm(_validators, _threshold) {}
}
abstract contract StorageMultisigIsmFactory is
IThresholdAddressFactory,
PackageVersioned
{
/**
* @notice Emitted when a multisig module is deployed
* @param module The deployed ISM
*/
event ModuleDeployed(address module);
// ============ External Functions ============
function deploy(
address[] calldata _validators,
uint8 _threshold
) external returns (address ism) {
ism = MinimalProxy.create(implementation());
emit ModuleDeployed(ism);
AbstractStorageMultisigIsm(ism).initialize(
msg.sender,
_validators,
_threshold
);
}
function implementation() public view virtual returns (address);
}
contract StorageMerkleRootMultisigIsmFactory is StorageMultisigIsmFactory {
address internal immutable _implementation;
constructor() {
_implementation = address(
new StorageMerkleRootMultisigIsm(new address[](0), 0)
);
}
function implementation() public view override returns (address) {
return _implementation;
}
}
contract StorageMessageIdMultisigIsmFactory is StorageMultisigIsmFactory {
address internal immutable _implementation;
constructor() {
_implementation = address(
new StorageMessageIdMultisigIsm(new address[](0), 0)
);
}
function implementation() public view override returns (address) {
return _implementation;
}
}

@ -7,8 +7,12 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
// ============ Internal Imports ============ // ============ Internal Imports ============
import {MetaProxy} from "./MetaProxy.sol"; import {MetaProxy} from "./MetaProxy.sol";
import {PackageVersioned} from "../PackageVersioned.sol"; import {PackageVersioned} from "../PackageVersioned.sol";
import {IThresholdAddressFactory} from "../interfaces/IThresholdAddressFactory.sol";
abstract contract StaticThresholdAddressSetFactory is PackageVersioned { abstract contract StaticThresholdAddressSetFactory is
PackageVersioned,
IThresholdAddressFactory
{
// ============ Immutables ============ // ============ Immutables ============
address public immutable implementation; address public immutable implementation;
@ -32,6 +36,10 @@ abstract contract StaticThresholdAddressSetFactory is PackageVersioned {
address[] calldata _values, address[] calldata _values,
uint8 _threshold uint8 _threshold
) public returns (address) { ) public returns (address) {
require(
0 < _threshold && _threshold <= _values.length,
"Invalid threshold"
);
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode( (bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values, _values,
_threshold _threshold

@ -78,8 +78,8 @@ contract MockMailbox is Mailbox {
inboundProcessedNonce++; inboundProcessedNonce++;
} }
function processInboundMessage(uint32 _nonce) public { function processInboundMessage(uint32 _nonce) public payable {
bytes memory _message = inboundMessages[_nonce]; bytes memory _message = inboundMessages[_nonce];
Mailbox(address(this)).process("", _message); Mailbox(address(this)).process{value: msg.value}("", _message);
} }
} }

@ -1,11 +1,11 @@
{ {
"name": "@hyperlane-xyz/core", "name": "@hyperlane-xyz/core",
"description": "Core solidity contracts for Hyperlane", "description": "Core solidity contracts for Hyperlane",
"version": "5.6.1", "version": "5.7.1",
"dependencies": { "dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1", "@arbitrum/nitro-contracts": "^1.2.1",
"@eth-optimism/contracts": "^0.6.0", "@eth-optimism/contracts": "^0.6.0",
"@hyperlane-xyz/utils": "5.6.2", "@hyperlane-xyz/utils": "6.0.0",
"@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2",
"@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^v4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3",

@ -22,6 +22,8 @@ contract AggregationHookTest is Test {
uint8 n, uint8 n,
uint256 fee uint256 fee
) internal returns (address[] memory) { ) internal returns (address[] memory) {
vm.assume(n > 0);
address[] memory hooks = new address[](n); address[] memory hooks = new address[](n);
for (uint8 i = 0; i < n; i++) { for (uint8 i = 0; i < n; i++) {
TestPostDispatchHook subHook = new TestPostDispatchHook(); TestPostDispatchHook subHook = new TestPostDispatchHook();

@ -7,6 +7,8 @@ import "@openzeppelin/contracts/utils/Strings.sol";
import {IAggregationIsm} from "../../contracts/interfaces/isms/IAggregationIsm.sol"; import {IAggregationIsm} from "../../contracts/interfaces/isms/IAggregationIsm.sol";
import {StaticAggregationIsmFactory} from "../../contracts/isms/aggregation/StaticAggregationIsmFactory.sol"; import {StaticAggregationIsmFactory} from "../../contracts/isms/aggregation/StaticAggregationIsmFactory.sol";
import {IThresholdAddressFactory} from "../../contracts/interfaces/IThresholdAddressFactory.sol";
import {StorageAggregationIsmFactory} from "../../contracts/isms/aggregation/StorageAggregationIsm.sol";
import {AggregationIsmMetadata} from "../../contracts/isms/libs/AggregationIsmMetadata.sol"; import {AggregationIsmMetadata} from "../../contracts/isms/libs/AggregationIsmMetadata.sol";
import {TestIsm, ThresholdTestUtils} from "./IsmTestUtils.sol"; import {TestIsm, ThresholdTestUtils} from "./IsmTestUtils.sol";
@ -16,10 +18,10 @@ contract AggregationIsmTest is Test {
string constant fixtureKey = "fixture"; string constant fixtureKey = "fixture";
StaticAggregationIsmFactory factory; IThresholdAddressFactory factory;
IAggregationIsm ism; IAggregationIsm ism;
function setUp() public { function setUp() public virtual {
factory = new StaticAggregationIsmFactory(); factory = new StaticAggregationIsmFactory();
} }
@ -46,6 +48,8 @@ contract AggregationIsmTest is Test {
uint8 n, uint8 n,
bytes32 seed bytes32 seed
) internal returns (address[] memory) { ) internal returns (address[] memory) {
vm.assume(m > 0 && m <= n && n < 10);
bytes32 randomness = seed; bytes32 randomness = seed;
address[] memory isms = new address[](n); address[] memory isms = new address[](n);
for (uint256 i = 0; i < n; i++) { for (uint256 i = 0; i < n; i++) {
@ -91,7 +95,6 @@ contract AggregationIsmTest is Test {
} }
function testVerify(uint8 m, uint8 n, bytes32 seed) public { function testVerify(uint8 m, uint8 n, bytes32 seed) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed); deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed); bytes memory metadata = getMetadata(m, seed);
@ -104,7 +107,7 @@ contract AggregationIsmTest is Test {
uint8 i, uint8 i,
bytes32 seed bytes32 seed
) public { ) public {
vm.assume(0 < m && m <= n && n < 10 && i < n); vm.assume(i < n);
deployIsms(m, n, seed); deployIsms(m, n, seed);
(address[] memory modules, ) = ism.modulesAndThreshold(""); (address[] memory modules, ) = ism.modulesAndThreshold("");
bytes memory noMetadata; bytes memory noMetadata;
@ -115,7 +118,6 @@ contract AggregationIsmTest is Test {
} }
function testVerifyMissingMetadata(uint8 m, uint8 n, bytes32 seed) public { function testVerifyMissingMetadata(uint8 m, uint8 n, bytes32 seed) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed); deployIsms(m, n, seed);
// Populate metadata for one fewer ISMs than needed. // Populate metadata for one fewer ISMs than needed.
@ -129,7 +131,6 @@ contract AggregationIsmTest is Test {
uint8 n, uint8 n,
bytes32 seed bytes32 seed
) public { ) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed); deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed); bytes memory metadata = getMetadata(m, seed);
@ -141,11 +142,26 @@ contract AggregationIsmTest is Test {
} }
function testModulesAndThreshold(uint8 m, uint8 n, bytes32 seed) public { function testModulesAndThreshold(uint8 m, uint8 n, bytes32 seed) public {
vm.assume(0 < m && m <= n && n < 10);
address[] memory expectedIsms = deployIsms(m, n, seed); address[] memory expectedIsms = deployIsms(m, n, seed);
(address[] memory actualIsms, uint8 actualThreshold) = ism (address[] memory actualIsms, uint8 actualThreshold) = ism
.modulesAndThreshold(""); .modulesAndThreshold("");
assertEq(abi.encode(actualIsms), abi.encode(expectedIsms)); assertEq(abi.encode(actualIsms), abi.encode(expectedIsms));
assertEq(actualThreshold, m); assertEq(actualThreshold, m);
} }
function testZeroThreshold() public {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 0);
}
function testThresholdExceedsLength() public {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 2);
}
}
contract StorageAggregationIsmTest is AggregationIsmTest {
function setUp() public override {
factory = new StorageAggregationIsmFactory();
}
} }

@ -11,13 +11,16 @@ import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {StaticMerkleRootMultisigIsmFactory, StaticMessageIdMultisigIsmFactory} from "../../contracts/isms/multisig/StaticMultisigIsm.sol"; import {StaticMerkleRootMultisigIsmFactory, StaticMessageIdMultisigIsmFactory} from "../../contracts/isms/multisig/StaticMultisigIsm.sol";
import {MerkleRootMultisigIsmMetadata} from "../../contracts/isms/libs/MerkleRootMultisigIsmMetadata.sol"; import {MerkleRootMultisigIsmMetadata} from "../../contracts/isms/libs/MerkleRootMultisigIsmMetadata.sol";
import {CheckpointLib} from "../../contracts/libs/CheckpointLib.sol"; import {CheckpointLib} from "../../contracts/libs/CheckpointLib.sol";
import {StaticThresholdAddressSetFactory} from "../../contracts/libs/StaticAddressSetFactory.sol"; import {IThresholdAddressFactory} from "../../contracts/interfaces/IThresholdAddressFactory.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {MerkleTreeHook} from "../../contracts/hooks/MerkleTreeHook.sol"; import {MerkleTreeHook} from "../../contracts/hooks/MerkleTreeHook.sol";
import {TestMerkleTreeHook} from "../../contracts/test/TestMerkleTreeHook.sol"; import {TestMerkleTreeHook} from "../../contracts/test/TestMerkleTreeHook.sol";
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol";
import {Message} from "../../contracts/libs/Message.sol"; import {Message} from "../../contracts/libs/Message.sol";
import {ThresholdTestUtils} from "./IsmTestUtils.sol"; import {ThresholdTestUtils} from "./IsmTestUtils.sol";
import {StorageMessageIdMultisigIsm, StorageMerkleRootMultisigIsm, StorageMessageIdMultisigIsmFactory, StorageMerkleRootMultisigIsmFactory, AbstractStorageMultisigIsm} from "../../contracts/isms/multisig/StorageMultisigIsm.sol";
uint8 constant MAX_VALIDATORS = 20;
/// @notice since we removed merkle tree from the mailbox, we need to include the MerkleTreeHook in the test /// @notice since we removed merkle tree from the mailbox, we need to include the MerkleTreeHook in the test
abstract contract AbstractMultisigIsmTest is Test { abstract contract AbstractMultisigIsmTest is Test {
@ -32,7 +35,7 @@ abstract contract AbstractMultisigIsmTest is Test {
string constant prefixKey = "prefix"; string constant prefixKey = "prefix";
uint32 constant ORIGIN = 11; uint32 constant ORIGIN = 11;
StaticThresholdAddressSetFactory factory; IThresholdAddressFactory factory;
IInterchainSecurityModule ism; IInterchainSecurityModule ism;
TestMerkleTreeHook internal merkleTreeHook; TestMerkleTreeHook internal merkleTreeHook;
TestPostDispatchHook internal noopHook; TestPostDispatchHook internal noopHook;
@ -163,7 +166,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 n, uint8 n,
bytes32 seed bytes32 seed
) public { ) public {
vm.assume(0 < m && m <= n && n < 10); vm.assume(0 < m && m <= n && n < MAX_VALIDATORS);
bytes memory message = getMessage(destination, recipient, body); bytes memory message = getMessage(destination, recipient, body);
bytes memory metadata = getMetadata(m, n, seed, message); bytes memory metadata = getMetadata(m, n, seed, message);
assertTrue(ism.verify(metadata, message)); assertTrue(ism.verify(metadata, message));
@ -177,7 +180,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 n, uint8 n,
bytes32 seed bytes32 seed
) public { ) public {
vm.assume(0 < m && m <= n && n < 10); vm.assume(0 < m && m <= n && n < MAX_VALIDATORS);
bytes memory message = getMessage(destination, recipient, body); bytes memory message = getMessage(destination, recipient, body);
bytes memory metadata = getMetadata(m, n, seed, message); bytes memory metadata = getMetadata(m, n, seed, message);
@ -212,6 +215,16 @@ abstract contract AbstractMultisigIsmTest is Test {
vm.expectRevert("!threshold"); vm.expectRevert("!threshold");
ism.verify(duplicateMetadata, message); ism.verify(duplicateMetadata, message);
} }
function testZeroThreshold() public virtual {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 0);
}
function testThresholdExceedsLength() public virtual {
vm.expectRevert("Invalid threshold");
factory.deploy(new address[](1), 2);
}
} }
contract MerkleRootMultisigIsmTest is AbstractMultisigIsmTest { contract MerkleRootMultisigIsmTest is AbstractMultisigIsmTest {
@ -308,3 +321,93 @@ contract MessageIdMultisigIsmTest is AbstractMultisigIsmTest {
return abi.encodePacked(merkleTreeAddress, root, index); return abi.encodePacked(merkleTreeAddress, root, index);
} }
} }
abstract contract StorageMultisigIsmTest is AbstractMultisigIsmTest {
event ValidatorsAndThresholdSet(address[] validators, uint8 threshold);
event Initialized(uint8 version);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
function test_initialize(
bytes32 seed,
address[] memory validators,
uint8 threshold
) public {
vm.assume(
0 < threshold &&
threshold <= validators.length &&
validators.length <= MAX_VALIDATORS
);
addValidators(threshold, uint8(validators.length), seed);
vm.expectRevert("Initializable: contract is already initialized");
AbstractStorageMultisigIsm(address(ism)).initialize(
address(this),
validators,
threshold
);
}
function test_setValidatorsAndThreshold(
bytes32 seed,
address[] memory validators,
uint8 threshold
) public {
vm.assume(
0 < threshold &&
threshold <= validators.length &&
validators.length <= MAX_VALIDATORS
);
addValidators(threshold, uint8(validators.length), seed);
AbstractStorageMultisigIsm storageIsm = AbstractStorageMultisigIsm(
address(ism)
);
address owner = storageIsm.owner();
address antiOwner = address(~bytes20(owner));
vm.expectRevert("Ownable: caller is not the owner");
vm.prank(antiOwner);
storageIsm.setValidatorsAndThreshold(validators, threshold);
vm.expectRevert("Invalid threshold");
vm.prank(owner);
storageIsm.setValidatorsAndThreshold(
validators,
uint8(validators.length + 1)
);
vm.prank(owner);
vm.expectEmit(true, true, false, false);
emit ValidatorsAndThresholdSet(validators, threshold);
storageIsm.setValidatorsAndThreshold(validators, threshold);
(address[] memory _validators, uint8 _threshold) = storageIsm
.validatorsAndThreshold("0x");
assertEq(_threshold, threshold);
assertEq(_validators, validators);
}
}
contract StorageMessageIdMultisigIsmTest is
StorageMultisigIsmTest,
MessageIdMultisigIsmTest
{
function setUp() public override {
super.setUp();
factory = new StorageMessageIdMultisigIsmFactory();
}
}
contract StorageMerkleRootMultisigIsmTest is
StorageMultisigIsmTest,
MerkleRootMultisigIsmTest
{
function setUp() public override {
super.setUp();
factory = new StorageMerkleRootMultisigIsmFactory();
}
}

@ -243,6 +243,14 @@ contract StaticMerkleRootWeightedMultisigIsmTest is
seed seed
); );
} }
function testThresholdExceedsLength() public override {
// no-op
}
function testZeroThreshold() public override {
// no-op
}
} }
contract StaticMessageIdWeightedMultisigIsmTest is contract StaticMessageIdWeightedMultisigIsmTest is
@ -298,4 +306,12 @@ contract StaticMessageIdWeightedMultisigIsmTest is
seed seed
); );
} }
function testThresholdExceedsLength() public override {
// no-op
}
function testZeroThreshold() public override {
// no-op
}
} }

@ -1,5 +1,9 @@
# @hyperlane-xyz/ccip-server # @hyperlane-xyz/ccip-server
## 6.0.0
## 5.7.0
## 5.6.2 ## 5.6.2
## 5.6.1 ## 5.6.1

@ -1,6 +1,6 @@
{ {
"name": "@hyperlane-xyz/ccip-server", "name": "@hyperlane-xyz/ccip-server",
"version": "5.6.2", "version": "6.0.0",
"description": "CCIP server", "description": "CCIP server",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"typedocMain": "src/index.ts", "typedocMain": "src/index.ts",

@ -1,5 +1,46 @@
# @hyperlane-xyz/cli # @hyperlane-xyz/cli
## 6.0.0
### Major Changes
- e3b97c455: Detangle assumption that chainId == domainId for EVM chains. Domain IDs and Chain Names are still unique, but chainId is no longer guaranteed to be a unique identifier. Domain ID is no longer an optional field and is now required for all chain metadata.
### Patch Changes
- Updated dependencies [7b3b07900]
- Updated dependencies [30d92c319]
- Updated dependencies [e3b97c455]
- @hyperlane-xyz/sdk@6.0.0
- @hyperlane-xyz/utils@6.0.0
## 5.7.0
### Minor Changes
- db0e73502: re-enable space key for multiselect cli prompt
- 7e9e248be: Add feat to allow updates to destination gas using warp apply
- 4c0605dca: Add optional proxy admin reuse in warp route deployments and admin proxy ownership transfer in warp apply
- db5875cc2: Add `hyperlane warp verify` to allow post-deployment verification.
- 956ff752a: Enable configuration of IGP hooks in the CLI
### Patch Changes
- Updated dependencies [5dabdf388]
- Updated dependencies [469f2f340]
- Updated dependencies [e104cf6aa]
- Updated dependencies [d9505ab58]
- Updated dependencies [04108155d]
- Updated dependencies [7e9e248be]
- Updated dependencies [4c0605dca]
- Updated dependencies [db9196837]
- Updated dependencies [db5875cc2]
- Updated dependencies [56328e6e1]
- Updated dependencies [956ff752a]
- Updated dependencies [39a9b2038]
- @hyperlane-xyz/sdk@5.7.0
- @hyperlane-xyz/utils@5.7.0
## 5.6.2 ## 5.6.2
### Patch Changes ### Patch Changes

@ -1,3 +1,4 @@
submitter: submitter:
chain: anvil2
type: impersonatedAccount type: impersonatedAccount
userAddress: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb' userAddress: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb'

@ -1,6 +1,8 @@
anvil2: anvil2:
submitter: submitter:
chain: anvil2
type: jsonRpc type: jsonRpc
anvil3: anvil3:
submitter: submitter:
chain: anvil3
type: jsonRpc type: jsonRpc

@ -1,2 +1,3 @@
submitter: submitter:
chain: anvil2
type: jsonRpc type: jsonRpc

@ -1,13 +1,13 @@
{ {
"name": "@hyperlane-xyz/cli", "name": "@hyperlane-xyz/cli",
"version": "5.6.2", "version": "6.0.0",
"description": "A command-line utility for common Hyperlane operations", "description": "A command-line utility for common Hyperlane operations",
"dependencies": { "dependencies": {
"@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-kms": "^3.577.0",
"@aws-sdk/client-s3": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0",
"@hyperlane-xyz/registry": "4.7.0", "@hyperlane-xyz/registry": "4.7.0",
"@hyperlane-xyz/sdk": "5.6.2", "@hyperlane-xyz/sdk": "6.0.0",
"@hyperlane-xyz/utils": "5.6.2", "@hyperlane-xyz/utils": "6.0.0",
"@inquirer/core": "9.0.10", "@inquirer/core": "9.0.10",
"@inquirer/figures": "1.0.5", "@inquirer/figures": "1.0.5",
"@inquirer/prompts": "^3.0.0", "@inquirer/prompts": "^3.0.0",

@ -24,6 +24,8 @@ import {
writeYamlOrJson, writeYamlOrJson,
} from '../utils/files.js'; } from '../utils/files.js';
import { getWarpCoreConfigOrExit } from '../utils/input.js'; import { getWarpCoreConfigOrExit } from '../utils/input.js';
import { selectRegistryWarpRoute } from '../utils/tokens.js';
import { runVerifyWarpRoute } from '../verify/warp.js';
import { import {
DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH, DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH,
@ -54,6 +56,7 @@ export const warpCommand: CommandModule = {
.command(init) .command(init)
.command(read) .command(read)
.command(send) .command(send)
.command(verify)
.version(false) .version(false)
.demandCommand(), .demandCommand(),
@ -334,3 +337,25 @@ export const check: CommandModuleWithContext<{
process.exit(0); process.exit(0);
}, },
}; };
export const verify: CommandModuleWithWriteContext<{
symbol: string;
}> = {
command: 'verify',
describe: 'Verify deployed contracts on explorers',
builder: {
symbol: {
...symbolCommandOption,
demandOption: false,
},
},
handler: async ({ context, symbol }) => {
logCommandHeader('Hyperlane Warp Verify');
const warpCoreConfig = await selectRegistryWarpRoute(
context.registry,
symbol,
);
return runVerifyWarpRoute({ context, warpCoreConfig });
},
};

@ -1,6 +1,11 @@
import { stringify as yamlStringify } from 'yaml'; import { stringify as yamlStringify } from 'yaml';
import { CoreConfigSchema, HookConfig, IsmConfig } from '@hyperlane-xyz/sdk'; import {
CoreConfigSchema,
HookConfig,
IsmConfig,
OwnableConfig,
} from '@hyperlane-xyz/sdk';
import { CommandContext } from '../context/types.js'; import { CommandContext } from '../context/types.js';
import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js';
@ -18,6 +23,9 @@ import {
} from './hooks.js'; } from './hooks.js';
import { createAdvancedIsmConfig, createTrustedRelayerConfig } from './ism.js'; import { createAdvancedIsmConfig, createTrustedRelayerConfig } from './ism.js';
const ENTER_DESIRED_VALUE_MSG = 'Enter the desired';
const SIGNER_PROMPT_LABEL = 'signer';
export async function createCoreDeployConfig({ export async function createCoreDeployConfig({
context, context,
configFilePath, configFilePath,
@ -31,9 +39,9 @@ export async function createCoreDeployConfig({
const owner = await detectAndConfirmOrPrompt( const owner = await detectAndConfirmOrPrompt(
async () => context.signer?.getAddress(), async () => context.signer?.getAddress(),
'Enter the desired', ENTER_DESIRED_VALUE_MSG,
'owner address', 'owner address',
'signer', SIGNER_PROMPT_LABEL,
); );
const defaultIsm: IsmConfig = advanced const defaultIsm: IsmConfig = advanced
@ -41,6 +49,7 @@ export async function createCoreDeployConfig({
: await createTrustedRelayerConfig(context, advanced); : await createTrustedRelayerConfig(context, advanced);
let defaultHook: HookConfig, requiredHook: HookConfig; let defaultHook: HookConfig, requiredHook: HookConfig;
let proxyAdmin: OwnableConfig;
if (advanced) { if (advanced) {
defaultHook = await createHookConfig({ defaultHook = await createHookConfig({
context, context,
@ -52,9 +61,20 @@ export async function createCoreDeployConfig({
selectMessage: 'Select required hook type', selectMessage: 'Select required hook type',
advanced, advanced,
}); });
proxyAdmin = {
owner: await detectAndConfirmOrPrompt(
async () => context.signer?.getAddress(),
ENTER_DESIRED_VALUE_MSG,
'ProxyAdmin owner address',
SIGNER_PROMPT_LABEL,
),
};
} else { } else {
defaultHook = await createMerkleTreeConfig(); defaultHook = await createMerkleTreeConfig();
requiredHook = await createProtocolFeeConfig(context, advanced); requiredHook = await createProtocolFeeConfig(context, advanced);
proxyAdmin = {
owner,
};
} }
try { try {
@ -63,6 +83,7 @@ export async function createCoreDeployConfig({
defaultIsm, defaultIsm,
defaultHook, defaultHook,
requiredHook, requiredHook,
proxyAdmin,
}); });
logBlue(`Core config is valid, writing to file ${configFilePath}:\n`); logBlue(`Core config is valid, writing to file ${configFilePath}:\n`);
log(indentYamlOrJson(yamlStringify(coreConfig, null, 2), 4)); log(indentYamlOrJson(yamlStringify(coreConfig, null, 2), 4));

@ -8,12 +8,12 @@ import {
ChainMap, ChainMap,
ChainMetadata, ChainMetadata,
ChainName, ChainName,
CoinGeckoTokenPriceGetter,
HookConfig, HookConfig,
HookConfigSchema, HookConfigSchema,
HookType, HookType,
IgpHookConfig, IgpHookConfig,
MultiProtocolProvider, MultiProtocolProvider,
getCoingeckoTokenPrices,
getGasPrice, getGasPrice,
getLocalStorageGasOracleConfig, getLocalStorageGasOracleConfig,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
@ -305,9 +305,17 @@ async function getIgpTokenPrices(
) { ) {
const isTestnet = const isTestnet =
context.chainMetadata[Object.keys(filteredMetadata)[0]].isTestnet; context.chainMetadata[Object.keys(filteredMetadata)[0]].isTestnet;
const fetchedPrices = isTestnet
? objMap(filteredMetadata, () => '10') let fetchedPrices: ChainMap<string>;
: await getCoingeckoTokenPrices(filteredMetadata); if (isTestnet) {
fetchedPrices = objMap(filteredMetadata, () => '10');
} else {
const tokenPriceGetter = new CoinGeckoTokenPriceGetter({
chainMetadata: filteredMetadata,
});
const results = await tokenPriceGetter.getAllTokenPrices();
fetchedPrices = objMap(results, (v) => v.toString());
}
logBlue( logBlue(
isTestnet isTestnet

@ -8,6 +8,7 @@ import {
IsmConfigSchema, IsmConfigSchema,
IsmType, IsmType,
MultisigIsmConfig, MultisigIsmConfig,
MultisigIsmConfigSchema,
TrustedRelayerIsmConfig, TrustedRelayerIsmConfig,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
@ -72,7 +73,11 @@ const ISM_TYPE_DESCRIPTIONS: Record<string, string> = {
"You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM", "You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM",
[IsmType.MERKLE_ROOT_MULTISIG]: [IsmType.MERKLE_ROOT_MULTISIG]:
'Validators need to sign the root of the merkle tree of all messages from origin chain', 'Validators need to sign the root of the merkle tree of all messages from origin chain',
[IsmType.STORAGE_MERKLE_ROOT_MULTISIG]:
'Mutable validators in storage need to sign the root of the merkle tree of all messages from origin chain',
[IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId',
[IsmType.STORAGE_MESSAGE_ID_MULTISIG]:
'Mutable validators in storage need to sign just this messageId',
[IsmType.ROUTING]: [IsmType.ROUTING]:
'Each origin chain can be verified by the specified ISM type via RoutingISM', 'Each origin chain can be verified by the specified ISM type via RoutingISM',
[IsmType.TEST_ISM]: [IsmType.TEST_ISM]:
@ -106,9 +111,10 @@ export async function createAdvancedIsmConfig(
case IsmType.FALLBACK_ROUTING: case IsmType.FALLBACK_ROUTING:
return createFallbackRoutingConfig(context); return createFallbackRoutingConfig(context);
case IsmType.MERKLE_ROOT_MULTISIG: case IsmType.MERKLE_ROOT_MULTISIG:
return createMerkleRootMultisigConfig(context);
case IsmType.MESSAGE_ID_MULTISIG: case IsmType.MESSAGE_ID_MULTISIG:
return createMessageIdMultisigConfig(context); case IsmType.STORAGE_MERKLE_ROOT_MULTISIG:
case IsmType.STORAGE_MESSAGE_ID_MULTISIG:
return createMultisigConfig(moduleType);
case IsmType.ROUTING: case IsmType.ROUTING:
return createRoutingConfig(context); return createRoutingConfig(context);
case IsmType.TEST_ISM: case IsmType.TEST_ISM:
@ -116,58 +122,40 @@ export async function createAdvancedIsmConfig(
case IsmType.TRUSTED_RELAYER: case IsmType.TRUSTED_RELAYER:
return createTrustedRelayerConfig(context, true); return createTrustedRelayerConfig(context, true);
default: default:
throw new Error(`Invalid ISM type: ${moduleType}.`); throw new Error(`Unsupported ISM type: ${moduleType}.`);
} }
} }
export const createMerkleRootMultisigConfig = callWithConfigCreationLogs( export const createMultisigConfig = async (
async (): Promise<MultisigIsmConfig> => { ismType: MultisigIsmConfig['type'],
): Promise<MultisigIsmConfig> => {
const validatorsInput = await input({ const validatorsInput = await input({
message: message:
'Enter validator addresses (comma separated list) for merkle root multisig ISM:', 'Enter validator addresses (comma separated list) for multisig ISM:',
}); });
const validators = validatorsInput.split(',').map((v) => v.trim()); const validators = validatorsInput.split(',').map((v) => v.trim());
const thresholdInput = await input({ const threshold = parseInt(
message: await input({
'Enter threshold of validators (number) for merkle root multisig ISM:', message: 'Enter threshold of validators (number) for multisig ISM:',
}),
10,
);
const result = MultisigIsmConfigSchema.safeParse({
type: ismType,
validators,
threshold,
}); });
const threshold = parseInt(thresholdInput, 10); if (!result.success) {
if (threshold > validators.length) {
errorRed( errorRed(
`Merkle root multisig signer threshold (${threshold}) cannot be greater than total number of validators (${validators.length}).`, result.error.issues
.map((input, index) => `input[${index}]: ${input.message}`)
.join('\n'),
); );
throw new Error('Invalid protocol fee.'); return createMultisigConfig(ismType);
} }
return {
type: IsmType.MERKLE_ROOT_MULTISIG,
threshold,
validators,
};
},
IsmType.MERKLE_ROOT_MULTISIG,
);
export const createMessageIdMultisigConfig = callWithConfigCreationLogs( return result.data;
async (): Promise<MultisigIsmConfig> => { };
const thresholdInput = await input({
message:
'Enter threshold of validators (number) for message ID multisig ISM',
});
const threshold = parseInt(thresholdInput, 10);
const validatorsInput = await input({
message:
'Enter validator addresses (comma separated list) for message ID multisig ISM',
});
const validators = validatorsInput.split(',').map((v) => v.trim());
return {
type: IsmType.MESSAGE_ID_MULTISIG,
threshold,
validators,
};
},
IsmType.MESSAGE_ID_MULTISIG,
);
export const createTrustedRelayerConfig = callWithConfigCreationLogs( export const createTrustedRelayerConfig = callWithConfigCreationLogs(
async ( async (

@ -3,8 +3,8 @@ import { stringify as yamlStringify } from 'yaml';
import { import {
AnnotatedEV5Transaction, AnnotatedEV5Transaction,
SubmissionStrategy, SubmissionStrategy,
getChainIdFromTxs,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { MultiProvider } from '@hyperlane-xyz/sdk';
import { assert, errorToString } from '@hyperlane-xyz/utils'; import { assert, errorToString } from '@hyperlane-xyz/utils';
import { WriteCommandContext } from '../context/types.js'; import { WriteCommandContext } from '../context/types.js';
@ -34,9 +34,9 @@ export async function runSubmit({
'Submission strategy required to submit transactions.\nPlease create a submission strategy. See examples in cli/examples/submit/strategy/*.', 'Submission strategy required to submit transactions.\nPlease create a submission strategy. See examples in cli/examples/submit/strategy/*.',
); );
const transactions = getTransactions(transactionsFilepath); const transactions = getTransactions(transactionsFilepath);
const chain = getChainFromTxs(multiProvider, transactions); const chainId = getChainIdFromTxs(transactions);
const protocol = chainMetadata[chain].protocol; const protocol = chainMetadata[chainId].protocol;
const submitterBuilder = await getSubmitterBuilder<typeof protocol>({ const submitterBuilder = await getSubmitterBuilder<typeof protocol>({
submissionStrategy, submissionStrategy,
multiProvider, multiProvider,
@ -60,28 +60,6 @@ export async function runSubmit({
} }
} }
/**
* Retrieves the chain name from transactions[0].
*
* @param multiProvider - The MultiProvider instance to use for chain name lookup.
* @param transactions - The list of populated transactions.
* @returns The name of the chain that the transactions are submitted on.
* @throws If the transactions are not all on the same chain or chain is not found
*/
function getChainFromTxs(
multiProvider: MultiProvider,
transactions: AnnotatedEV5Transaction[],
) {
const firstTransaction = transactions[0];
assert(firstTransaction.chainId, 'Invalid transaction: chainId is required');
const sameChainIds = transactions.every(
(t: AnnotatedEV5Transaction) => t.chainId === firstTransaction.chainId,
);
assert(sameChainIds, 'Transactions must be submitted on the same chains');
return multiProvider.getChainName(firstTransaction.chainId);
}
function getTransactions( function getTransactions(
transactionsFilepath: string, transactionsFilepath: string,
): AnnotatedEV5Transaction[] { ): AnnotatedEV5Transaction[] {

@ -3,6 +3,7 @@ import { stringify as yamlStringify } from 'yaml';
import { import {
ChainMap, ChainMap,
DeployedOwnableConfig,
IsmConfig, IsmConfig,
IsmType, IsmType,
MailboxClientConfig, MailboxClientConfig,
@ -28,7 +29,10 @@ import {
readYamlOrJson, readYamlOrJson,
writeYamlOrJson, writeYamlOrJson,
} from '../utils/files.js'; } from '../utils/files.js';
import { detectAndConfirmOrPrompt } from '../utils/input.js'; import {
detectAndConfirmOrPrompt,
setProxyAdminConfig,
} from '../utils/input.js';
import { createAdvancedIsmConfig } from './ism.js'; import { createAdvancedIsmConfig } from './ism.js';
@ -129,7 +133,9 @@ export async function createWarpRouteDeployConfig({
chainMetadata: context.chainMetadata, chainMetadata: context.chainMetadata,
message: 'Select chains to connect', message: 'Select chains to connect',
requireNumber: 1, requireNumber: 1,
requiresConfirmation: true, // If the user supplied the --yes flag we skip asking selection
// confirmation
requiresConfirmation: !context.skipConfirmation,
}); });
const result: WarpRouteDeployConfig = {}; const result: WarpRouteDeployConfig = {};
@ -147,6 +153,12 @@ export async function createWarpRouteDeployConfig({
message: `Could not retrieve mailbox address from the registry for chain "${chain}". Please enter a valid mailbox address:`, message: `Could not retrieve mailbox address from the registry for chain "${chain}". Please enter a valid mailbox address:`,
})); }));
const proxyAdmin: DeployedOwnableConfig = await setProxyAdminConfig(
context,
chain,
owner,
);
/** /**
* The logic from the cli is as follows: * The logic from the cli is as follows:
* --advanced flag is provided: the user will have to build their own configuration using the available ISM types * --advanced flag is provided: the user will have to build their own configuration using the available ISM types
@ -190,6 +202,7 @@ export async function createWarpRouteDeployConfig({
mailbox, mailbox,
type, type,
owner, owner,
proxyAdmin,
isNft, isNft,
interchainSecurityModule, interchainSecurityModule,
token: await input({ token: await input({
@ -203,6 +216,7 @@ export async function createWarpRouteDeployConfig({
type, type,
owner, owner,
isNft, isNft,
proxyAdmin,
collateralChainName: '', // This will be derived correctly by zod.parse() below collateralChainName: '', // This will be derived correctly by zod.parse() below
interchainSecurityModule, interchainSecurityModule,
}; };
@ -216,6 +230,7 @@ export async function createWarpRouteDeployConfig({
mailbox, mailbox,
type, type,
owner, owner,
proxyAdmin,
isNft, isNft,
interchainSecurityModule, interchainSecurityModule,
token: await input({ token: await input({
@ -230,6 +245,7 @@ export async function createWarpRouteDeployConfig({
mailbox, mailbox,
type, type,
owner, owner,
proxyAdmin,
isNft, isNft,
interchainSecurityModule, interchainSecurityModule,
token: await input({ token: await input({
@ -242,6 +258,7 @@ export async function createWarpRouteDeployConfig({
mailbox, mailbox,
type, type,
owner, owner,
proxyAdmin,
isNft, isNft,
interchainSecurityModule, interchainSecurityModule,
}; };

@ -188,9 +188,18 @@ async function getMultiProvider(registry: IRegistry, signer?: ethers.Signer) {
return multiProvider; return multiProvider;
} }
export async function getOrRequestApiKeys( /**
* Requests and saves Block Explorer API keys for the specified chains, prompting the user if necessary.
*
* @param chains - The list of chain names to request API keys for.
* @param chainMetadata - The chain metadata, used to determine if an API key is already configured.
* @param registry - The registry used to update the chain metadata with the new API key.
* @returns A mapping of chain names to their API keys.
*/
export async function requestAndSaveApiKeys(
chains: ChainName[], chains: ChainName[],
chainMetadata: ChainMap<ChainMetadata>, chainMetadata: ChainMap<ChainMetadata>,
registry: IRegistry,
): Promise<ChainMap<string>> { ): Promise<ChainMap<string>> {
const apiKeys: ChainMap<string> = {}; const apiKeys: ChainMap<string> = {};
@ -218,6 +227,11 @@ export async function getOrRequestApiKeys(
`${chain} api key`, `${chain} api key`,
`${chain} metadata blockExplorers config`, `${chain} metadata blockExplorers config`,
); );
chainMetadata[chain].blockExplorers![0].apiKey = apiKeys[chain];
await registry.updateChain({
chainName: chain,
metadata: chainMetadata[chain],
});
} }
} }

@ -12,7 +12,7 @@ import {
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js';
import { getOrRequestApiKeys } from '../context/context.js'; import { requestAndSaveApiKeys } from '../context/context.js';
import { WriteCommandContext } from '../context/types.js'; import { WriteCommandContext } from '../context/types.js';
import { log, logBlue, logGray, logGreen } from '../logger.js'; import { log, logBlue, logGray, logGreen } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js'; import { runSingleChainSelectionStep } from '../utils/chains.js';
@ -34,6 +34,7 @@ interface DeployParams {
interface ApplyParams extends DeployParams { interface ApplyParams extends DeployParams {
deployedCoreAddresses: DeployedCoreAddresses; deployedCoreAddresses: DeployedCoreAddresses;
} }
/** /**
* Executes the core deploy command. * Executes the core deploy command.
*/ */
@ -64,7 +65,7 @@ export async function runCoreDeploy(params: DeployParams) {
let apiKeys: ChainMap<string> = {}; let apiKeys: ChainMap<string> = {};
if (!skipConfirmation) if (!skipConfirmation)
apiKeys = await getOrRequestApiKeys([chain], chainMetadata); apiKeys = await requestAndSaveApiKeys([chain], chainMetadata, registry);
const deploymentParams: DeployParams = { const deploymentParams: DeployParams = {
context, context,

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

Loading…
Cancel
Save