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. 2096
      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. 88
      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. 22
      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. 88
      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
---
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: |
**/node_modules
.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
# restoring the cache, let's run the install step ourselves.

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

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

@ -40,9 +40,9 @@ jobs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
${{ runner.os }}-yarn-4.5.1-cache-
- name: yarn-install
run: |
@ -70,7 +70,7 @@ jobs:
path: |
**/node_modules
.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
- 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
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 yarn set version 4.0.2
RUN yarn set version 4.5.1
# Copy package.json and friends
COPY package.json yarn.lock .yarnrc.yml ./

@ -17,7 +17,7 @@
"dependencies": {
"@changesets/cli": "^2.26.2"
},
"packageManager": "yarn@4.0.2",
"packageManager": "yarn@4.5.1",
"private": true,
"scripts": {
"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",
"convert_case 0.6.0",
"derive-new",
"derive_builder",
"ed25519-dalek 1.0.1",
"ethers",
"ethers-prometheus",
@ -4630,6 +4629,7 @@ dependencies = [
"hyperlane-sealevel-multisig-ism-message-id",
"hyperlane-sealevel-validator-announce",
"jsonrpc-core",
"lazy_static",
"multisig-ism",
"num-traits",
"reqwest",

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

@ -12,11 +12,12 @@ use hyperlane_base::{
broadcast::BroadcastMpscSender,
db::{HyperlaneRocksDB, DB},
metrics::{AgentMetrics, MetricsUpdater},
settings::ChainConf,
settings::{ChainConf, IndexSettings},
AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics,
HyperlaneAgentCore, SyncOptions,
};
use hyperlane_core::{
rpc_clients::call_and_retry_n_times, ChainCommunicationError, ContractSyncCursor,
HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation,
H512, U256,
};
@ -50,6 +51,9 @@ use crate::{
};
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)]
struct ContextKey {
origin: u32,
@ -354,6 +358,7 @@ impl BaseAgent for Relayer {
}
for origin in &self.origin_chains {
self.chain_metrics.set_critical_error(origin.name(), false);
let maybe_broadcaster = self
.message_syncs
.get(origin)
@ -412,6 +417,34 @@ impl BaseAgent for 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(
&self,
origin: &HyperlaneDomain,
@ -419,10 +452,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> {
let index_settings = self.as_ref().settings.chains[origin.name()].index_settings();
let contract_sync = self.message_syncs.get(origin).unwrap().clone();
let cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
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 {
contract_sync
.clone()
@ -444,10 +483,16 @@ impl Relayer {
.get(origin)
.unwrap()
.clone();
let cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
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 {
contract_sync
.clone()
@ -468,10 +513,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> {
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 cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
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 {
contract_sync
.clone()

@ -490,7 +490,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Domain::TimeUpdated).timestamp().not_null())
.col(ColumnDef::new(Domain::Name).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::IsDeprecated).boolean().not_null())
.to_owned(),

@ -65,7 +65,7 @@ impl ColumnTrait for Column {
Self::TimeUpdated => ColumnType::DateTime.def(),
Self::Name => 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::IsDeprecated => ColumnType::Boolean.def(),
}

@ -18,9 +18,10 @@ use tracing::{error, warn};
use crypto::decompress_public_key;
use hyperlane_core::{
bytes_to_h512, h512_to_bytes, AccountAddressType, BlockInfo, ChainCommunicationError,
ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider,
HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256,
bytes_to_h512, h512_to_bytes, utils::to_atto, AccountAddressType, BlockInfo,
ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain,
HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256,
H512, U256,
};
use crate::grpc::{WasmGrpcProvider, WasmProvider};
@ -33,9 +34,6 @@ use crate::{
mod parse;
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Injective public key type URL for protobuf Any
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.
///
/// 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();
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);
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
let supported = self.report_unsupported_denominations(tx, hash);
if supported.is_err() {
return U256::max_value();
return Ok(U256::max_value());
}
let gas_limit = U256::from(tx.auth_info.fee.gas_limit);
@ -349,13 +346,13 @@ impl CosmosProvider {
.amount
.iter()
.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 {
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 (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 {
hash: hash.into(),

@ -3,10 +3,10 @@ use std::str::FromStr;
use url::Url;
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::{ConnectionConf, CosmosAddress, CosmosAmount, NativeToken, RawCosmosAmount};
use crate::{ConnectionConf, CosmosAddress, CosmosAmount, RawCosmosAmount};
#[ignore]
#[tokio::test]

@ -3,7 +3,9 @@ use std::str::FromStr;
use derive_new::new;
use url::Url;
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber};
use hyperlane_core::{
config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber, NativeToken,
};
/// Cosmos connection configuration
#[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.
#[derive(thiserror::Error, Debug)]
pub enum ConnectionConfError {

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

@ -1,7 +1,7 @@
use hyperlane_core::{ChainCommunicationError, H512};
use solana_client::client_error::ClientError;
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
/// implementation.
@ -27,12 +27,27 @@ pub enum HyperlaneSealevelError {
/// Unsupported transaction encoding
#[error("{0:?}")]
UnsupportedTransactionEncoding(EncodedTransaction),
/// Unsupported message encoding
#[error("{0:?}")]
UnsupportedMessageEncoding(UiMessage),
/// Unsigned transaction
#[error("{0}")]
UnsignedTransaction(H512),
/// Incorrect transaction
#[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")]
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 {

@ -33,6 +33,7 @@ use solana_client::{
use solana_sdk::{
account::Account,
bs58,
clock::Slot,
commitment_config::CommitmentConfig,
compute_budget::ComputeBudgetInstruction,
hash::Hash,
@ -61,7 +62,9 @@ use hyperlane_core::{
use crate::account::{search_accounts_by_discriminator, search_and_validate_account};
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::{ConnectionConf, SealevelProvider, SealevelRpcClient};
@ -694,44 +697,15 @@ impl SealevelMailboxIndexer {
let hyperlane_message =
HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?;
let block = self
.mailbox
.provider
.rpc()
.get_block(dispatched_message_account.slot)
let log_meta = self
.dispatch_message_log_meta(
U256::from(nonce),
&valid_message_storage_pda_pubkey,
&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,
transactions,
);
// 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((
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),
},
))
Ok((hyperlane_message.into(), log_meta))
}
fn dispatched_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
@ -748,6 +722,28 @@ impl SealevelMailboxIndexer {
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(
&self,
nonce: u32,
@ -782,19 +778,15 @@ impl SealevelMailboxIndexer {
.into_inner();
let message_id = delivered_message_account.message_id;
Ok((
message_id.into(),
LogMeta {
address: self.mailbox.program_id.to_bytes().into(),
block_number: delivered_message_account.slot,
// TODO: get these when building out scraper support.
// It's inconvenient to get these :|
block_hash: H256::zero(),
transaction_id: H512::zero(),
transaction_index: 0,
log_index: U256::zero(),
},
))
let log_meta = self
.delivered_message_log_meta(
U256::from(nonce),
&valid_message_storage_pda_pubkey,
&delivered_message_account.slot,
)
.await?;
Ok((message_id.into(), log_meta))
}
fn delivered_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
@ -808,6 +800,88 @@ impl SealevelMailboxIndexer {
})?;
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]

@ -1,23 +1,45 @@
use std::collections::HashSet;
use std::sync::Arc;
use async_trait::async_trait;
use lazy_static::lazy_static;
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::{
BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain,
HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256,
utils::to_atto, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain,
HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, NativeToken, TxnInfo,
TxnReceiptInfo, H256, H512, U256,
};
use crate::error::HyperlaneSealevelError;
use crate::utils::{decode_h256, decode_h512, decode_pubkey};
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.
#[derive(Debug)]
pub struct SealevelProvider {
domain: HyperlaneDomain,
rpc_client: Arc<SealevelRpcClient>,
native_token: NativeToken,
}
impl SealevelProvider {
@ -25,14 +47,120 @@ impl SealevelProvider {
pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self {
// Set the `processed` commitment at rpc level
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
pub fn rpc(&self) -> &SealevelRpcClient {
&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 {
@ -44,6 +172,7 @@ impl HyperlaneChain for SealevelProvider {
Box::new(SealevelProvider {
domain: self.domain.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.
async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult<TxnInfo> {
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,
t => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedTransactionEncoding(t),
HyperlaneSealevelError::UnsupportedTransactionEncoding(t.clone()),
))?,
};
let received_signature = ui_transaction
.signatures
.first()
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?;
let received_hash = decode_h512(received_signature)?;
Self::validate_transaction(hash, txn)?;
let sender = Self::sender(hash, txn)?;
let recipient = Self::recipient(hash, txn)?;
let meta = Self::meta(txn_with_meta)?;
let gas_used = Self::gas(meta)?;
let fee = self.fee(meta)?;
if &received_hash != hash {
Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::IncorrectTransaction(
Box::new(*hash),
Box::new(received_hash),
),
))?;
if fee < gas_used {
warn!(tx_hash = ?hash, ?fee, ?gas_used, "calculated fee is less than gas used. it will result in zero gas price");
}
let gas_price = Some(fee / gas_used);
let receipt = TxnReceiptInfo {
gas_used: Default::default(),
cumulative_gas_used: Default::default(),
effective_gas_price: None,
gas_used,
cumulative_gas_used: gas_used,
effective_gas_price: gas_price,
};
Ok(TxnInfo {
hash: *hash,
gas_limit: Default::default(),
gas_limit: gas_used,
max_priority_fee_per_gas: None,
max_fee_per_gas: None,
gas_price: None,
gas_price,
nonce: 0,
sender: Default::default(),
recipient: None,
sender,
recipient: Some(recipient),
receipt: Some(receipt),
raw_input_data: None,
})

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

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

@ -13,27 +13,35 @@ use hyperlane_core::H512;
use crate::utils::{decode_h512, from_base58};
/// This function searches for a transaction which dispatches Hyperlane message and returns
/// list of hashes of such transactions.
/// This function searches for a transaction which specified instruction on Hyperlane message and
/// returns list of hashes of such transactions.
///
/// 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
/// of such transactions.
/// of such transactions and their relative index in the block.
///
/// The transaction will be searched with the following criteria:
/// 1. Transaction contains Mailbox program id in the list of accounts.
/// 2. Transaction contains dispatched message PDA in the list of accounts.
/// 3. Transaction is performing message dispatch (OutboxDispatch).
/// 2. Transaction contains dispatched/delivered message PDA in the list of accounts.
/// 3. Transaction is performing the specified message instruction.
///
/// * `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
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,
message_storage_pda_pubkey: &Pubkey,
transactions: Vec<EncodedTransactionWithStatusMeta>,
) -> Vec<(usize, H512)> {
is_specified_message_instruction: &F,
) -> Vec<(usize, H512)>
where
F: Fn(Instruction) -> bool,
{
transactions
.into_iter()
.enumerate()
@ -43,38 +51,43 @@ pub fn search_dispatched_message_transactions(
.map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions))
})
.filter_map(|(index, hash, account_keys, instructions)| {
filter_not_relevant(
filter_by_relevancy(
mailbox_program_id,
message_storage_pda_pubkey,
hash,
account_keys,
instructions,
is_specified_message_instruction,
)
.map(|hash| (index, hash))
})
.collect::<Vec<(usize, H512)>>()
}
fn filter_not_relevant(
fn filter_by_relevancy<F>(
mailbox_program_id: &Pubkey,
message_storage_pda_pubkey: &Pubkey,
hash: H512,
account_keys: Vec<String>,
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 mailbox_program_id_str = mailbox_program_id.to_string();
let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) {
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 dispatch_message_pda_account_index =
let message_storage_pda_account_index =
match account_index_map.get(&message_storage_pda_pubkey_str) {
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
@ -83,35 +96,43 @@ fn filter_not_relevant(
let mailbox_program = match mailbox_program_maybe {
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
.accounts
.contains(&dispatch_message_pda_account_index)
.contains(&message_storage_pda_account_index)
{
return None;
}
let instruction_data = match from_base58(&mailbox_program.data) {
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) {
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 !matches!(instruction, Instruction::OutboxDispatch(_)) {
// If the call into Mailbox program is not OutboxDispatch/InboxProcess, transaction is not message dispatch/delivery.
if is_specified_message_instruction(instruction) {
return None;
}
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(
tx: UiTransaction,
meta: UiTransactionStatusMeta,

@ -1,29 +1,57 @@
use solana_sdk::pubkey::Pubkey;
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;
#[test]
pub fn test_search_dispatched_message_transaction() {
// given
let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap();
let dispatched_message_pda_account =
decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap();
let transaction = serde_json::from_str::<EncodedTransactionWithStatusMeta>(JSON).unwrap();
let transactions = vec![transaction];
let (mailbox_program_id, transactions) = transactions(DISPATCH_TXN_JSON);
// when
let transaction_hashes = search_dispatched_message_transactions(
let transaction_hashes = search_message_transactions(
&mailbox_program_id,
&dispatched_message_pda_account,
transactions,
&is_message_dispatch_instruction,
);
// then
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,
"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",
"interchainAccountRouter": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b",
"interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297",
"interchainSecurityModule": "0xC8513429105955cf01669bfD1ac5396Faf0748a5",
"interchainSecurityModule": "0xDf1d3c37FfA6134767911B8876305afc187dA207",
"isTestnet": true,
"mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59",
"merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa",
@ -94,7 +94,7 @@
"from": 49690504
},
"interchainGasPaymaster": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8",
"interchainSecurityModule": "0xA6D6d30c37434b142618eF97AB15a71871d721C6",
"interchainSecurityModule": "0x69a84432Ba4FaD95FC5850aCD613C6daD286908C",
"isTestnet": true,
"mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8",
"merkleTreeHook": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C",
@ -162,7 +162,7 @@
"from": 13851043
},
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x3E857CB33b76f680F3dB557Ce3BBf2591A98d92d",
"interchainSecurityModule": "0x2945eCB46AE83B1A37b589A9c1219061522A73aD",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -232,7 +232,7 @@
"interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE",
"interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2",
"interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949",
"interchainSecurityModule": "0x2B3bEc44051C3A0c26360Ae513e98A947E9939b7",
"interchainSecurityModule": "0x0c29F3Ce8995b41eB8c2b3E1eC33c3fa10C8cf91",
"isTestnet": true,
"mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D",
"merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f",
@ -301,7 +301,7 @@
"from": 4950
},
"interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450",
"interchainSecurityModule": "0x1e58386A3f012D69568B3E1aB5f8E41169Ba69A9",
"interchainSecurityModule": "0xa25786D36B5a5eDeCAf75142dD056B1Ed1473f44",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
@ -402,7 +402,7 @@
"from": 1606754
},
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x4c8A96b43fD59a4171b7c79d657AD9FedFb2d7B5",
"interchainSecurityModule": "0x1AFC8F84cAE294C3E6d3Ddb031946B93f08272b5",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -468,7 +468,7 @@
"interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311",
"interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b",
"interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E",
"interchainSecurityModule": "0xe412A2d273c02d6f837532946d1B05A6EAB72B04",
"interchainSecurityModule": "0x7b5AD17cdAbdED0C29B161c647DA32dCE51AB13B",
"isTestnet": true,
"mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0",
"merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612",
@ -534,7 +534,7 @@
"from": 1543015
},
"interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9",
"interchainSecurityModule": "0xA4bFAA24c14f0398903E59344F4a36334F47AA50",
"interchainSecurityModule": "0x16738b80D39Fa0652F2D853c3E6235Afb7cA6dDc",
"isTestnet": true,
"mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc",
"merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE",
@ -599,7 +599,7 @@
"from": 15833917
},
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x4c8A96b43fD59a4171b7c79d657AD9FedFb2d7B5",
"interchainSecurityModule": "0x81B81B3b296ecf99d5bAC0DE4e5fF7a3ceECf08b",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
@ -727,7 +727,7 @@
"from": 10634605
},
"interchainGasPaymaster": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"interchainSecurityModule": "0xC5117582A9b64B5b3071B7f11943b21A515A84C6",
"interchainSecurityModule": "0x428a2384F6013A9c561737E9A58288e071470192",
"isTestnet": true,
"mailbox": "0x54148470292C24345fb828B003461a9444414517",
"merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
@ -802,7 +802,7 @@
"interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350",
"interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF",
"interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"interchainSecurityModule": "0x69873c153380149e901b4aD031025Bc195ee1CB8",
"interchainSecurityModule": "0xb9A0fa05Fcce52605f0142c1A5Aca32f223eB961",
"isTestnet": true,
"mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68",
"merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0",
@ -880,7 +880,7 @@
"interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02",
"interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d",
"interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56",
"interchainSecurityModule": "0x43b6a311BF787241BB71b7aE2a29ef639932b9b8",
"interchainSecurityModule": "0x5bC248C8010848067919fA73F4555AeE95Df38a4",
"isTestnet": true,
"mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766",
"merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d",
@ -990,7 +990,7 @@
"from": 3111622
},
"interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450",
"interchainSecurityModule": "0x1e58386A3f012D69568B3E1aB5f8E41169Ba69A9",
"interchainSecurityModule": "0x8a833B1230A75712a767e81556F1Ad612A0F76F3",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
@ -1078,7 +1078,7 @@
"interchainAccountIsm": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainAccountRouter": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainGasPaymaster": "0x04438ef7622f5412f82915F59caD4f704C61eA48",
"interchainSecurityModule": "0xDabB212640f59026a861202ca82CDcD8181aD723",
"interchainSecurityModule": "0x5e39968B0435332dE915C2FF62fD373fBbbb7C75",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"pausableHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
@ -1138,7 +1138,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3",
"interchainSecurityModule": "0x97b1e5FBF33d82fAD9C3Aa0fF901e9B9d63090E3",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1206,7 +1206,7 @@
"interchainAccountIsm": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72",
"interchainAccountRouter": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c",
"interchainGasPaymaster": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"interchainSecurityModule": "0x792F905736703DCb511066f2E0C4b97504CD2728",
"interchainSecurityModule": "0xAfDF88EB9447e412c89304F34813c5564307709b",
"mailbox": "0xB08d78F439e55D02C398519eef61606A5926245F",
"merkleTreeHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
"pausableHook": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c",
@ -1267,7 +1267,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3",
"interchainSecurityModule": "0xe5B27D2198EEacf4FeC7d558CdE3b4fDCe4d37f9",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1395,7 +1395,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3",
"interchainSecurityModule": "0x8714Caec7020c611DA73811E897A879C22d7C396",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1459,7 +1459,7 @@
"interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainSecurityModule": "0x8214144F223b550E5BFf6164F2136F0Ef30bB8b3",
"interchainSecurityModule": "0x0511f73F8D734e0fc9df1E6C0937592a3A371B18",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A",
"pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011",
@ -1673,7 +1673,7 @@
"from": 111
}
},
"arcadiatestnet": {
"arcadiatestnet2": {
"blockExplorers": [
{
"apiUrl": "https://explorer.khalani.network/api",
@ -1688,10 +1688,10 @@
"reorgPeriod": 1
},
"chainId": 1098411886,
"displayName": "Arcadia Testnet",
"displayName": "Arcadia Testnet v2",
"domainId": 1098411886,
"isTestnet": true,
"name": "arcadiatestnet",
"name": "arcadiatestnet2",
"nativeToken": {
"decimals": 18,
"name": "Ether",
@ -1703,33 +1703,33 @@
"http": "https://rpc.khalani.network"
}
],
"aggregationHook": "0x862Ce2De59C13a0406c104d317CfaEf6B672D638",
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"fallbackRoutingHook": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8",
"interchainAccountIsm": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38",
"interchainAccountRouter": "0xBF2C366530C1269d531707154948494D3fF4AcA7",
"interchainGasPaymaster": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"interchainSecurityModule": "0xd89063A7e8Eaee25dA8D3b7eBcbAeF9869702A80",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F",
"pausableHook": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E",
"pausableIsm": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5",
"protocolFee": "0x01812D60958798695391dacF092BAc4a715B1718",
"proxyAdmin": "0x54148470292C24345fb828B003461a9444414517",
"staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticAggregationIsm": "0x206789B0d838568eaFDcCa1e551FCF5c00bF99E2",
"staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"storageGasOracle": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72",
"testRecipient": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb",
"aggregationHook": "0x602160148F2e2A40bc42BADD5f5936aCFd431a35",
"domainRoutingIsm": "0x2589992a07E664c20123c6232620Af479F9ba7DC",
"domainRoutingIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"fallbackRoutingHook": "0x7483faD0Bc297667664A43A064bA7c9911659f57",
"interchainAccountIsm": "0x39c85C84876479694A2470c0E8075e9d68049aFc",
"interchainAccountRouter": "0x80fE4Cb8c70fc60B745d4ffD4403c27a8cBC9e02",
"interchainGasPaymaster": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb",
"interchainSecurityModule": "0x3865c419335B36d9CE240b515124Ab1c33927004",
"mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065",
"merkleTreeHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"pausableHook": "0x4fE19d49F45854Da50b6009258929613EC92C147",
"pausableIsm": "0xc76E477437065093D353b7d56c81ff54D167B0Ab",
"protocolFee": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643",
"proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B",
"staticAggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"staticAggregationIsm": "0x0b071Eb80757Dd347B3B8736C7ba9e5324c37D77",
"staticAggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticMerkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMerkleRootWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"staticMessageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMessageIdWeightedMultisigIsmFactory": "0x54148470292C24345fb828B003461a9444414517",
"storageGasOracle": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"testRecipient": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"validatorAnnounce": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC",
"index": {
"from": 5243565
"from": 251532
},
"deployer": {
"name": "Abacus Works",
@ -1773,7 +1773,7 @@
"interchainAccountIsm": "0xc08675806BA844467E559E45E4bB59e66778bDcd",
"interchainAccountRouter": "0x39c85C84876479694A2470c0E8075e9d68049aFc",
"interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainSecurityModule": "0x8a5D09753Ab5571fa78131EF839C70AFa3c45bFd",
"interchainSecurityModule": "0x4B310268158B842cbE65A216714C6A7D1d087155",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE",
"pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F",
@ -1836,7 +1836,7 @@
"interchainAccountIsm": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606",
"interchainAccountRouter": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44",
"interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"interchainSecurityModule": "0x4B2e8f63E345Db18973E46cE70972cE3D76585Bf",
"interchainSecurityModule": "0xAd6172DA241CE5DC38a32E0E375FD0A1889b9E48",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE",
"pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F",
@ -1899,7 +1899,7 @@
"interchainAccountIsm": "0xBF2C366530C1269d531707154948494D3fF4AcA7",
"interchainAccountRouter": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680",
"interchainGasPaymaster": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"interchainSecurityModule": "0x9e71cC1A91E48CfFA2F7D2956eB5c3b730bD8605",
"interchainSecurityModule": "0xa6570241124A6534801d1eba13F46078Dc7d1974",
"mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"merkleTreeHook": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72",
"pausableHook": "0xc76E477437065093D353b7d56c81ff54D167B0Ab",
@ -1924,6 +1924,246 @@
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
}
},
"alephzeroevmtestnet": {
"blockExplorers": [
{
"apiUrl": "https://evm-explorer-testnet.alephzero.org/api",
"family": "blockscout",
"name": "Aleph Zero EVM Testnet Explorer",
"url": "https://evm-explorer-testnet.alephzero.org"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 5
},
"chainId": 2039,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Aleph Zero EVM Testnet",
"domainId": 2039,
"index": {
"from": 1380870
},
"isTestnet": true,
"name": "alephzeroevmtestnet",
"nativeToken": {
"decimals": 18,
"name": "Testnet AZERO",
"symbol": "TZERO"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.alephzero-testnet.gelato.digital"
}
],
"technicalStack": "arbitrumnitro",
"aggregationHook": "0xf63f12A71d730794F8de247c65a40E5BF8fA590A",
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"fallbackRoutingHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD",
"interchainAccountIsm": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c",
"interchainAccountRouter": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670",
"interchainGasPaymaster": "0x867f2089D09903f208AeCac84E599B90E5a4A821",
"interchainSecurityModule": "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"

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

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

@ -4,7 +4,6 @@
use std::sync::Arc;
use std::time::Duration;
use derive_builder::Builder;
use eyre::Result;
use hyperlane_core::metrics::agent::decimals_by_protocol;
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 =
"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
#[derive(Clone, Builder, Debug)]
#[derive(Clone, Debug)]
pub struct AgentMetrics {
/// Current balance of native tokens for the
/// wallet address.
@ -57,27 +62,28 @@ pub struct AgentMetrics {
/// - `token_address`: Address of the token.
/// - `token_symbol`: Symbol of the token.
/// - `token_name`: Full name of the token.
#[builder(setter(into, strip_option), default)]
wallet_balance: Option<GaugeVec>,
}
pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result<AgentMetrics> {
Ok(AgentMetricsBuilder::default()
.wallet_balance(metrics.new_gauge(
"wallet_balance",
WALLET_BALANCE_HELP,
WALLET_BALANCE_LABELS,
)?)
.build()?)
impl AgentMetrics {
pub(crate) fn new(metrics: &CoreMetrics) -> Result<AgentMetrics> {
let agent_metrics = AgentMetrics {
wallet_balance: Some(metrics.new_gauge(
"wallet_balance",
WALLET_BALANCE_HELP,
WALLET_BALANCE_LABELS,
)?),
};
Ok(agent_metrics)
}
}
/// Chain-specific metrics
#[derive(Clone, Builder, Debug)]
#[derive(Clone, Debug)]
pub struct ChainMetrics {
/// Tracks the current block height of the chain.
/// - `chain`: the chain name (or ID if the name is unknown) of the chain
/// the block number refers to.
#[builder(setter(into))]
pub block_height: IntGaugeVec,
/// 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.
/// - `chain`: the chain name (or chain ID if the name is unknown) of the
/// chain the gas price refers to.
#[builder(setter(into, strip_option), default)]
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> {
Ok(ChainMetricsBuilder::default()
.block_height(metrics.new_int_gauge(
"block_height",
BLOCK_HEIGHT_HELP,
BLOCK_HEIGHT_LABELS,
)?)
.gas_price(metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?)
.build()?)
impl ChainMetrics {
pub(crate) fn new(metrics: &CoreMetrics) -> Result<ChainMetrics> {
let block_height_metrics =
metrics.new_int_gauge("block_height", BLOCK_HEIGHT_HELP, BLOCK_HEIGHT_LABELS)?;
let gas_price_metrics = metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?;
let critical_error_metrics =
metrics.new_int_gauge("critical_error", CRITICAL_ERROR_HELP, CRITICAL_ERROR_LABELS)?;
let chain_metrics = ChainMetrics {
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.
@ -174,8 +206,6 @@ impl MetricsUpdater {
}
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 {
return;
};
@ -195,10 +225,8 @@ impl MetricsUpdater {
let height = chain_metrics.latest_block.number as i64;
trace!(chain, height, "Fetched block height for metrics");
block_height
.with(&hashmap! { "chain" => chain })
.set(height);
if let Some(gas_price) = gas_price {
self.chain_metrics.set_block_height(chain, height);
if self.chain_metrics.gas_price.is_some() {
let protocol = self.conf.domain.domain_protocol();
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)
@ -208,7 +236,7 @@ impl MetricsUpdater {
gas = format!("{gas:.2}"),
"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 h_eth::TransactionOverrides;
use hyperlane_core::config::{ConfigErrResultExt, OperationBatchConfig};
use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol};
use hyperlane_cosmos::NativeToken;
use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol, NativeToken};
use crate::settings::envs::*;
use crate::settings::ChainConnectionConf;
@ -137,24 +137,7 @@ pub fn build_cosmos_connection_conf(
.parse_u64()
.end();
let native_token_decimals = chain
.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(),
};
let native_token = parse_native_token(chain, err, 18);
if !local_err.is_ok() {
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(
domain_protocol: HyperlaneDomainProtocol,
rpcs: &[Url],
@ -195,10 +217,12 @@ pub fn build_connection_conf(
.next()
.map(|url| ChainConnectionConf::Fuel(h_fuel::ConnectionConf { url: url.clone() })),
HyperlaneDomainProtocol::Sealevel => rpcs.iter().next().map(|url| {
ChainConnectionConf::Sealevel(h_sealevel::ConnectionConf {
url: url.clone(),
ChainConnectionConf::Sealevel(build_sealevel_connection_conf(
url,
chain,
err,
operation_batch,
})
))
}),
HyperlaneDomainProtocol::Cosmos => {
build_cosmos_connection_conf(rpcs, chain, err, operation_batch)

@ -17,6 +17,7 @@ pub use indexing::*;
pub use log_metadata::*;
pub use merkle_tree::*;
pub use message::*;
pub use native_token::NativeToken;
pub use reorg::*;
pub use transaction::*;
@ -33,6 +34,7 @@ mod indexing;
mod log_metadata;
mod merkle_tree;
mod message;
mod native_token;
mod reorg;
mod serialize;
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")]
use std::time::Duration;
use crate::{KnownHyperlaneDomain, H160, H256};
use crate::{KnownHyperlaneDomain, H160, H256, U256};
/// Converts a hex or base58 string to an H256.
pub fn hex_or_base58_to_h256(string: &str) -> Result<H256> {
@ -62,6 +62,17 @@ pub fn bytes_to_hex(bytes: &[u8]) -> String {
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.
pub fn fmt_domain(domain: u32) -> String {
#[cfg(feature = "strum")]

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

@ -192,19 +192,25 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
app_config: &TokenConfig,
program_id: Pubkey,
) {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
app_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
let try_fund_ata_payer = |ctx: &mut Context, client: &RpcClient| {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
app_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
}
}
}
};
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
if account_exists(client, &token_pda).unwrap() {
println!("Warp route token already exists, skipping init");
// Fund the ATA payer up to the specified amount.
try_fund_ata_payer(ctx, client);
return;
}
@ -398,6 +404,8 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
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.

@ -80,6 +80,45 @@
],
"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": {
"blockExplorers": [
{
@ -126,6 +165,45 @@
],
"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": {
"blockExplorers": [
{
@ -138,7 +216,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 32
"reorgPeriod": "finalized"
},
"chainId": 592,
"deployer": {
@ -245,6 +323,42 @@
],
"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": {
"blockExplorers": [
{
@ -419,7 +533,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 15
"reorgPeriod": "finalized"
},
"chainId": 56,
"deployer": {
@ -899,6 +1013,57 @@
],
"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": {
"blockExplorers": [
{
@ -1111,6 +1276,91 @@
],
"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": {
"blockExplorers": [
{
@ -1199,13 +1449,22 @@
},
"injective": {
"bech32Prefix": "inj",
"blockExplorers": [],
"blockExplorers": [
{
"apiUrl": "https://www.mintscan.io/injective",
"family": "other",
"name": "Mintscan",
"url": "https://www.mintscan.io/injective"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 10
},
"canonicalAsset": "inj",
"chainId": "injective-1",
"contractAddressBytes": 20,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
@ -1213,11 +1472,19 @@
"displayName": "Injective",
"domainId": 6909546,
"gasCurrencyCoinGeckoId": "injective-protocol",
"gasPrice": {
"amount": "700000000",
"denom": "inj"
},
"grpcUrls": [
{
"http": "sentry.chain.grpc.injective.network:443"
"http": "https://injective-grpc.goldenratiostaking.net:443"
}
],
"index": {
"chunk": 25,
"from": 58419500
},
"name": "injective",
"nativeToken": {
"decimals": 18,
@ -1239,6 +1506,42 @@
"slip44": 118,
"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": {
"blockExplorers": [
{
@ -1766,7 +2069,7 @@
"blocks": {
"confirmations": 2,
"estimateBlockTime": 12,
"reorgPeriod": 10
"reorgPeriod": "finalized"
},
"chainId": 1284,
"deployer": {
@ -1795,6 +2098,42 @@
"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": {
"bech32Prefix": "neutron",
"blockExplorers": [
@ -1810,7 +2149,9 @@
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"canonicalAsset": "untrn",
"chainId": "neutron-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
@ -1818,12 +2159,19 @@
"displayName": "Neutron",
"domainId": 1853125230,
"gasCurrencyCoinGeckoId": "neutron-3",
"gasPrice": {
"amount": "0.0053",
"denom": "untrn"
},
"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",
"nativeToken": {
"decimals": 6,
@ -1924,6 +2272,45 @@
],
"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": {
"bech32Prefix": "osmo",
"blockExplorers": [
@ -1939,7 +2326,9 @@
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"canonicalAsset": "uosmo",
"chainId": "osmosis-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Mitosis",
"url": "https://mitosis.org"
@ -1947,11 +2336,19 @@
"displayName": "Osmosis",
"domainId": 875,
"gasCurrencyCoinGeckoId": "osmosis",
"gasPrice": {
"amount": "0.025",
"denom": "uosmo"
},
"grpcUrls": [
{
"http": "osmosis-grpc.publicnode.com:443"
"http": "https://osmosis-grpc.publicnode.com:443"
}
],
"index": {
"chunk": 10,
"from": 14389169
},
"isTestnet": false,
"name": "osmosis",
"nativeToken": {
@ -1989,7 +2386,7 @@
"blocks": {
"confirmations": 3,
"estimateBlockTime": 2,
"reorgPeriod": 256
"reorgPeriod": "finalized"
},
"chainId": 137,
"deployer": {
@ -2434,7 +2831,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 5,
"reorgPeriod": 5
"reorgPeriod": "finalized"
},
"chainId": 109,
"deployer": {
@ -2461,6 +2858,42 @@
],
"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": {
"blockExplorers": [
{
@ -2512,7 +2945,9 @@
"estimateBlockTime": 5,
"reorgPeriod": 1
},
"canonicalAsset": "ustrd",
"chainId": "stride-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Stride Labs",
"url": "https://www.stride.zone"
@ -2520,12 +2955,19 @@
"displayName": "Stride",
"domainId": 745,
"gasCurrencyCoinGeckoId": "stride",
"gasPrice": {
"amount": "0.025",
"denom": "ustrd"
},
"grpcUrls": [
{
"http": "stride-grpc.polkachu.com:12290"
"http": "https://stride-grpc.publicnode.com:443"
}
],
"isTestnet": false,
"index": {
"chunk": 5,
"from": 9152000
},
"name": "stride",
"nativeToken": {
"decimals": 6,
@ -2637,7 +3079,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 6,
"reorgPeriod": 10
"reorgPeriod": "finalized"
},
"chainId": 5845,
"deployer": {
@ -2732,7 +3174,7 @@
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://raas-backend.alchemy.com/rpc/worldchain-mainnet/rollup"
"http": "https://worldchain-mainnet.g.alchemy.com/public"
}
],
"technicalStack": "opstack"
@ -2817,6 +3259,42 @@
],
"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": {
"blockExplorers": [
{
@ -2900,6 +3378,42 @@
],
"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": {
"blockExplorers": [
{

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

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

@ -7,5 +7,5 @@ pragma solidity >=0.6.11;
**/
abstract contract PackageVersioned {
// 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 {IAggregationIsm} from "../../interfaces/isms/IAggregationIsm.sol";
import {AggregationIsmMetadata} from "../../isms/libs/AggregationIsmMetadata.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
/**
* @title AggregationIsm
* @notice Manages per-domain m-of-n ISM sets that are used to verify
* interchain messages.
*/
abstract contract AbstractAggregationIsm is IAggregationIsm {
abstract contract AbstractAggregationIsm is IAggregationIsm, PackageVersioned {
// ============ Constants ============
// 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
* interchain messages.
*/
contract StaticAggregationIsm is AbstractAggregationIsm, PackageVersioned {
contract StaticAggregationIsm is AbstractAggregationIsm {
// ============ 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
* interchain messages.
*/
abstract contract AbstractMultisigIsm is AbstractMultisig {
abstract contract AbstractMultisigIsm is AbstractMultisig, IMultisigIsm {
// ============ Virtual Functions ============
// ======= 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 ============
import {MetaProxy} from "./MetaProxy.sol";
import {PackageVersioned} from "../PackageVersioned.sol";
import {IThresholdAddressFactory} from "../interfaces/IThresholdAddressFactory.sol";
abstract contract StaticThresholdAddressSetFactory is PackageVersioned {
abstract contract StaticThresholdAddressSetFactory is
PackageVersioned,
IThresholdAddressFactory
{
// ============ Immutables ============
address public immutable implementation;
@ -32,6 +36,10 @@ abstract contract StaticThresholdAddressSetFactory is PackageVersioned {
address[] calldata _values,
uint8 _threshold
) public returns (address) {
require(
0 < _threshold && _threshold <= _values.length,
"Invalid threshold"
);
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values,
_threshold

@ -78,8 +78,8 @@ contract MockMailbox is Mailbox {
inboundProcessedNonce++;
}
function processInboundMessage(uint32 _nonce) public {
function processInboundMessage(uint32 _nonce) public payable {
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",
"description": "Core solidity contracts for Hyperlane",
"version": "5.6.1",
"version": "5.7.1",
"dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1",
"@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",
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^v4.9.3",

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

@ -7,6 +7,8 @@ import "@openzeppelin/contracts/utils/Strings.sol";
import {IAggregationIsm} from "../../contracts/interfaces/isms/IAggregationIsm.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 {TestIsm, ThresholdTestUtils} from "./IsmTestUtils.sol";
@ -16,10 +18,10 @@ contract AggregationIsmTest is Test {
string constant fixtureKey = "fixture";
StaticAggregationIsmFactory factory;
IThresholdAddressFactory factory;
IAggregationIsm ism;
function setUp() public {
function setUp() public virtual {
factory = new StaticAggregationIsmFactory();
}
@ -46,6 +48,8 @@ contract AggregationIsmTest is Test {
uint8 n,
bytes32 seed
) internal returns (address[] memory) {
vm.assume(m > 0 && m <= n && n < 10);
bytes32 randomness = seed;
address[] memory isms = new address[](n);
for (uint256 i = 0; i < n; i++) {
@ -91,7 +95,6 @@ contract AggregationIsmTest is Test {
}
function testVerify(uint8 m, uint8 n, bytes32 seed) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed);
@ -104,7 +107,7 @@ contract AggregationIsmTest is Test {
uint8 i,
bytes32 seed
) public {
vm.assume(0 < m && m <= n && n < 10 && i < n);
vm.assume(i < n);
deployIsms(m, n, seed);
(address[] memory modules, ) = ism.modulesAndThreshold("");
bytes memory noMetadata;
@ -115,7 +118,6 @@ contract AggregationIsmTest is Test {
}
function testVerifyMissingMetadata(uint8 m, uint8 n, bytes32 seed) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed);
// Populate metadata for one fewer ISMs than needed.
@ -129,7 +131,6 @@ contract AggregationIsmTest is Test {
uint8 n,
bytes32 seed
) public {
vm.assume(0 < m && m <= n && n < 10);
deployIsms(m, n, seed);
bytes memory metadata = getMetadata(m, seed);
@ -141,11 +142,26 @@ contract AggregationIsmTest is Test {
}
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 actualIsms, uint8 actualThreshold) = ism
.modulesAndThreshold("");
assertEq(abi.encode(actualIsms), abi.encode(expectedIsms));
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 {MerkleRootMultisigIsmMetadata} from "../../contracts/isms/libs/MerkleRootMultisigIsmMetadata.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 {MerkleTreeHook} from "../../contracts/hooks/MerkleTreeHook.sol";
import {TestMerkleTreeHook} from "../../contracts/test/TestMerkleTreeHook.sol";
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol";
import {Message} from "../../contracts/libs/Message.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
abstract contract AbstractMultisigIsmTest is Test {
@ -32,7 +35,7 @@ abstract contract AbstractMultisigIsmTest is Test {
string constant prefixKey = "prefix";
uint32 constant ORIGIN = 11;
StaticThresholdAddressSetFactory factory;
IThresholdAddressFactory factory;
IInterchainSecurityModule ism;
TestMerkleTreeHook internal merkleTreeHook;
TestPostDispatchHook internal noopHook;
@ -163,7 +166,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 n,
bytes32 seed
) 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 metadata = getMetadata(m, n, seed, message);
assertTrue(ism.verify(metadata, message));
@ -177,7 +180,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 n,
bytes32 seed
) 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 metadata = getMetadata(m, n, seed, message);
@ -212,6 +215,16 @@ abstract contract AbstractMultisigIsmTest is Test {
vm.expectRevert("!threshold");
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 {
@ -308,3 +321,93 @@ contract MessageIdMultisigIsmTest is AbstractMultisigIsmTest {
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
);
}
function testThresholdExceedsLength() public override {
// no-op
}
function testZeroThreshold() public override {
// no-op
}
}
contract StaticMessageIdWeightedMultisigIsmTest is
@ -298,4 +306,12 @@ contract StaticMessageIdWeightedMultisigIsmTest is
seed
);
}
function testThresholdExceedsLength() public override {
// no-op
}
function testZeroThreshold() public override {
// no-op
}
}

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

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

@ -1,5 +1,46 @@
# @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
### Patch Changes

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

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

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

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

@ -24,6 +24,8 @@ import {
writeYamlOrJson,
} from '../utils/files.js';
import { getWarpCoreConfigOrExit } from '../utils/input.js';
import { selectRegistryWarpRoute } from '../utils/tokens.js';
import { runVerifyWarpRoute } from '../verify/warp.js';
import {
DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH,
@ -54,6 +56,7 @@ export const warpCommand: CommandModule = {
.command(init)
.command(read)
.command(send)
.command(verify)
.version(false)
.demandCommand(),
@ -334,3 +337,25 @@ export const check: CommandModuleWithContext<{
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 { CoreConfigSchema, HookConfig, IsmConfig } from '@hyperlane-xyz/sdk';
import {
CoreConfigSchema,
HookConfig,
IsmConfig,
OwnableConfig,
} from '@hyperlane-xyz/sdk';
import { CommandContext } from '../context/types.js';
import { errorRed, log, logBlue, logGreen } from '../logger.js';
@ -18,6 +23,9 @@ import {
} from './hooks.js';
import { createAdvancedIsmConfig, createTrustedRelayerConfig } from './ism.js';
const ENTER_DESIRED_VALUE_MSG = 'Enter the desired';
const SIGNER_PROMPT_LABEL = 'signer';
export async function createCoreDeployConfig({
context,
configFilePath,
@ -31,9 +39,9 @@ export async function createCoreDeployConfig({
const owner = await detectAndConfirmOrPrompt(
async () => context.signer?.getAddress(),
'Enter the desired',
ENTER_DESIRED_VALUE_MSG,
'owner address',
'signer',
SIGNER_PROMPT_LABEL,
);
const defaultIsm: IsmConfig = advanced
@ -41,6 +49,7 @@ export async function createCoreDeployConfig({
: await createTrustedRelayerConfig(context, advanced);
let defaultHook: HookConfig, requiredHook: HookConfig;
let proxyAdmin: OwnableConfig;
if (advanced) {
defaultHook = await createHookConfig({
context,
@ -52,9 +61,20 @@ export async function createCoreDeployConfig({
selectMessage: 'Select required hook type',
advanced,
});
proxyAdmin = {
owner: await detectAndConfirmOrPrompt(
async () => context.signer?.getAddress(),
ENTER_DESIRED_VALUE_MSG,
'ProxyAdmin owner address',
SIGNER_PROMPT_LABEL,
),
};
} else {
defaultHook = await createMerkleTreeConfig();
requiredHook = await createProtocolFeeConfig(context, advanced);
proxyAdmin = {
owner,
};
}
try {
@ -63,6 +83,7 @@ export async function createCoreDeployConfig({
defaultIsm,
defaultHook,
requiredHook,
proxyAdmin,
});
logBlue(`Core config is valid, writing to file ${configFilePath}:\n`);
log(indentYamlOrJson(yamlStringify(coreConfig, null, 2), 4));

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

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

@ -3,8 +3,8 @@ import { stringify as yamlStringify } from 'yaml';
import {
AnnotatedEV5Transaction,
SubmissionStrategy,
getChainIdFromTxs,
} from '@hyperlane-xyz/sdk';
import { MultiProvider } from '@hyperlane-xyz/sdk';
import { assert, errorToString } from '@hyperlane-xyz/utils';
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/*.',
);
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>({
submissionStrategy,
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(
transactionsFilepath: string,
): AnnotatedEV5Transaction[] {

@ -3,6 +3,7 @@ import { stringify as yamlStringify } from 'yaml';
import {
ChainMap,
DeployedOwnableConfig,
IsmConfig,
IsmType,
MailboxClientConfig,
@ -28,7 +29,10 @@ import {
readYamlOrJson,
writeYamlOrJson,
} from '../utils/files.js';
import { detectAndConfirmOrPrompt } from '../utils/input.js';
import {
detectAndConfirmOrPrompt,
setProxyAdminConfig,
} from '../utils/input.js';
import { createAdvancedIsmConfig } from './ism.js';
@ -129,7 +133,9 @@ export async function createWarpRouteDeployConfig({
chainMetadata: context.chainMetadata,
message: 'Select chains to connect',
requireNumber: 1,
requiresConfirmation: true,
// If the user supplied the --yes flag we skip asking selection
// confirmation
requiresConfirmation: !context.skipConfirmation,
});
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:`,
}));
const proxyAdmin: DeployedOwnableConfig = await setProxyAdminConfig(
context,
chain,
owner,
);
/**
* 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
@ -190,6 +202,7 @@ export async function createWarpRouteDeployConfig({
mailbox,
type,
owner,
proxyAdmin,
isNft,
interchainSecurityModule,
token: await input({
@ -203,6 +216,7 @@ export async function createWarpRouteDeployConfig({
type,
owner,
isNft,
proxyAdmin,
collateralChainName: '', // This will be derived correctly by zod.parse() below
interchainSecurityModule,
};
@ -216,6 +230,7 @@ export async function createWarpRouteDeployConfig({
mailbox,
type,
owner,
proxyAdmin,
isNft,
interchainSecurityModule,
token: await input({
@ -230,6 +245,7 @@ export async function createWarpRouteDeployConfig({
mailbox,
type,
owner,
proxyAdmin,
isNft,
interchainSecurityModule,
token: await input({
@ -242,6 +258,7 @@ export async function createWarpRouteDeployConfig({
mailbox,
type,
owner,
proxyAdmin,
isNft,
interchainSecurityModule,
};

@ -188,9 +188,18 @@ async function getMultiProvider(registry: IRegistry, signer?: ethers.Signer) {
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[],
chainMetadata: ChainMap<ChainMetadata>,
registry: IRegistry,
): Promise<ChainMap<string>> {
const apiKeys: ChainMap<string> = {};
@ -218,6 +227,11 @@ export async function getOrRequestApiKeys(
`${chain} api key`,
`${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';
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 { log, logBlue, logGray, logGreen } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
@ -34,6 +34,7 @@ interface DeployParams {
interface ApplyParams extends DeployParams {
deployedCoreAddresses: DeployedCoreAddresses;
}
/**
* Executes the core deploy command.
*/
@ -64,7 +65,7 @@ export async function runCoreDeploy(params: DeployParams) {
let apiKeys: ChainMap<string> = {};
if (!skipConfirmation)
apiKeys = await getOrRequestApiKeys([chain], chainMetadata);
apiKeys = await requestAndSaveApiKeys([chain], chainMetadata, registry);
const deploymentParams: DeployParams = {
context,

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

Loading…
Cancel
Save