Merge main to v3 (#2812)

dan/v3-sealevel-e2e
Yorke Rhodes 1 year ago committed by GitHub
parent 9168cca6d2
commit 1d18549755
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .github/CODEOWNERS
  2. 2
      README.md
  3. 2
      rust-toolchain
  4. 1694
      rust/Cargo.lock
  5. 11
      rust/Cargo.toml
  6. 4
      rust/Dockerfile
  7. 4
      rust/README.md
  8. 21
      rust/agents/relayer/src/msg/metadata/base.rs
  9. 26
      rust/agents/relayer/src/msg/pending_message.rs
  10. 5
      rust/agents/relayer/src/msg/pending_operation.rs
  11. 2
      rust/agents/relayer/src/msg/processor.rs
  12. 2
      rust/agents/scraper/migration/Cargo.toml
  13. 13
      rust/agents/scraper/migration/bin/generate_entities.rs
  14. 6
      rust/agents/scraper/src/db/generated/block.rs
  15. 2
      rust/agents/scraper/src/db/generated/cursor.rs
  16. 10
      rust/agents/scraper/src/db/generated/delivered_message.rs
  17. 2
      rust/agents/scraper/src/db/generated/domain.rs
  18. 4
      rust/agents/scraper/src/db/generated/gas_payment.rs
  19. 16
      rust/agents/scraper/src/db/generated/message.rs
  20. 2
      rust/agents/scraper/src/db/generated/mod.rs
  21. 15
      rust/agents/scraper/src/db/generated/prelude.rs
  22. 12
      rust/agents/scraper/src/db/generated/transaction.rs
  23. 16
      rust/chains/hyperlane-ethereum/src/tx.rs
  24. 28
      rust/chains/hyperlane-ethereum/src/validator_announce.rs
  25. 12
      rust/chains/hyperlane-sealevel/src/interchain_gas.rs
  26. 2
      rust/hyperlane-base/Cargo.toml
  27. 71
      rust/hyperlane-base/src/contract_sync/cursor.rs
  28. 9
      rust/hyperlane-base/src/contract_sync/eta_calculator.rs
  29. 4
      rust/hyperlane-base/src/contract_sync/mod.rs
  30. 4
      rust/hyperlane-base/src/settings/loader/case_adapter.rs
  31. 5
      rust/hyperlane-base/src/types/local_storage.rs
  32. 1
      rust/hyperlane-base/src/types/multisig.rs
  33. 1
      rust/hyperlane-core/Cargo.toml
  34. 2
      rust/hyperlane-core/src/traits/indexer.rs
  35. 13
      rust/hyperlane-core/src/traits/interchain_security_module.rs
  36. 18
      rust/hyperlane-core/src/types/primitive_types.rs
  37. 2
      rust/hyperlane-core/src/utils.rs
  38. 1
      rust/sealevel/client/Cargo.toml
  39. 59
      rust/sealevel/client/src/artifacts.rs
  40. 10
      rust/sealevel/client/src/context.rs
  41. 99
      rust/sealevel/client/src/core.rs
  42. 197
      rust/sealevel/client/src/helloworld.rs
  43. 470
      rust/sealevel/client/src/main.rs
  44. 312
      rust/sealevel/client/src/multisig_ism.rs
  45. 593
      rust/sealevel/client/src/router.rs
  46. 68
      rust/sealevel/client/src/serde.rs
  47. 723
      rust/sealevel/client/src/warp_route.rs
  48. 1
      rust/sealevel/environments/devnet/solanadevnet/core/keys/hyperlane_sealevel_mailbox-keypair.json
  49. 1
      rust/sealevel/environments/devnet/solanadevnet/core/keys/hyperlane_sealevel_multisig_ism_message_id-keypair.json
  50. 1
      rust/sealevel/environments/devnet/solanadevnet/core/keys/hyperlane_sealevel_validator_announce-keypair.json
  51. 5
      rust/sealevel/environments/devnet/solanadevnet/core/program-ids.json
  52. 1
      rust/sealevel/environments/devnet/solanadevnet1/core/keys/hyperlane_sealevel_mailbox-keypair.json
  53. 1
      rust/sealevel/environments/devnet/solanadevnet1/core/keys/hyperlane_sealevel_multisig_ism_message_id-keypair.json
  54. 1
      rust/sealevel/environments/devnet/solanadevnet1/core/keys/hyperlane_sealevel_validator_announce-keypair.json
  55. 5
      rust/sealevel/environments/devnet/solanadevnet1/core/program-ids.json
  56. 29
      rust/sealevel/environments/devnet/warp-routes/chain-config.json
  57. 1
      rust/sealevel/environments/devnet/warp-routes/collateraltest/keys/hyperlane_sealevel_token-solanadevnet1.json
  58. 1
      rust/sealevel/environments/devnet/warp-routes/collateraltest/keys/hyperlane_sealevel_token_collateral-solanadevnet.json
  59. 14
      rust/sealevel/environments/devnet/warp-routes/collateraltest/program-ids.json
  60. 23
      rust/sealevel/environments/devnet/warp-routes/collateraltest/token-config.json
  61. 1
      rust/sealevel/environments/devnet/warp-routes/nativetest/keys/hyperlane_sealevel_token-solanadevnet1.json
  62. 1
      rust/sealevel/environments/devnet/warp-routes/nativetest/keys/hyperlane_sealevel_token_native-solanadevnet.json
  63. 14
      rust/sealevel/environments/devnet/warp-routes/nativetest/program-ids.json
  64. 19
      rust/sealevel/environments/devnet/warp-routes/nativetest/token-config.json
  65. 4
      rust/sealevel/environments/local-e2e/chain-config.json
  66. 350
      rust/sealevel/environments/mainnet2/chain-config.json
  67. 30
      rust/sealevel/environments/mainnet2/helloworld/hyperlane/helloworld-config.json
  68. 42
      rust/sealevel/environments/mainnet2/helloworld/hyperlane/program-ids.json
  69. 32
      rust/sealevel/environments/mainnet2/helloworld/rc/helloworld-config.json
  70. 42
      rust/sealevel/environments/mainnet2/helloworld/rc/program-ids.json
  71. 106
      rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/hyperlane/multisig-config.json
  72. 18
      rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/multisig-config.json
  73. 3
      rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/program-ids.json
  74. 65
      rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/multisig-config.json
  75. 3
      rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/program-ids.json
  76. 29
      rust/sealevel/environments/mainnet2/warp-routes/chain-config.json
  77. 4
      rust/sealevel/environments/mainnet2/warp-routes/zbc/program-ids.json
  78. 12
      rust/sealevel/environments/mainnet2/warp-routes/zbc/token-config.json
  79. 335
      rust/sealevel/environments/testnet3/chain-config.json
  80. 32
      rust/sealevel/environments/testnet3/helloworld/hyperlane/helloworld-config.json
  81. 1
      rust/sealevel/environments/testnet3/helloworld/hyperlane/keys/hyperlane_sealevel_hello_world-solanadevnet.json
  82. 42
      rust/sealevel/environments/testnet3/helloworld/hyperlane/program-ids.json
  83. 32
      rust/sealevel/environments/testnet3/helloworld/rc/helloworld-config.json
  84. 1
      rust/sealevel/environments/testnet3/helloworld/rc/keys/hyperlane_sealevel_hello_world-solanadevnet.json
  85. 42
      rust/sealevel/environments/testnet3/helloworld/rc/program-ids.json
  86. 92
      rust/sealevel/environments/testnet3/multisig-ism-message-id/solanadevnet/hyperlane/multisig-config.json
  87. 1
      rust/sealevel/environments/testnet3/multisig-ism-message-id/solanadevnet/rc/keys/hyperlane_sealevel_multisig_ism_message_id-keypair.json
  88. 65
      rust/sealevel/environments/testnet3/multisig-ism-message-id/solanadevnet/rc/multisig-config.json
  89. 3
      rust/sealevel/environments/testnet3/multisig-ism-message-id/solanadevnet/rc/program-ids.json
  90. 2
      rust/sealevel/environments/testnet3/solanadevnet/core/keys/hyperlane_sealevel_multisig_ism_message_id-keypair.json
  91. 2
      rust/sealevel/environments/testnet3/solanadevnet/core/program-ids.json
  92. 29
      rust/sealevel/environments/testnet3/warp-routes/chain-config.json
  93. 12
      rust/sealevel/environments/testnet3/warp-routes/proteustest/program-ids.json
  94. 4
      rust/sealevel/libraries/account-utils/src/lib.rs
  95. 58
      rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs
  96. 3
      rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs
  97. 30
      rust/sealevel/programs/helloworld/Cargo.toml
  98. 104
      rust/sealevel/programs/helloworld/src/accounts.rs
  99. 146
      rust/sealevel/programs/helloworld/src/instruction.rs
  100. 9
      rust/sealevel/programs/helloworld/src/lib.rs
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,7 +2,7 @@
*.sol @yorhodes @tkporter @aroralanuk *.sol @yorhodes @tkporter @aroralanuk
*.ts @yorhodes @jmrossy @nambrot *.ts @yorhodes @jmrossy @nambrot
*.rs @mattiecnvr @tkporter @daniel-savu *.rs @tkporter @daniel-savu
*.md @Skunkchain @nambrot @avious00 *.md @Skunkchain @nambrot @avious00
# Package owners # Package owners
@ -11,7 +11,7 @@
solidity/ @yorhodes @tkporter @aroralanuk solidity/ @yorhodes @tkporter @aroralanuk
## Agents ## Agents
rust/ @mattiecnvr @tkporter @daniel-savu rust/ @tkporter @daniel-savu
## SDK ## SDK
typescript/sdk @yorhodes @jmrossy typescript/sdk @yorhodes @jmrossy
@ -23,4 +23,4 @@ typescript/token @yorhodes @jmrossy @tkporter @aroralanuk
typescript/helloworld @yorhodes @nambrot typescript/helloworld @yorhodes @nambrot
## Infra ## Infra
typescript/infra @tkporter @nambrot @mattiecnvr typescript/infra @tkporter @nambrot

@ -24,7 +24,7 @@ Hyperlane is an interchain messaging protocol that allows applications to commun
Developers can use Hyperlane to share state between blockchains, allowing them to build interchain applications that live natively across multiple chains. Developers can use Hyperlane to share state between blockchains, allowing them to build interchain applications that live natively across multiple chains.
To read more about interchain applications, how the protocol works, and how to integrate with Hyperlane, please see the [documentation](https://docs.hyperlane.xyz/). To read more about interchain applications, how the protocol works, and how to integrate with Hyperlane, please see the [documentation](https://docs.hyperlane.xyz).
## Working on Hyperlane ## Working on Hyperlane

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.71.1" channel = "1.72.1"
profile = "default" profile = "default"

1694
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -22,6 +22,7 @@ members = [
"sealevel/libraries/serializable-account-meta", "sealevel/libraries/serializable-account-meta",
"sealevel/libraries/test-transaction-utils", "sealevel/libraries/test-transaction-utils",
"sealevel/libraries/test-utils", "sealevel/libraries/test-utils",
"sealevel/programs/helloworld",
"sealevel/programs/hyperlane-sealevel-token", "sealevel/programs/hyperlane-sealevel-token",
"sealevel/programs/hyperlane-sealevel-token-collateral", "sealevel/programs/hyperlane-sealevel-token-collateral",
"sealevel/programs/hyperlane-sealevel-token-native", "sealevel/programs/hyperlane-sealevel-token-native",
@ -55,7 +56,7 @@ auto_impl = "1.0"
backtrace = "0.3" backtrace = "0.3"
base64 = "0.21.2" base64 = "0.21.2"
bincode = "1.3" bincode = "1.3"
borsh = "0.9" borsh = "0.9" # 0.9 is needed for solana 1.14
bs58 = "0.5.0" bs58 = "0.5.0"
bytes = "1" bytes = "1"
clap = "4" clap = "4"
@ -76,7 +77,7 @@ fuels = "0.38"
fuels-code-gen = "0.38" fuels-code-gen = "0.38"
futures = "0.3" futures = "0.3"
futures-util = "0.3" futures-util = "0.3"
generic-array = { version = "0.14", features = ["serde", "more_lengths"] } generic-array = { version = "1.0", features = ["serde", "more_lengths"] }
getrandom = { version = "0.2", features = ["js"] } # Required for WASM support https://docs.rs/getrandom/latest/getrandom/#webassembly-support getrandom = { version = "0.2", features = ["js"] } # Required for WASM support https://docs.rs/getrandom/latest/getrandom/#webassembly-support
hex = "0.4" hex = "0.4"
itertools = "0.11.0" itertools = "0.11.0"
@ -85,7 +86,7 @@ log = "0.4"
macro_rules_attribute = "0.2" macro_rules_attribute = "0.2"
maplit = "1.0" maplit = "1.0"
mockall = "0.11" mockall = "0.11"
nix = { version = "0.26", default-features = false } nix = { version = "0.27", default-features = false }
num = "0.4" num = "0.4"
num-bigint = "0.4" num-bigint = "0.4"
num-derive = "0.4.0" num-derive = "0.4.0"
@ -99,8 +100,8 @@ regex = "1.5"
reqwest = "0.11" reqwest = "0.11"
rlp = "=0.5.2" rlp = "=0.5.2"
rocksdb = "0.21.0" rocksdb = "0.21.0"
sea-orm = { version = "0.11.1", features = ["sqlx-postgres", "runtime-tokio-native-tls", "with-bigdecimal", "with-time", "macros"] } sea-orm = { version = "0.12.3", features = ["sqlx-postgres", "runtime-tokio-native-tls", "with-bigdecimal", "with-time", "macros"] }
sea-orm-migration = { version = "0.11.1", features = ["sqlx-postgres", "runtime-tokio-native-tls"] } sea-orm-migration = { version = "0.12.3", features = ["sqlx-postgres", "runtime-tokio-native-tls"] }
semver = "1.0" semver = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11" serde_bytes = "0.11"

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:experimental # syntax=docker/dockerfile:experimental
FROM rust:1.71.1 as builder FROM rust:1.72.1 as builder
WORKDIR /usr/src WORKDIR /usr/src
# 1a: Prepare for static linking # 1a: Prepare for static linking
@ -34,7 +34,7 @@ RUN \
cp /usr/src/target/release/scraper /release cp /usr/src/target/release/scraper /release
## 2: Copy the binaries to release image ## 2: Copy the binaries to release image
FROM ubuntu:20.04 FROM ubuntu:22.04
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y \ apt-get install -y \
openssl \ openssl \

@ -5,14 +5,14 @@
- install `rustup` - install `rustup`
- [link here](https://rustup.rs/) - [link here](https://rustup.rs/)
Note: You should be running >= version `1.71.1` of the rustc compiler, you can see that version with this command and Note: You should be running >= version `1.72.1` of the rustc compiler, you can see that version with this command and
should see similar output: should see similar output:
``` ```
$ rustup --version $ rustup --version
rustup 1.26.0 (5af9b9484 2023-04-05) rustup 1.26.0 (5af9b9484 2023-04-05)
info: This is the version for the rustup toolchain manager, not the rustc compiler. info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.71.1 (eb26296b5 2023-08-03)` info: The currently active `rustc` version is `rustc 1.72.1 (d5c2e9c34 2023-09-13)`
``` ```
### Running Locally ### Running Locally

@ -76,9 +76,14 @@ impl MetadataBuilder for BaseMetadataBuilder {
ism_address: H256, ism_address: H256,
message: &HyperlaneMessage, message: &HyperlaneMessage,
) -> Result<Option<Vec<u8>>> { ) -> Result<Option<Vec<u8>>> {
const CTX: &str = "When fetching module type"; let ism = self
let ism = self.build_ism(ism_address).await.context(CTX)?; .build_ism(ism_address)
let module_type = ism.module_type().await.context(CTX)?; .await
.context("When building ISM")?;
let module_type = ism
.module_type()
.await
.context("When fetching module type")?;
let base = self.clone_with_incremented_depth()?; let base = self.clone_with_incremented_depth()?;
let metadata_builder: Box<dyn MetadataBuilder> = match module_type { let metadata_builder: Box<dyn MetadataBuilder> = match module_type {
@ -96,7 +101,7 @@ impl MetadataBuilder for BaseMetadataBuilder {
metadata_builder metadata_builder
.build(ism_address, message) .build(ism_address, message)
.await .await
.context(CTX) .context("When building metadata")
} }
} }
@ -193,8 +198,12 @@ impl BaseMetadataBuilder {
for (&validator, validator_storage_locations) in validators.iter().zip(storage_locations) { for (&validator, validator_storage_locations) in validators.iter().zip(storage_locations) {
for storage_location in validator_storage_locations.iter().rev() { for storage_location in validator_storage_locations.iter().rev() {
let Ok(config) = CheckpointSyncerConf::from_str(storage_location) else { let Ok(config) = CheckpointSyncerConf::from_str(storage_location) else {
debug!(?validator, ?storage_location, "Could not parse checkpoint syncer config for validator"); debug!(
continue ?validator,
?storage_location,
"Could not parse checkpoint syncer config for validator"
);
continue;
}; };
// If this is a LocalStorage based checkpoint syncer and it's not // If this is a LocalStorage based checkpoint syncer and it's not

@ -1,17 +1,17 @@
use std::fmt::{Debug, Formatter}; use std::{
use std::sync::Arc; fmt::{Debug, Formatter},
use std::time::{Duration, Instant}; sync::Arc,
time::{Duration, Instant},
};
use async_trait::async_trait; use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use eyre::{Context, Result}; use eyre::{Context, Result};
use hyperlane_base::db::HyperlaneRocksDB; use hyperlane_base::{db::HyperlaneRocksDB, CoreMetrics};
use hyperlane_core::{HyperlaneChain, HyperlaneDomain, HyperlaneMessage, Mailbox, U256};
use prometheus::{IntCounter, IntGauge}; use prometheus::{IntCounter, IntGauge};
use tracing::{debug, error, info, instrument, trace, warn}; use tracing::{debug, error, info, instrument, trace, warn};
use hyperlane_base::CoreMetrics;
use hyperlane_core::{HyperlaneChain, HyperlaneDomain, HyperlaneMessage, Mailbox, U256};
use super::{ use super::{
gas_payment::GasPaymentEnforcer, gas_payment::GasPaymentEnforcer,
metadata::{BaseMetadataBuilder, MetadataBuilder}, metadata::{BaseMetadataBuilder, MetadataBuilder},
@ -183,8 +183,7 @@ impl PendingOperation for PendingMessage {
.message_meets_gas_payment_requirement(&self.message, &tx_cost_estimate) .message_meets_gas_payment_requirement(&self.message, &tx_cost_estimate)
.await, .await,
"checking if message meets gas payment requirement" "checking if message meets gas payment requirement"
) ) else {
else {
info!(?tx_cost_estimate, "Gas payment requirement not met yet"); info!(?tx_cost_estimate, "Gas payment requirement not met yet");
return self.on_reprepare(); return self.on_reprepare();
}; };
@ -393,9 +392,12 @@ impl PendingMessage {
i if (1..12).contains(&i) => 10, i if (1..12).contains(&i) => 10,
// wait 90s to 19.5min with a linear increase // wait 90s to 19.5min with a linear increase
i if (12..24).contains(&i) => (i as u64 - 11) * 90, i if (12..24).contains(&i) => (i as u64 - 11) * 90,
// exponential increase + 30 min; -21 makes it so that at i = 32 it will be // wait 30min for the next 12 attempts
// ~60min timeout (64min to be more precise). i if (24..36).contains(&i) => 60 * 30,
i => (2u64).pow(i - 21) + 60 * 30, // wait 60min for the next 12 attempts
i if (36..48).contains(&i) => 60 * 60,
// wait 3h for the next 12 attempts,
_ => 60 * 60 * 3,
})) }))
} }
} }

@ -1,10 +1,8 @@
use std::cmp::Ordering; use std::{cmp::Ordering, time::Instant};
use std::time::Instant;
use async_trait::async_trait; use async_trait::async_trait;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use eyre::Report; use eyre::Report;
use hyperlane_core::HyperlaneDomain; use hyperlane_core::HyperlaneDomain;
#[allow(unused_imports)] // required for enum_dispatch #[allow(unused_imports)] // required for enum_dispatch
@ -141,6 +139,7 @@ macro_rules! make_op_try {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
warn!(error=?e, concat!("Error when ", $ctx)); warn!(error=?e, concat!("Error when ", $ctx));
#[allow(clippy::redundant_closure_call)]
return $on_retry(); return $on_retry();
} }
} }

@ -390,7 +390,7 @@ mod test {
.await; .await;
// Set some retry counts. This should update HyperlaneDB entries too. // Set some retry counts. This should update HyperlaneDB entries too.
let msg_retries_to_set = vec![3, 0, 10]; let msg_retries_to_set = [3, 0, 10];
pending_messages pending_messages
.into_iter() .into_iter()
.enumerate() .enumerate()

@ -18,7 +18,7 @@ sea-orm.workspace = true
sea-orm-migration.workspace = true sea-orm-migration.workspace = true
serde.workspace = true serde.workspace = true
time.workspace = true time.workspace = true
tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tokio = { workspace = true, features = ["rt", "process", "macros", "parking_lot"] }
# bin-only deps # bin-only deps
tracing-subscriber.workspace = true tracing-subscriber.workspace = true

@ -1,18 +1,13 @@
use std::path::Path; use std::{path::Path, process::Stdio, time::Duration};
use std::process::Stdio;
use std::time::Duration;
use tokio::fs::remove_dir_all;
use tokio::process::Command;
use tokio::time::sleep;
use common::*; use common::*;
use tokio::{fs::remove_dir_all, process::Command, time::sleep};
mod common; mod common;
const RAW_DB_PATH: &str = "./agents/scraper/src/db/generated"; const RAW_DB_PATH: &str = "./agents/scraper/src/db/generated";
const DOCKER_NAME: &str = "scraper-entity-generator"; const DOCKER_NAME: &str = "scraper-entity-generator";
const CLI_VERSION: &str = "0.11.1"; const CLI_VERSION: &str = "0.12.3";
struct PostgresDockerContainer; struct PostgresDockerContainer;
@ -58,7 +53,7 @@ impl Drop for PostgresDockerContainer {
} }
} }
#[tokio::main] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), DbErr> { async fn main() -> Result<(), DbErr> {
assert_eq!( assert_eq!(
std::env::current_dir().unwrap().file_name().unwrap(), std::env::current_dir().unwrap().file_name().unwrap(),

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -56,9 +56,7 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::BigInteger.def(), Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(), Self::TimeCreated => ColumnType::DateTime.def(),
Self::Domain => ColumnType::Integer.def(), Self::Domain => ColumnType::Integer.def(),
Self::Hash => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)) Self::Hash => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
.def()
.unique(),
Self::Height => ColumnType::BigInteger.def(), Self::Height => ColumnType::BigInteger.def(),
Self::Timestamp => ColumnType::DateTime.def(), Self::Timestamp => ColumnType::DateTime.def(),
} }

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -55,13 +55,9 @@ impl ColumnTrait for Column {
match self { match self {
Self::Id => ColumnType::BigInteger.def(), Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(), Self::TimeCreated => ColumnType::DateTime.def(),
Self::MsgId => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)) Self::MsgId => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
.def()
.unique(),
Self::Domain => ColumnType::Integer.def(), Self::Domain => ColumnType::Integer.def(),
Self::DestinationMailbox => { Self::DestinationMailbox => ColumnType::Binary(BlobSize::Blob(None)).def(),
ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def()
}
Self::DestinationTxId => ColumnType::BigInteger.def(), Self::DestinationTxId => ColumnType::BigInteger.def(),
} }
} }

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -60,7 +60,7 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::BigInteger.def(), Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(), Self::TimeCreated => ColumnType::DateTime.def(),
Self::Domain => ColumnType::Integer.def(), Self::Domain => ColumnType::Integer.def(),
Self::MsgId => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::MsgId => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Payment => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::Payment => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::GasAmount => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::GasAmount => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::TxId => ColumnType::BigInteger.def(), Self::TxId => ColumnType::BigInteger.def(),

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -65,18 +65,14 @@ impl ColumnTrait for Column {
match self { match self {
Self::Id => ColumnType::BigInteger.def(), Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(), Self::TimeCreated => ColumnType::DateTime.def(),
Self::MsgId => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::MsgId => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Origin => ColumnType::Integer.def(), Self::Origin => ColumnType::Integer.def(),
Self::Destination => ColumnType::Integer.def(), Self::Destination => ColumnType::Integer.def(),
Self::Nonce => ColumnType::Integer.def(), Self::Nonce => ColumnType::Integer.def(),
Self::Sender => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::Sender => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::MsgBody => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)) Self::MsgBody => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
.def() Self::OriginMailbox => ColumnType::Binary(BlobSize::Blob(None)).def(),
.null(),
Self::OriginMailbox => {
ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def()
}
Self::OriginTxId => ColumnType::BigInteger.def(), Self::OriginTxId => ColumnType::BigInteger.def(),
} }
} }

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
pub mod prelude; pub mod prelude;

@ -1,9 +1,8 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
pub use super::block::Entity as Block; pub use super::{
pub use super::cursor::Entity as Cursor; block::Entity as Block, cursor::Entity as Cursor,
pub use super::delivered_message::Entity as DeliveredMessage; delivered_message::Entity as DeliveredMessage, domain::Entity as Domain,
pub use super::domain::Entity as Domain; gas_payment::Entity as GasPayment, message::Entity as Message,
pub use super::gas_payment::Entity as GasPayment; transaction::Entity as Transaction,
pub use super::message::Entity as Message; };
pub use super::transaction::Entity as Transaction;

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@ -73,9 +73,7 @@ impl ColumnTrait for Column {
match self { match self {
Self::Id => ColumnType::BigInteger.def(), Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(), Self::TimeCreated => ColumnType::DateTime.def(),
Self::Hash => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)) Self::Hash => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
.def()
.unique(),
Self::BlockId => ColumnType::BigInteger.def(), Self::BlockId => ColumnType::BigInteger.def(),
Self::GasLimit => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::GasLimit => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::MaxPriorityFeePerGas => ColumnType::Decimal(Some((78u32, 0u32))).def().null(), Self::MaxPriorityFeePerGas => ColumnType::Decimal(Some((78u32, 0u32))).def().null(),
@ -83,10 +81,8 @@ impl ColumnTrait for Column {
Self::GasPrice => ColumnType::Decimal(Some((78u32, 0u32))).def().null(), Self::GasPrice => ColumnType::Decimal(Some((78u32, 0u32))).def().null(),
Self::EffectiveGasPrice => ColumnType::Decimal(Some((78u32, 0u32))).def().null(), Self::EffectiveGasPrice => ColumnType::Decimal(Some((78u32, 0u32))).def().null(),
Self::Nonce => ColumnType::BigInteger.def(), Self::Nonce => ColumnType::BigInteger.def(),
Self::Sender => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::Sender => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)) Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
.def()
.null(),
Self::GasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::GasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::CumulativeGasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::CumulativeGasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(),
} }

@ -2,16 +2,18 @@ use std::num::NonZeroU64;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use ethers::abi::Detokenize; use ethers::{
use ethers::prelude::{NameOrAddress, TransactionReceipt}; abi::Detokenize,
use ethers::types::Eip1559TransactionRequest; prelude::{NameOrAddress, TransactionReceipt},
types::Eip1559TransactionRequest,
};
use ethers_contract::builders::ContractCall; use ethers_contract::builders::ContractCall;
use ethers_core::types::BlockNumber; use ethers_core::types::BlockNumber;
use hyperlane_core::{
utils::fmt_bytes, ChainCommunicationError, ChainResult, KnownHyperlaneDomain, H256, U256,
};
use tracing::{error, info}; use tracing::{error, info};
use hyperlane_core::utils::fmt_bytes;
use hyperlane_core::{ChainCommunicationError, ChainResult, KnownHyperlaneDomain, H256, U256};
use crate::Middleware; use crate::Middleware;
/// An amount of gas to add to the estimated gas /// An amount of gas to add to the estimated gas
@ -87,7 +89,7 @@ where
}; };
let Ok((max_fee, max_priority_fee)) = provider.estimate_eip1559_fees(None).await else { let Ok((max_fee, max_priority_fee)) = provider.estimate_eip1559_fees(None).await else {
// Is not EIP 1559 chain // Is not EIP 1559 chain
return Ok(tx.gas(gas_limit)) return Ok(tx.gas(gas_limit));
}; };
let max_priority_fee = if matches!( let max_priority_fee = if matches!(
KnownHyperlaneDomain::try_from(domain), KnownHyperlaneDomain::try_from(domain),

@ -1,26 +1,25 @@
#![allow(clippy::enum_variant_names)] #![allow(clippy::enum_variant_names)]
#![allow(missing_docs)] #![allow(missing_docs)]
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use ethers::providers::Middleware; use ethers::providers::Middleware;
use ethers_contract::builders::ContractCall; use ethers_contract::builders::ContractCall;
use hyperlane_core::{ use hyperlane_core::{
Announcement, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, Announcement, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256, HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256,
}; };
use tracing::instrument; use tracing::{instrument, log::trace};
use tracing::log::trace;
use crate::contracts::i_validator_announce::{ use crate::{
contracts::i_validator_announce::{
IValidatorAnnounce as EthereumValidatorAnnounceInternal, IVALIDATORANNOUNCE_ABI, IValidatorAnnounce as EthereumValidatorAnnounceInternal, IVALIDATORANNOUNCE_ABI,
},
trait_builder::BuildableWithProvider,
tx::{fill_tx_gas_params, report_tx},
EthereumProvider,
}; };
use crate::trait_builder::BuildableWithProvider;
use crate::tx::{fill_tx_gas_params, report_tx};
use crate::EthereumProvider;
impl<M> std::fmt::Display for EthereumValidatorAnnounceInternal<M> impl<M> std::fmt::Display for EthereumValidatorAnnounceInternal<M>
where where
@ -140,22 +139,17 @@ where
let validator = announcement.value.validator; let validator = announcement.value.validator;
let eth_h160: ethers::types::H160 = validator.into(); let eth_h160: ethers::types::H160 = validator.into();
let Ok(contract_call) = self let Ok(contract_call) = self.announce_contract_call(announcement, None).await else {
.announce_contract_call(announcement, None)
.await
else {
trace!("Unable to get announce contract call"); trace!("Unable to get announce contract call");
return None; return None;
}; };
let Ok(balance) = self.provider.get_balance(eth_h160, None).await let Ok(balance) = self.provider.get_balance(eth_h160, None).await else {
else {
trace!("Unable to query balance"); trace!("Unable to query balance");
return None; return None;
}; };
let Some(max_cost) = contract_call.tx.max_cost() let Some(max_cost) = contract_call.tx.max_cost() else {
else {
trace!("Unable to get announce max cost"); trace!("Unable to get announce max cost");
return None; return None;
}; };

@ -23,7 +23,9 @@ use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
use derive_new::new; use derive_new::new;
/// The offset to get the `unique_gas_payment_pubkey` field from the serialized GasPaymentData /// The offset to get the `unique_gas_payment_pubkey` field from the serialized GasPaymentData.
/// The account data includes prefixes that are accounted for here: a 1 byte initialized flag
/// and an 8 byte discriminator.
const UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET: usize = 1 + 8 + 8 + 32 + 4 + 32 + 8 + 8; const UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET: usize = 1 + 8 + 8 + 32 + 4 + 32 + 8 + 8;
/// A reference to an IGP contract on some Sealevel chain /// A reference to an IGP contract on some Sealevel chain
@ -126,6 +128,7 @@ impl SealevelInterchainGasPaymasterIndexer {
Ok(Self { rpc_client, igp }) Ok(Self { rpc_client, igp })
} }
#[instrument(err, skip(self))]
async fn get_payment_with_sequence( async fn get_payment_with_sequence(
&self, &self,
sequence_number: u64, sequence_number: u64,
@ -163,12 +166,15 @@ impl SealevelInterchainGasPaymasterIndexer {
}, },
with_context: Some(false), with_context: Some(false),
}; };
tracing::debug!(config=?config, "Fetching program accounts");
let accounts = self let accounts = self
.rpc_client .rpc_client
.get_program_accounts_with_config(&self.igp.program_id, config) .get_program_accounts_with_config(&self.igp.program_id, config)
.await .await
.map_err(ChainCommunicationError::from_other)?; .map_err(ChainCommunicationError::from_other)?;
tracing::debug!(accounts=?accounts, "Fetched program accounts");
// Now loop through matching accounts and find the one with a valid account pubkey // Now loop through matching accounts and find the one with a valid account pubkey
// that proves it's an actual gas payment PDA. // that proves it's an actual gas payment PDA.
let mut valid_payment_pda_pubkey = Option::<Pubkey>::None; let mut valid_payment_pda_pubkey = Option::<Pubkey>::None;
@ -208,6 +214,8 @@ impl SealevelInterchainGasPaymasterIndexer {
.map_err(ChainCommunicationError::from_other)? .map_err(ChainCommunicationError::from_other)?
.into_inner(); .into_inner();
tracing::debug!(gas_payment_account=?gas_payment_account, "Found gas payment account");
let igp_payment = InterchainGasPayment { let igp_payment = InterchainGasPayment {
message_id: gas_payment_account.message_id, message_id: gas_payment_account.message_id,
destination: gas_payment_account.destination_domain, destination: gas_payment_account.destination_domain,
@ -251,6 +259,8 @@ impl Indexer<InterchainGasPayment> for SealevelInterchainGasPaymasterIndexer {
let igp_account_filter = self.igp.igp_account; let igp_account_filter = self.igp.igp_account;
if igp_account_filter == sealevel_payment.igp_account_pubkey { if igp_account_filter == sealevel_payment.igp_account_pubkey {
payments.push((sealevel_payment.payment, sealevel_payment.log_meta)); payments.push((sealevel_payment.payment, sealevel_payment.log_meta));
} else {
tracing::debug!(sealevel_payment=?sealevel_payment, igp_account_filter=?igp_account_filter, "Found interchain gas payment for a different IGP account, skipping");
} }
} }
} }

@ -41,7 +41,7 @@ backtrace = { workspace = true, optional = true }
backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true } backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true }
ethers-prometheus = { path = "../ethers-prometheus", features = ["serde"] } ethers-prometheus = { path = "../ethers-prometheus", features = ["serde"] }
hyperlane-core = { path = "../hyperlane-core", features = ["agent"] } hyperlane-core = { path = "../hyperlane-core", features = ["agent", "float"] }
hyperlane-ethereum = { path = "../chains/hyperlane-ethereum" } hyperlane-ethereum = { path = "../chains/hyperlane-ethereum" }
hyperlane-fuel = { path = "../chains/hyperlane-fuel" } hyperlane-fuel = { path = "../chains/hyperlane-fuel" }
hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" } hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" }

@ -1,7 +1,7 @@
use std::cmp::Ordering;
use std::fmt::Debug;
use std::ops::RangeInclusive;
use std::{ use std::{
cmp::Ordering,
fmt::Debug,
ops::RangeInclusive,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -9,14 +9,13 @@ use std::{
use async_trait::async_trait; use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use eyre::Result; use eyre::Result;
use tokio::time::sleep;
use tracing::{debug, warn};
use hyperlane_core::{ use hyperlane_core::{
ChainCommunicationError, ChainResult, ContractSyncCursor, CursorAction, HyperlaneMessage, ChainCommunicationError, ChainResult, ContractSyncCursor, CursorAction, HyperlaneMessage,
HyperlaneMessageStore, HyperlaneWatermarkedLogStore, IndexMode, Indexer, LogMeta, HyperlaneMessageStore, HyperlaneWatermarkedLogStore, IndexMode, Indexer, LogMeta,
SequenceIndexer, SequenceIndexer,
}; };
use tokio::time::sleep;
use tracing::{debug, warn};
use crate::contract_sync::eta_calculator::SyncerEtaCalculator; use crate::contract_sync::eta_calculator::SyncerEtaCalculator;
@ -67,7 +66,7 @@ impl SyncState {
"Sequence indexing requires a max sequence", "Sequence indexing requires a max sequence",
) )
})?; })?;
if let Some(range) = self.sequence_range(tip, max_sequence)? { if let Some(range) = self.sequence_range(max_sequence)? {
range range
} else { } else {
return Ok(None); return Ok(None);
@ -107,11 +106,7 @@ impl SyncState {
/// * `max_sequence` - The maximum sequence that should be indexed. /// * `max_sequence` - The maximum sequence that should be indexed.
/// `max_sequence` is the exclusive upper bound of the range to be indexed. /// `max_sequence` is the exclusive upper bound of the range to be indexed.
/// (e.g. `0..max_sequence`) /// (e.g. `0..max_sequence`)
fn sequence_range( fn sequence_range(&mut self, max_sequence: u32) -> ChainResult<Option<RangeInclusive<u32>>> {
&mut self,
tip: u32,
max_sequence: u32,
) -> ChainResult<Option<RangeInclusive<u32>>> {
let (from, to) = match self.direction { let (from, to) = match self.direction {
SyncDirection::Forward => { SyncDirection::Forward => {
let sequence_start = self.next_sequence; let sequence_start = self.next_sequence;
@ -120,7 +115,6 @@ impl SyncState {
return Ok(None); return Ok(None);
} }
sequence_end = u32::min(sequence_end, max_sequence.saturating_sub(1)); sequence_end = u32::min(sequence_end, max_sequence.saturating_sub(1));
self.next_block = tip;
self.next_sequence = sequence_end + 1; self.next_sequence = sequence_end + 1;
(sequence_start, sequence_end) (sequence_start, sequence_end)
} }
@ -236,8 +230,7 @@ impl ForwardMessageSyncCursor {
self.cursor.sync_state.next_sequence += 1; self.cursor.sync_state.next_sequence += 1;
} }
let (Some(mailbox_count), tip) = self.cursor.indexer.sequence_and_tip().await? let (Some(mailbox_count), tip) = self.cursor.indexer.sequence_and_tip().await? else {
else {
return Ok(None); return Ok(None);
}; };
let cursor_count = self.cursor.sync_state.next_sequence; let cursor_count = self.cursor.sync_state.next_sequence;
@ -476,6 +469,7 @@ pub(crate) struct RateLimitedContractSyncCursor<T> {
indexer: Arc<dyn SequenceIndexer<T>>, indexer: Arc<dyn SequenceIndexer<T>>,
db: Arc<dyn HyperlaneWatermarkedLogStore<T>>, db: Arc<dyn HyperlaneWatermarkedLogStore<T>>,
tip: u32, tip: u32,
max_sequence: Option<u32>,
last_tip_update: Instant, last_tip_update: Instant,
eta_calculator: SyncerEtaCalculator, eta_calculator: SyncerEtaCalculator,
sync_state: SyncState, sync_state: SyncState,
@ -490,11 +484,12 @@ impl<T> RateLimitedContractSyncCursor<T> {
initial_height: u32, initial_height: u32,
mode: IndexMode, mode: IndexMode,
) -> Result<Self> { ) -> Result<Self> {
let tip = indexer.get_finalized_block_number().await?; let (max_sequence, tip) = indexer.sequence_and_tip().await?;
Ok(Self { Ok(Self {
indexer, indexer,
db, db,
tip, tip,
max_sequence,
last_tip_update: Instant::now(), last_tip_update: Instant::now(),
eta_calculator: SyncerEtaCalculator::new(initial_height, tip, ETA_TIME_WINDOW), eta_calculator: SyncerEtaCalculator::new(initial_height, tip, ETA_TIME_WINDOW),
sync_state: SyncState::new( sync_state: SyncState::new(
@ -539,6 +534,32 @@ impl<T> RateLimitedContractSyncCursor<T> {
} }
} }
} }
fn sync_end(&self) -> ChainResult<u32> {
match self.sync_state.mode {
IndexMode::Block => Ok(self.tip),
IndexMode::Sequence => {
self.max_sequence
.ok_or(ChainCommunicationError::from_other_str(
"Sequence indexing requires a max sequence",
))
}
}
}
fn sync_position(&self) -> u32 {
match self.sync_state.mode {
IndexMode::Block => self.sync_state.next_block,
IndexMode::Sequence => self.sync_state.next_sequence,
}
}
fn sync_step(&self) -> u32 {
match self.sync_state.mode {
IndexMode::Block => self.sync_state.chunk_size,
IndexMode::Sequence => MAX_SEQUENCE_RANGE,
}
}
} }
#[async_trait] #[async_trait]
@ -547,13 +568,11 @@ where
T: Send + Debug + 'static, T: Send + Debug + 'static,
{ {
async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)> { async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)> {
let to = u32::min( let sync_end = self.sync_end()?;
self.tip, let to = u32::min(sync_end, self.sync_position() + self.sync_step());
self.sync_state.next_block + self.sync_state.chunk_size, let from = self.sync_position();
); let eta = if to < sync_end {
let from = to.saturating_sub(self.sync_state.chunk_size); self.eta_calculator.calculate(from, sync_end)
let eta = if to < self.tip {
self.eta_calculator.calculate(from, self.tip)
} else { } else {
Duration::from_secs(0) Duration::from_secs(0)
}; };
@ -562,8 +581,10 @@ where
if let Some(rate_limit) = rate_limit { if let Some(rate_limit) = rate_limit {
return Ok((CursorAction::Sleep(rate_limit), eta)); return Ok((CursorAction::Sleep(rate_limit), eta));
} }
let (count, tip) = self.indexer.sequence_and_tip().await?; let (max_sequence, tip) = self.indexer.sequence_and_tip().await?;
if let Some(range) = self.sync_state.get_next_range(count, tip).await? { self.tip = tip;
self.max_sequence = max_sequence;
if let Some(range) = self.sync_state.get_next_range(max_sequence, tip).await? {
return Ok((CursorAction::Query(range), eta)); return Ok((CursorAction::Query(range), eta));
} }

@ -1,7 +1,7 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use derive_new::new; use derive_new::new;
use tracing::debug; use tracing::warn;
/// Calculates the expected time to catch up to the tip of the blockchain. /// Calculates the expected time to catch up to the tip of the blockchain.
#[derive(new)] #[derive(new)]
@ -38,10 +38,13 @@ impl SyncerEtaCalculator {
self.last_block = current_block; self.last_block = current_block;
self.last_tip = current_tip; self.last_tip = current_tip;
// The block-processing rate, minus the tip-progression rate, measured in
// blocks per second.
let new_rate = (blocks_processed - tip_progression) / elapsed; let new_rate = (blocks_processed - tip_progression) / elapsed;
// Calculate the effective rate using a moving average. Only set the past // Calculate the effective rate using a moving average. Only set the past
// effective rate once we have seen a move to prevent it taking a long // effective rate once we have seen a move, to prevent it taking a long
// time to normalize. // time to normalize.
let effective_rate = if let Some(old_rate) = self.effective_rate { let effective_rate = if let Some(old_rate) = self.effective_rate {
let new_coeff = f64::min(elapsed / self.time_window, 0.9); let new_coeff = f64::min(elapsed / self.time_window, 0.9);
@ -66,7 +69,7 @@ impl SyncerEtaCalculator {
{ {
Ok(eta) => eta, Ok(eta) => eta,
Err(e) => { Err(e) => {
debug!(error=?e, tip=?current_tip, block=?current_block, rate=?effective_rate, "Failed to calculate the eta"); warn!(error=?e, tip=?current_tip, block=?current_block, rate=?effective_rate, "Failed to calculate the eta");
default_duration default_duration
} }
} }

@ -59,7 +59,9 @@ where
loop { loop {
indexed_height.set(cursor.latest_block() as i64); indexed_height.set(cursor.latest_block() as i64);
let Ok((action, eta)) = cursor.next_action().await else { continue }; let Ok((action, eta)) = cursor.next_action().await else {
continue;
};
match action { match action {
CursorAction::Query(range) => { CursorAction::Query(range) => {
debug!(?range, "Looking for for events in index range"); debug!(?range, "Looking for for events in index range");

@ -36,14 +36,14 @@ fn recase_pair(key: String, mut val: Value, case: Case) -> (String, Value) {
.drain() .drain()
.map(|(k, v)| recase_pair(k, v, case)) .map(|(k, v)| recase_pair(k, v, case))
.collect_vec(); .collect_vec();
table.extend(tmp.into_iter()); table.extend(tmp);
} }
ValueKind::Array(ary) => { ValueKind::Array(ary) => {
let tmp = ary let tmp = ary
.drain(..) .drain(..)
.map(|v| recase_pair(String::new(), v, case).1) .map(|v| recase_pair(String::new(), v, case).1)
.collect_vec(); .collect_vec();
ary.extend(tmp.into_iter()) ary.extend(tmp)
} }
_ => {} _ => {}
} }

@ -2,9 +2,8 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use eyre::{Context, Result}; use eyre::{Context, Result};
use prometheus::IntGauge;
use hyperlane_core::{SignedAnnouncement, SignedCheckpoint, SignedCheckpointWithMessageId}; use hyperlane_core::{SignedAnnouncement, SignedCheckpoint, SignedCheckpointWithMessageId};
use prometheus::IntGauge;
use crate::traits::CheckpointSyncer; use crate::traits::CheckpointSyncer;
@ -87,7 +86,7 @@ impl CheckpointSyncer for LocalStorage {
async fn fetch_checkpoint(&self, index: u32) -> Result<Option<SignedCheckpointWithMessageId>> { async fn fetch_checkpoint(&self, index: u32) -> Result<Option<SignedCheckpointWithMessageId>> {
let Ok(data) = tokio::fs::read(self.checkpoint_file_path(index)).await else { let Ok(data) = tokio::fs::read(self.checkpoint_file_path(index)).await else {
return Ok(None) return Ok(None);
}; };
let checkpoint = serde_json::from_slice(&data)?; let checkpoint = serde_json::from_slice(&data)?;
Ok(Some(checkpoint)) Ok(Some(checkpoint))

@ -355,6 +355,7 @@ impl MultisigCheckpointSyncer {
continue; continue;
} }
} }
debug!("No quorum checkpoint found for message");
Ok(None) Ok(None)
} }
} }

@ -45,6 +45,7 @@ tokio = { workspace = true, features = ["rt", "time"] }
[features] [features]
default = [] default = []
float = []
test-utils = ["dep:config"] test-utils = ["dep:config"]
agent = ["ethers", "strum"] agent = ["ethers", "strum"]
strum = ["dep:strum"] strum = ["dep:strum"]

@ -38,7 +38,7 @@ pub trait Indexer<T: Sized>: Send + Sync + Debug {
/// Interface for indexing data in sequence. /// Interface for indexing data in sequence.
#[async_trait] #[async_trait]
#[auto_impl(&, Box, Arc)] #[auto_impl(&, Box, Arc)]
pub trait SequenceIndexer<T>: Indexer<T> + 'static { pub trait SequenceIndexer<T>: Indexer<T> {
/// Return the latest finalized sequence (if any) and block number /// Return the latest finalized sequence (if any) and block number
async fn sequence_and_tip(&self) -> ChainResult<(Option<u32>, u32)>; async fn sequence_and_tip(&self) -> ChainResult<(Option<u32>, u32)>;
} }

@ -4,12 +4,23 @@ use async_trait::async_trait;
use auto_impl::auto_impl; use auto_impl::auto_impl;
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use serde::{Deserialize, Serialize};
use crate::{ChainResult, HyperlaneContract, HyperlaneMessage, U256}; use crate::{ChainResult, HyperlaneContract, HyperlaneMessage, U256};
/// Enumeration of all known module types /// Enumeration of all known module types
#[derive( #[derive(
FromPrimitive, Clone, Debug, Default, Copy, PartialEq, Eq, BorshDeserialize, BorshSerialize, FromPrimitive,
Clone,
Debug,
Default,
Copy,
PartialEq,
Eq,
BorshDeserialize,
BorshSerialize,
Serialize,
Deserialize,
)] )]
#[cfg_attr(feature = "strum", derive(strum::Display))] #[cfg_attr(feature = "strum", derive(strum::Display))]
pub enum ModuleType { pub enum ModuleType {

@ -3,11 +3,12 @@
#![allow(clippy::assign_op_pattern)] #![allow(clippy::assign_op_pattern)]
#![allow(clippy::reversed_empty_ranges)] #![allow(clippy::reversed_empty_ranges)]
use crate::types::serialize;
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use fixed_hash::{construct_fixed_hash, impl_fixed_hash_conversions}; use fixed_hash::impl_fixed_hash_conversions;
use uint::construct_uint; use uint::construct_uint;
use crate::types::serialize;
/// Error type for conversion. /// Error type for conversion.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
@ -32,6 +33,13 @@ construct_uint! {
pub struct U512(8); pub struct U512(8);
} }
mod fixed_hashes {
// we can't change how they made the macro, so ignore the lint
#![allow(clippy::incorrect_clone_impl_on_copy_type)]
use borsh::{BorshDeserialize, BorshSerialize};
use fixed_hash::construct_fixed_hash;
construct_fixed_hash! { construct_fixed_hash! {
/// 128-bit hash type. /// 128-bit hash type.
#[derive(BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
@ -55,6 +63,8 @@ construct_fixed_hash! {
#[derive(BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
pub struct H512(64); pub struct H512(64);
} }
}
pub use fixed_hashes::*;
#[cfg(feature = "ethers")] #[cfg(feature = "ethers")]
type EthersH160 = ethers_core::types::H160; type EthersH160 = ethers_core::types::H160;
@ -143,6 +153,7 @@ impl_fixed_uint_conversions!(ethers_core::types::U512, U256);
#[cfg(feature = "ethers")] #[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(ethers_core::types::U512, U128); impl_fixed_uint_conversions!(ethers_core::types::U512, U128);
#[cfg(feature = "float")]
macro_rules! impl_f64_conversions { macro_rules! impl_f64_conversions {
($ty:ty) => { ($ty:ty) => {
impl $ty { impl $ty {
@ -190,8 +201,11 @@ macro_rules! impl_f64_conversions {
}; };
} }
#[cfg(feature = "float")]
impl_f64_conversions!(U128); impl_f64_conversions!(U128);
#[cfg(feature = "float")]
impl_f64_conversions!(U256); impl_f64_conversions!(U256);
#[cfg(feature = "float")]
impl_f64_conversions!(U512); impl_f64_conversions!(U512);
#[cfg(feature = "ethers")] #[cfg(feature = "ethers")]

@ -75,6 +75,7 @@ pub fn fmt_domain(domain: u32) -> String {
} }
/// Formats the duration in the most appropriate time units. /// Formats the duration in the most appropriate time units.
#[cfg(feature = "float")]
pub fn fmt_duration(dur: Duration) -> String { pub fn fmt_duration(dur: Duration) -> String {
const MIN: f64 = 60.; const MIN: f64 = 60.;
const HOUR: f64 = MIN * 60.; const HOUR: f64 = MIN * 60.;
@ -97,6 +98,7 @@ pub fn fmt_duration(dur: Duration) -> String {
/// Formats the duration in the most appropriate time units and says "synced" if /// Formats the duration in the most appropriate time units and says "synced" if
/// the duration is 0. /// the duration is 0.
#[cfg(feature = "float")]
pub fn fmt_sync_time(dur: Duration) -> String { pub fn fmt_sync_time(dur: Duration) -> String {
if dur.as_secs() == 0 { if dur.as_secs() == 0 {
"synced".into() "synced".into()

@ -32,3 +32,4 @@ hyperlane-sealevel-token-collateral = { path = "../programs/hyperlane-sealevel-t
hyperlane-sealevel-token-lib = { path = "../libraries/hyperlane-sealevel-token" } hyperlane-sealevel-token-lib = { path = "../libraries/hyperlane-sealevel-token" }
hyperlane-sealevel-token-native = { path = "../programs/hyperlane-sealevel-token-native", features = ["no-entrypoint"] } hyperlane-sealevel-token-native = { path = "../programs/hyperlane-sealevel-token-native", features = ["no-entrypoint"] }
hyperlane-sealevel-validator-announce = { path = "../programs/validator-announce", features = ["no-entrypoint"] } hyperlane-sealevel-validator-announce = { path = "../programs/validator-announce", features = ["no-entrypoint"] }
hyperlane-sealevel-hello-world = { path = "../programs/helloworld" }

@ -0,0 +1,59 @@
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use hyperlane_core::H256;
use solana_program::pubkey::Pubkey;
use std::{fs::File, io::Write, path::Path, str::FromStr};
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct SingularProgramIdArtifact {
#[serde(with = "crate::serde::serde_pubkey")]
pub program_id: Pubkey,
}
impl From<Pubkey> for SingularProgramIdArtifact {
fn from(val: Pubkey) -> Self {
SingularProgramIdArtifact { program_id: val }
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct HexAndBase58ProgramIdArtifact {
hex: String,
base58: String,
}
impl From<H256> for HexAndBase58ProgramIdArtifact {
fn from(val: H256) -> Self {
HexAndBase58ProgramIdArtifact {
hex: format!("0x{}", hex::encode(val)),
base58: Pubkey::new_from_array(val.to_fixed_bytes()).to_string(),
}
}
}
impl From<&HexAndBase58ProgramIdArtifact> for Pubkey {
fn from(val: &HexAndBase58ProgramIdArtifact) -> Self {
Pubkey::from_str(&val.base58).unwrap()
}
}
pub(crate) fn write_json<T>(path: &Path, program_id: T)
where
T: Serialize,
{
let json = serde_json::to_string_pretty(&program_id).unwrap();
println!("Writing to file {} contents:\n{}", path.display(), json);
let mut file = File::create(path).expect("Failed to create file");
file.write_all(json.as_bytes())
.expect("Failed write JSON to file");
}
pub(crate) fn read_json<T>(path: &Path) -> T
where
T: DeserializeOwned,
{
let file = File::open(path).expect("Failed to open JSON file");
serde_json::from_reader(file).expect("Failed to read JSON file")
}

@ -26,6 +26,7 @@ pub(crate) struct Context {
payer_keypair: Option<PayerKeypair>, payer_keypair: Option<PayerKeypair>,
pub commitment: CommitmentConfig, pub commitment: CommitmentConfig,
pub initial_instructions: RefCell<Vec<InstructionWithDescription>>, pub initial_instructions: RefCell<Vec<InstructionWithDescription>>,
pub require_tx_approval: bool,
} }
pub(crate) struct InstructionWithDescription { pub(crate) struct InstructionWithDescription {
@ -58,6 +59,7 @@ impl Context {
payer_keypair: Option<PayerKeypair>, payer_keypair: Option<PayerKeypair>,
commitment: CommitmentConfig, commitment: CommitmentConfig,
initial_instructions: RefCell<Vec<InstructionWithDescription>>, initial_instructions: RefCell<Vec<InstructionWithDescription>>,
require_tx_approval: bool,
) -> Self { ) -> Self {
Self { Self {
client, client,
@ -65,6 +67,7 @@ impl Context {
payer_keypair, payer_keypair,
commitment, commitment,
initial_instructions, initial_instructions,
require_tx_approval,
} }
} }
@ -183,6 +186,13 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
return None; return None;
} }
// Print the tx as an indication for what's about to happen
self.pretty_print_transaction();
if self.ctx.require_tx_approval {
wait_for_user_confirmation();
}
let client = self.client.unwrap_or(&self.ctx.client); let client = self.client.unwrap_or(&self.ctx.client);
let recent_blockhash = client.get_latest_blockhash().unwrap(); let recent_blockhash = client.get_latest_blockhash().unwrap();

@ -4,10 +4,12 @@ use solana_program::pubkey::Pubkey;
use solana_sdk::signature::Signer; use solana_sdk::signature::Signer;
use std::collections::HashMap; use std::collections::HashMap;
use std::{fs::File, io::Write, path::Path}; use std::{fs::File, path::Path};
use crate::{ use crate::{
artifacts::{read_json, write_json},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program}, cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program},
multisig_ism::deploy_multisig_ism_message_id,
Context, CoreCmd, CoreDeploy, CoreSubCmd, Context, CoreCmd, CoreDeploy, CoreSubCmd,
}; };
use hyperlane_core::H256; use hyperlane_core::H256;
@ -21,7 +23,12 @@ pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) {
let core_dir = create_new_directory(&chain_dir, "core"); let core_dir = create_new_directory(&chain_dir, "core");
let key_dir = create_new_directory(&core_dir, "keys"); let key_dir = create_new_directory(&core_dir, "keys");
let ism_program_id = deploy_multisig_ism_message_id(&mut ctx, &core, &key_dir); let ism_program_id = deploy_multisig_ism_message_id(
&mut ctx,
&core.built_so_dir,
core.use_existing_keys,
&key_dir,
);
let mailbox_program_id = deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id); let mailbox_program_id = deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id);
@ -44,43 +51,6 @@ pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) {
} }
} }
fn deploy_multisig_ism_message_id(ctx: &mut Context, cmd: &CoreDeploy, key_dir: &Path) -> Pubkey {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
"hyperlane_sealevel_multisig_ism_message_id-keypair.json",
cmd.use_existing_keys,
);
let program_id = keypair.pubkey();
deploy_program(
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
cmd.built_so_dir
.join("hyperlane_sealevel_multisig_ism_message_id.so")
.to_str()
.unwrap(),
&ctx.client.url(),
);
println!(
"Deployed Multisig ISM Message ID at program ID {}",
program_id
);
// Initialize
let instruction = hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!("Initialized Multisig ISM Message ID ");
program_id
}
fn deploy_mailbox( fn deploy_mailbox(
ctx: &mut Context, ctx: &mut Context,
core: &CoreDeploy, core: &CoreDeploy,
@ -325,55 +295,23 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub(crate) struct CoreProgramIds { pub struct CoreProgramIds {
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub mailbox: Pubkey, pub mailbox: Pubkey,
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub validator_announce: Pubkey, pub validator_announce: Pubkey,
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub multisig_ism_message_id: Pubkey, pub multisig_ism_message_id: Pubkey,
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub igp_program_id: Pubkey, pub igp_program_id: Pubkey,
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub overhead_igp_account: Pubkey, pub overhead_igp_account: Pubkey,
#[serde(with = "serde_pubkey")] #[serde(with = "crate::serde::serde_pubkey")]
pub igp_account: Pubkey, pub igp_account: Pubkey,
} }
mod serde_pubkey {
use borsh::BorshDeserialize;
use serde::{Deserialize, Deserializer, Serializer};
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Deserialize)]
#[serde(untagged)]
enum RawPubkey {
String(String),
Bytes(Vec<u8>),
}
pub fn serialize<S: Serializer>(k: &Pubkey, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&k.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Pubkey, D::Error> {
match RawPubkey::deserialize(de)? {
RawPubkey::String(s) => Pubkey::from_str(&s).map_err(serde::de::Error::custom),
RawPubkey::Bytes(b) => Pubkey::try_from_slice(&b).map_err(serde::de::Error::custom),
}
}
}
fn write_program_ids(core_dir: &Path, program_ids: CoreProgramIds) { fn write_program_ids(core_dir: &Path, program_ids: CoreProgramIds) {
let json = serde_json::to_string_pretty(&program_ids).unwrap(); write_json(&core_dir.join("program-ids.json"), program_ids);
let path = core_dir.join("program-ids.json");
println!("Writing program IDs to {}:\n{}", path.display(), json);
let mut file = File::create(path).expect("Failed to create keypair file");
file.write_all(json.as_bytes())
.expect("Failed to write program IDs to file");
} }
pub(crate) fn read_core_program_ids( pub(crate) fn read_core_program_ids(
@ -386,6 +324,5 @@ pub(crate) fn read_core_program_ids(
.join(chain) .join(chain)
.join("core") .join("core")
.join("program-ids.json"); .join("program-ids.json");
let file = File::open(path).expect("Failed to open program IDs file"); read_json(&path)
serde_json::from_reader(file).expect("Failed to read program IDs file")
} }

@ -0,0 +1,197 @@
use std::collections::HashMap;
use hyperlane_core::H256;
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_hello_world::{
accounts::{HelloWorldStorage, HelloWorldStorageAccount},
instruction::{
enroll_remote_routers_instruction, init_instruction,
set_interchain_security_module_instruction,
},
program_storage_pda_seeds,
};
use serde::{Deserialize, Serialize};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
use crate::{
cmd_utils::account_exists,
router::{
deploy_routers, ChainMetadata, ConnectionClient, Ownable, RouterConfig, RouterConfigGetter,
RouterDeployer,
},
Context, CoreProgramIds, HelloWorldCmd, HelloWorldDeploy, HelloWorldSubCmd, RpcClient,
};
pub(crate) fn process_helloworld_cmd(mut ctx: Context, cmd: HelloWorldCmd) {
match cmd.cmd {
HelloWorldSubCmd::Deploy(deploy) => {
deploy_helloworld(&mut ctx, deploy);
}
HelloWorldSubCmd::Query(query) => {
let program_storage_key =
Pubkey::find_program_address(program_storage_pda_seeds!(), &query.program_id);
let account = ctx.client.get_account(&program_storage_key.0).unwrap();
let storage = HelloWorldStorageAccount::fetch(&mut &account.data[..])
.unwrap()
.into_inner();
println!("HelloWorld storage: {:?}", storage);
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
struct HelloWorldConfig {
#[serde(flatten)]
router_config: RouterConfig,
}
struct HelloWorldDeployer {}
impl HelloWorldDeployer {
fn new() -> Self {
Self {}
}
fn get_storage(&self, client: &RpcClient, program_id: &Pubkey) -> HelloWorldStorage {
let (program_storage_account, _program_storage_bump) =
Pubkey::find_program_address(program_storage_pda_seeds!(), program_id);
let account = client.get_account(&program_storage_account).unwrap();
*HelloWorldStorageAccount::fetch(&mut &account.data[..])
.unwrap()
.into_inner()
}
}
impl RouterDeployer<HelloWorldConfig> for HelloWorldDeployer {
fn program_name(&self, _config: &HelloWorldConfig) -> &str {
"hyperlane_sealevel_hello_world"
}
fn enroll_remote_routers_instruction(
&self,
program_id: Pubkey,
payer: Pubkey,
router_configs: Vec<RemoteRouterConfig>,
) -> Instruction {
enroll_remote_routers_instruction(program_id, payer, router_configs).unwrap()
}
fn get_routers(&self, client: &RpcClient, program_id: &Pubkey) -> HashMap<u32, H256> {
let storage = self.get_storage(client, program_id);
storage.routers
}
fn init_program_idempotent(
&self,
ctx: &mut Context,
client: &RpcClient,
core_program_ids: &CoreProgramIds,
chain_config: &ChainMetadata,
app_config: &HelloWorldConfig,
program_id: Pubkey,
) {
let (program_storage_account, _program_storage_bump) =
Pubkey::find_program_address(program_storage_pda_seeds!(), &program_id);
if account_exists(client, &program_storage_account).unwrap() {
println!("HelloWorld storage already exists, skipping init");
return;
}
let domain_id = chain_config.domain_id();
let mailbox = app_config
.router_config()
.connection_client
.mailbox(core_program_ids.mailbox);
let ism = app_config
.router_config()
.connection_client
.interchain_security_module();
let owner = Some(app_config.router_config().ownable.owner(ctx.payer_pubkey));
ctx.new_txn()
.add_with_description(
init_instruction(
program_id,
ctx.payer_pubkey,
domain_id,
mailbox,
ism,
// TODO revisit this when we want to deploy with IGPs
None,
owner,
)
.unwrap(),
format!(
"Initializing HelloWorld program: domain_id: {}, mailbox: {}, ism: {:?}, owner: {:?}",
domain_id, mailbox, ism, owner
)
)
.with_client(client)
.send_with_payer();
}
}
impl RouterConfigGetter for HelloWorldConfig {
fn router_config(&self) -> &RouterConfig {
&self.router_config
}
}
impl Ownable for HelloWorldDeployer {
/// Gets the owner configured on-chain.
fn get_owner(&self, client: &RpcClient, program_id: &Pubkey) -> Option<Pubkey> {
let storage = self.get_storage(client, program_id);
storage.owner
}
/// Gets an instruction to set the owner.
fn set_owner_instruction(
&self,
_client: &RpcClient,
_program_id: &Pubkey,
_new_owner: Option<Pubkey>,
) -> Instruction {
unimplemented!("HelloWorld does not support changing the owner")
}
}
impl ConnectionClient for HelloWorldDeployer {
fn get_interchain_security_module(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<Pubkey> {
let storage = self.get_storage(client, program_id);
storage.ism
}
fn set_interchain_security_module_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
ism: Option<Pubkey>,
) -> Instruction {
let storage = self.get_storage(client, program_id);
set_interchain_security_module_instruction(*program_id, storage.owner.unwrap(), ism)
.unwrap()
}
}
fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) {
deploy_routers(
ctx,
HelloWorldDeployer::new(),
"helloworld",
&deploy.context,
deploy.config_file,
deploy.chain_config_file,
deploy.environments_dir,
&deploy.environment,
deploy.built_so_dir,
)
}

@ -20,32 +20,26 @@ use solana_sdk::{
}; };
use account_utils::DiscriminatorEncode; use account_utils::DiscriminatorEncode;
use hyperlane_core::{Encode, HyperlaneMessage, H160, H256}; use hyperlane_core::{H160, H256};
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::{ use hyperlane_sealevel_igp::{
accounts::{ accounts::{
GasOracle, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, RemoteGasData, GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount,
ProgramDataAccount as IgpProgramDataAccount, RemoteGasData,
}, },
igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds,
instruction::{GasOracleConfig, GasOverheadConfig}, instruction::{GasOracleConfig, GasOverheadConfig},
}; };
use hyperlane_sealevel_mailbox::{ use hyperlane_sealevel_mailbox::{
accounts::{InboxAccount, OutboxAccount}, accounts::{InboxAccount, OutboxAccount},
instruction::{InboxProcess, Instruction as MailboxInstruction, OutboxDispatch, VERSION}, instruction::{Instruction as MailboxInstruction, OutboxDispatch},
mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds,
mailbox_message_dispatch_authority_pda_seeds, mailbox_outbox_pda_seeds, mailbox_message_dispatch_authority_pda_seeds, mailbox_outbox_pda_seeds,
mailbox_processed_message_pda_seeds, spl_noop, mailbox_processed_message_pda_seeds, spl_noop,
}; };
use hyperlane_sealevel_multisig_ism_message_id::{
access_control_pda_seeds as multisig_ism_message_id_access_control_pda_seeds,
accounts::AccessControlAccount,
domain_data_pda_seeds as multisig_ism_message_id_domain_data_pda_seeds,
instruction::{
Domained, Instruction as MultisigIsmMessageIdInstruction, ValidatorsAndThreshold,
},
};
use hyperlane_sealevel_token::{ use hyperlane_sealevel_token::{
hyperlane_token_ata_payer_pda_seeds, hyperlane_token_mint_pda_seeds, plugin::SyntheticPlugin, hyperlane_token_ata_payer_pda_seeds, hyperlane_token_mint_pda_seeds,
spl_associated_token_account::get_associated_token_address_with_program_id, spl_token_2022, spl_associated_token_account::get_associated_token_address_with_program_id, spl_token_2022,
}; };
use hyperlane_sealevel_token_collateral::{ use hyperlane_sealevel_token_collateral::{
@ -56,9 +50,7 @@ use hyperlane_sealevel_token_lib::{
hyperlane_token_pda_seeds, hyperlane_token_pda_seeds,
instruction::{Instruction as HtInstruction, TransferRemote as HtTransferRemote}, instruction::{Instruction as HtInstruction, TransferRemote as HtTransferRemote},
}; };
use hyperlane_sealevel_token_native::{ use hyperlane_sealevel_token_native::hyperlane_token_native_collateral_pda_seeds;
hyperlane_token_native_collateral_pda_seeds, plugin::NativePlugin,
};
use hyperlane_sealevel_validator_announce::{ use hyperlane_sealevel_validator_announce::{
accounts::ValidatorStorageLocationsAccount, accounts::ValidatorStorageLocationsAccount,
instruction::{ instruction::{
@ -68,15 +60,23 @@ use hyperlane_sealevel_validator_announce::{
replay_protection_pda_seeds, validator_announce_pda_seeds, replay_protection_pda_seeds, validator_announce_pda_seeds,
validator_storage_locations_pda_seeds, validator_storage_locations_pda_seeds,
}; };
use warp_route::parse_token_account_data;
use crate::warp_route::process_warp_route_cmd; mod artifacts;
pub(crate) use crate::{context::*, core::*};
mod cmd_utils; mod cmd_utils;
mod context; mod context;
mod r#core; mod r#core;
mod helloworld;
mod multisig_ism;
mod router;
mod serde;
mod warp_route; mod warp_route;
use crate::helloworld::process_helloworld_cmd;
use crate::multisig_ism::process_multisig_ism_message_id_cmd;
use crate::warp_route::process_warp_route_cmd;
pub(crate) use crate::{context::*, core::*};
// Note: from solana_program_runtime::compute_budget // Note: from solana_program_runtime::compute_budget
const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
@ -99,6 +99,8 @@ struct Cli {
heap_size: Option<u32>, heap_size: Option<u32>,
#[arg(long, short = 'C')] #[arg(long, short = 'C')]
config: Option<String>, config: Option<String>,
#[arg(long, default_value_t = false)]
require_tx_approval: bool,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -110,6 +112,7 @@ enum HyperlaneSealevelCmd {
ValidatorAnnounce(ValidatorAnnounceCmd), ValidatorAnnounce(ValidatorAnnounceCmd),
MultisigIsmMessageId(MultisigIsmMessageIdCmd), MultisigIsmMessageId(MultisigIsmMessageIdCmd),
WarpRoute(WarpRouteCmd), WarpRoute(WarpRouteCmd),
HelloWorld(HelloWorldCmd),
} }
#[derive(Args)] #[derive(Args)]
@ -194,9 +197,9 @@ enum MailboxSubCmd {
Init(Init), Init(Init),
Query(Query), Query(Query),
Send(Outbox), Send(Outbox),
Receive(Inbox),
Delivered(Delivered), Delivered(Delivered),
TransferOwnership(TransferOwnership), TransferOwnership(TransferOwnership),
SetDefaultIsm(SetDefaultIsm),
} }
const MAILBOX_PROG_ID: Pubkey = pubkey!("692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1"); const MAILBOX_PROG_ID: Pubkey = pubkey!("692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1");
@ -221,6 +224,14 @@ struct Query {
program_id: Pubkey, program_id: Pubkey,
} }
#[derive(Args)]
struct SetDefaultIsm {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, short)]
default_ism: Pubkey,
}
#[derive(Args)] #[derive(Args)]
struct Outbox { struct Outbox {
#[arg(long, short, default_value_t = ECLIPSE_DOMAIN)] #[arg(long, short, default_value_t = ECLIPSE_DOMAIN)]
@ -259,32 +270,6 @@ struct Delivered {
message_id: H256, message_id: H256,
} }
// Actual content depends on which ISM is used.
struct ExampleMetadata {
pub root: H256,
pub index: u32,
pub leaf_index: u32,
// pub proof: [H256; 32],
pub signatures: Vec<H256>,
}
impl Encode for ExampleMetadata {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where
W: std::io::Write,
{
writer.write_all(self.root.as_ref())?;
writer.write_all(&self.index.to_be_bytes())?;
writer.write_all(&self.leaf_index.to_be_bytes())?;
// for hash in self.proof {
// writer.write_all(hash.as_ref())?;
// }
for signature in &self.signatures {
writer.write_all(signature.as_ref())?;
}
Ok(32 + 4 + 4 + (32 * 32) + (self.signatures.len() * 32))
}
}
#[derive(Args)] #[derive(Args)]
struct TokenCmd { struct TokenCmd {
#[command(subcommand)] #[command(subcommand)]
@ -297,10 +282,12 @@ enum TokenSubCmd {
TransferRemote(TokenTransferRemote), TransferRemote(TokenTransferRemote),
EnrollRemoteRouter(TokenEnrollRemoteRouter), EnrollRemoteRouter(TokenEnrollRemoteRouter),
TransferOwnership(TransferOwnership), TransferOwnership(TransferOwnership),
SetInterchainSecurityModule(SetInterchainSecurityModule),
Igp(Igp),
} }
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum TokenType { pub enum TokenType {
Native, Native,
Synthetic, Synthetic,
Collateral, Collateral,
@ -336,6 +323,14 @@ struct TokenEnrollRemoteRouter {
router: H256, router: H256,
} }
#[derive(Args)]
struct SetInterchainSecurityModule {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, short)]
ism: Option<Pubkey>,
}
#[derive(Args)] #[derive(Args)]
struct TransferOwnership { struct TransferOwnership {
#[arg(long, short)] #[arg(long, short)]
@ -345,6 +340,39 @@ struct TransferOwnership {
new_owner: Pubkey, new_owner: Pubkey,
} }
#[derive(Args)]
struct Igp {
#[arg(long, short, default_value_t = HYPERLANE_TOKEN_PROG_ID)]
program_id: Pubkey,
#[command(subcommand)]
cmd: GetSetCmd<GetIgpArgs, SetIgpArgs>,
}
#[derive(Subcommand)]
enum GetSetCmd<G: Args, S: Args> {
Get(G),
Set(S),
}
#[derive(Args)]
struct SetIgpArgs {
igp_program: Pubkey,
#[arg(value_enum)]
igp_type: IgpType,
igp_account: Pubkey,
}
#[derive(Args)]
struct GetIgpArgs {
token_type: TokenType,
}
#[derive(ValueEnum, Clone)]
enum IgpType {
Igp,
OverheadIgp,
}
#[derive(Args)] #[derive(Args)]
struct IgpCmd { struct IgpCmd {
#[command(subcommand)] #[command(subcommand)]
@ -353,6 +381,7 @@ struct IgpCmd {
#[derive(Subcommand)] #[derive(Subcommand)]
enum IgpSubCmd { enum IgpSubCmd {
Query(IgpQueryArgs),
PayForGas(PayForGasArgs), PayForGas(PayForGasArgs),
GasOracleConfig(GasOracleConfigArgs), GasOracleConfig(GasOracleConfigArgs),
DestinationGasOverhead(DestinationGasOverheadArgs), DestinationGasOverhead(DestinationGasOverheadArgs),
@ -360,6 +389,16 @@ enum IgpSubCmd {
TransferOverheadIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership),
} }
#[derive(Args)]
struct IgpQueryArgs {
#[arg(long)]
program_id: Pubkey,
#[arg(long)]
igp_account: Pubkey,
#[arg(long)]
gas_payment_account: Option<Pubkey>,
}
#[derive(Args)] #[derive(Args)]
struct TransferIgpOwnership { struct TransferIgpOwnership {
#[arg(long, short)] #[arg(long, short)]
@ -373,8 +412,14 @@ struct TransferIgpOwnership {
#[derive(Args)] #[derive(Args)]
struct PayForGasArgs { struct PayForGasArgs {
#[arg(long)]
program_id: Pubkey, program_id: Pubkey,
#[arg(long)]
message_id: String, message_id: String,
#[arg(long)]
destination_domain: u32,
#[arg(long)]
gas: u64,
} }
#[derive(Args)] #[derive(Args)]
@ -388,13 +433,7 @@ struct GasOracleConfigArgs {
#[arg(long)] #[arg(long)]
remote_domain: u32, remote_domain: u32,
#[command(subcommand)] #[command(subcommand)]
cmd: GasOracleSubCmd, cmd: GetSetCmd<GetGasOracleArgs, SetGasOracleArgs>,
}
#[derive(Subcommand)]
enum GasOracleSubCmd {
Set(SetGasOracleArgs),
Get,
} }
#[derive(Args)] #[derive(Args)]
@ -407,6 +446,9 @@ struct SetGasOracleArgs {
token_decimals: u8, token_decimals: u8,
} }
#[derive(Args)]
struct GetGasOracleArgs;
#[derive(Args)] #[derive(Args)]
struct DestinationGasOverheadArgs { struct DestinationGasOverheadArgs {
#[arg(long)] #[arg(long)]
@ -483,10 +525,36 @@ struct MultisigIsmMessageIdCmd {
#[derive(Subcommand)] #[derive(Subcommand)]
enum MultisigIsmMessageIdSubCmd { enum MultisigIsmMessageIdSubCmd {
Deploy(MultisigIsmMessageIdDeploy),
Init(MultisigIsmMessageIdInit), Init(MultisigIsmMessageIdInit),
SetValidatorsAndThreshold(MultisigIsmMessageIdSetValidatorsAndThreshold), SetValidatorsAndThreshold(MultisigIsmMessageIdSetValidatorsAndThreshold),
Query(MultisigIsmMessageIdInit), Query(MultisigIsmMessageIdQuery),
TransferOwnership(TransferOwnership), TransferOwnership(TransferOwnership),
Configure(MultisigIsmMessageIdConfigure),
}
#[derive(Args)]
struct MultisigIsmMessageIdDeploy {
#[arg(long)]
environment: String,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)]
built_so_dir: PathBuf,
#[arg(long)]
chain: String,
#[arg(long)]
context: String,
}
#[derive(Args)]
struct MultisigIsmMessageIdConfigure {
#[arg(long)]
program_id: Pubkey,
#[arg(long)]
multisig_config_file: PathBuf,
#[arg(long)]
chain_config_file: PathBuf,
} }
#[derive(Args)] #[derive(Args)]
@ -495,6 +563,14 @@ struct MultisigIsmMessageIdInit {
program_id: Pubkey, program_id: Pubkey,
} }
#[derive(Args)]
struct MultisigIsmMessageIdQuery {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, value_delimiter = ',')]
domains: Option<Vec<u32>>,
}
#[derive(Args)] #[derive(Args)]
struct MultisigIsmMessageIdSetValidatorsAndThreshold { struct MultisigIsmMessageIdSetValidatorsAndThreshold {
#[arg(long, short, default_value_t = MULTISIG_ISM_MESSAGE_ID_PROG_ID)] #[arg(long, short, default_value_t = MULTISIG_ISM_MESSAGE_ID_PROG_ID)]
@ -507,6 +583,40 @@ struct MultisigIsmMessageIdSetValidatorsAndThreshold {
threshold: u8, threshold: u8,
} }
#[derive(Args)]
pub(crate) struct HelloWorldCmd {
#[command(subcommand)]
cmd: HelloWorldSubCmd,
}
#[derive(Subcommand)]
pub(crate) enum HelloWorldSubCmd {
Deploy(HelloWorldDeploy),
Query(HelloWorldQuery),
}
#[derive(Args)]
pub(crate) struct HelloWorldDeploy {
#[arg(long)]
environment: String,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)]
built_so_dir: PathBuf,
#[arg(long)]
config_file: PathBuf,
#[arg(long)]
chain_config_file: PathBuf,
#[arg(long)]
context: String,
}
#[derive(Args)]
pub(crate) struct HelloWorldQuery {
#[arg(long)]
program_id: Pubkey,
}
fn main() { fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
@ -569,6 +679,7 @@ fn main() {
payer_keypair, payer_keypair,
commitment, commitment,
instructions.into(), instructions.into(),
cli.require_tx_approval,
); );
match cli.cmd { match cli.cmd {
HyperlaneSealevelCmd::Mailbox(cmd) => process_mailbox_cmd(ctx, cmd), HyperlaneSealevelCmd::Mailbox(cmd) => process_mailbox_cmd(ctx, cmd),
@ -579,6 +690,7 @@ fn main() {
} }
HyperlaneSealevelCmd::Core(cmd) => process_core_cmd(ctx, cmd), HyperlaneSealevelCmd::Core(cmd) => process_core_cmd(ctx, cmd),
HyperlaneSealevelCmd::WarpRoute(cmd) => process_warp_route_cmd(ctx, cmd), HyperlaneSealevelCmd::WarpRoute(cmd) => process_warp_route_cmd(ctx, cmd),
HyperlaneSealevelCmd::HelloWorld(cmd) => process_helloworld_cmd(ctx, cmd),
HyperlaneSealevelCmd::Igp(cmd) => process_igp_cmd(ctx, cmd), HyperlaneSealevelCmd::Igp(cmd) => process_igp_cmd(ctx, cmd),
} }
} }
@ -642,7 +754,6 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
destination_domain: outbox.destination, destination_domain: outbox.destination,
recipient: H256(outbox.recipient.to_bytes()), recipient: H256(outbox.recipient.to_bytes()),
message_body: outbox.message.into(), message_body: outbox.message.into(),
// message_body: std::iter::repeat(0x41).take(outbox.message_len).collect(),
}); });
let outbox_instruction = Instruction { let outbox_instruction = Instruction {
program_id: outbox.program_id, program_id: outbox.program_id,
@ -655,50 +766,6 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
}; };
ctx.new_txn().add(outbox_instruction).send_with_payer(); ctx.new_txn().add(outbox_instruction).send_with_payer();
} }
MailboxSubCmd::Receive(inbox) => {
// TODO this probably needs some love
let (inbox_account, _inbox_bump) =
Pubkey::find_program_address(mailbox_inbox_pda_seeds!(), &inbox.program_id);
let hyperlane_message = HyperlaneMessage {
version: VERSION,
nonce: inbox.nonce,
origin: inbox.origin,
sender: H256::repeat_byte(123),
destination: inbox.local_domain,
recipient: H256::from(inbox.recipient.to_bytes()),
body: inbox.message.bytes().collect(),
};
let mut encoded_message = vec![];
hyperlane_message.write_to(&mut encoded_message).unwrap();
let metadata = ExampleMetadata {
root: Default::default(),
index: 1,
leaf_index: 0,
// proof: Default::default(),
signatures: vec![],
};
let mut encoded_metadata = vec![];
metadata.write_to(&mut encoded_metadata).unwrap();
let ixn = MailboxInstruction::InboxProcess(InboxProcess {
metadata: encoded_metadata,
message: encoded_message,
});
let inbox_instruction = Instruction {
program_id: inbox.program_id,
data: ixn.into_instruction_data().unwrap(),
accounts: vec![
AccountMeta::new(inbox_account, false),
AccountMeta::new_readonly(spl_noop::id(), false),
AccountMeta::new_readonly(inbox.ism, false),
AccountMeta::new_readonly(inbox.recipient, false),
// Note: we would have to provide ism accounts and recipient accounts here if
// they were to use other accounts.
],
};
ctx.new_txn().add(inbox_instruction).send_with_payer();
}
MailboxSubCmd::Delivered(delivered) => { MailboxSubCmd::Delivered(delivered) => {
let (processed_message_account_key, _processed_message_account_bump) = let (processed_message_account_key, _processed_message_account_bump) =
Pubkey::find_program_address( Pubkey::find_program_address(
@ -731,6 +798,20 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
) )
.send_with_payer(); .send_with_payer();
} }
MailboxSubCmd::SetDefaultIsm(set_default_ism) => {
let instruction = hyperlane_sealevel_mailbox::instruction::set_default_ism_instruction(
set_default_ism.program_id,
ctx.payer_pubkey,
set_default_ism.default_ism,
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Setting default ISM to {}", set_default_ism.default_ism),
)
.send_with_payer();
}
}; };
} }
@ -785,32 +866,7 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
); );
if let Some(info) = &accounts[0] { if let Some(info) = &accounts[0] {
println!("{:#?}", info); println!("{:#?}", info);
parse_token_account_data(query.token_type, &mut info.data.as_ref());
match query.token_type {
TokenType::Native => {
match HyperlaneTokenAccount::<NativePlugin>::fetch(&mut info.data.as_ref())
{
Ok(token) => println!("{:#?}", token.into_inner()),
Err(err) => println!("Failed to deserialize account data: {}", err),
}
}
TokenType::Synthetic => {
match HyperlaneTokenAccount::<SyntheticPlugin>::fetch(
&mut info.data.as_ref(),
) {
Ok(token) => println!("{:#?}", token.into_inner()),
Err(err) => println!("Failed to deserialize account data: {}", err),
}
}
TokenType::Collateral => {
match HyperlaneTokenAccount::<CollateralPlugin>::fetch(
&mut info.data.as_ref(),
) {
Ok(token) => println!("{:#?}", token.into_inner()),
Err(err) => println!("Failed to deserialize account data: {}", err),
}
}
}
} else { } else {
println!("Not yet created?"); println!("Not yet created?");
} }
@ -1108,6 +1164,59 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
) )
.send_with_payer(); .send_with_payer();
} }
TokenSubCmd::SetInterchainSecurityModule(set_ism) => {
let instruction =
hyperlane_sealevel_token_lib::instruction::set_interchain_security_module_instruction(
set_ism.program_id,
ctx.payer_pubkey,
set_ism.ism
)
.unwrap();
ctx.new_txn()
.add_with_description(instruction, format!("Set ISM to {:?}", set_ism.ism))
.send_with_payer();
}
TokenSubCmd::Igp(args) => match args.cmd {
GetSetCmd::Set(set_args) => {
let igp_type: InterchainGasPaymasterType = match set_args.igp_type {
IgpType::Igp => InterchainGasPaymasterType::Igp(set_args.igp_account),
IgpType::OverheadIgp => {
InterchainGasPaymasterType::OverheadIgp(set_args.igp_account)
}
};
let instruction = hyperlane_sealevel_token_lib::instruction::set_igp_instruction(
args.program_id,
ctx.payer_pubkey,
Some((set_args.igp_program, igp_type.clone())),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Set IGP of {} to program {}, type {:?}",
args.program_id, set_args.igp_program, igp_type
),
)
.send_with_payer();
}
GetSetCmd::Get(get_args) => {
let (token_account, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &args.program_id);
let token_account = ctx
.client
.get_account_with_commitment(&token_account, ctx.commitment)
.unwrap()
.value
.expect(
"Token account not found. Make sure you are connected to the right RPC.",
);
parse_token_account_data(get_args.token_type, &mut &token_account.data[..]);
}
},
} }
} }
@ -1205,93 +1314,47 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) {
} }
} }
fn process_multisig_ism_message_id_cmd(ctx: Context, cmd: MultisigIsmMessageIdCmd) { fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
match cmd.cmd { match cmd.cmd {
MultisigIsmMessageIdSubCmd::Init(init) => { IgpSubCmd::Query(query) => {
let init_instruction = let (program_data_account_pda, _program_data_account_bump) =
hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction( Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id);
init.program_id,
ctx.payer_pubkey, let accounts = ctx
.client
.get_multiple_accounts_with_commitment(
&[program_data_account_pda, query.igp_account],
ctx.commitment,
) )
.unwrap(); .unwrap()
ctx.new_txn().add(init_instruction).send_with_payer(); .value;
}
MultisigIsmMessageIdSubCmd::SetValidatorsAndThreshold(set_config) => {
let (access_control_pda_key, _access_control_pda_bump) = Pubkey::find_program_address(
multisig_ism_message_id_access_control_pda_seeds!(),
&set_config.program_id,
);
let (domain_data_pda_key, _domain_data_pda_bump) = Pubkey::find_program_address( let igp_program_data =
multisig_ism_message_id_domain_data_pda_seeds!(set_config.domain), IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
&set_config.program_id, .unwrap()
); .into_inner();
let ixn = MultisigIsmMessageIdInstruction::SetValidatorsAndThreshold(Domained { println!("IGP program data: {:?}", igp_program_data);
domain: set_config.domain,
data: ValidatorsAndThreshold {
validators: set_config.validators,
threshold: set_config.threshold,
},
});
// Accounts: let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..])
// 0. `[signer]` The access control owner and payer of the domain PDA. .unwrap()
// 1. `[]` The access control PDA account. .into_inner();
// 2. `[writable]` The PDA relating to the provided domain.
// 3. `[executable]` OPTIONAL - The system program account. Required if creating the domain PDA.
let accounts = vec![
AccountMeta::new(ctx.payer_pubkey, true),
AccountMeta::new_readonly(access_control_pda_key, false),
AccountMeta::new(domain_data_pda_key, false),
AccountMeta::new_readonly(system_program::id(), false),
];
let set_instruction = Instruction { println!("IGP account: {:?}", igp);
program_id: set_config.program_id,
data: ixn.encode().unwrap(),
accounts,
};
ctx.new_txn().add(set_instruction).send_with_payer();
}
MultisigIsmMessageIdSubCmd::Query(query) => {
let (access_control_pda_key, _access_control_pda_bump) = Pubkey::find_program_address(
multisig_ism_message_id_access_control_pda_seeds!(),
&query.program_id,
);
let accounts = ctx if let Some(gas_payment_account_pubkey) = query.gas_payment_account {
let account = ctx
.client .client
.get_multiple_accounts_with_commitment(&[access_control_pda_key], ctx.commitment) .get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment)
.unwrap() .unwrap()
.value; .value
let access_control = .unwrap();
AccessControlAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..]) let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..])
.unwrap() .unwrap()
.into_inner(); .into_inner();
println!("Access control: {:#?}", access_control); println!("Gas payment account: {:?}", gas_payment_account);
}
MultisigIsmMessageIdSubCmd::TransferOwnership(transfer_ownership) => {
let instruction =
hyperlane_sealevel_multisig_ism_message_id::instruction::transfer_ownership_instruction(
transfer_ownership.program_id,
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Transfer ownership to {}", transfer_ownership.new_owner),
)
.send_with_payer();
}
} }
} }
fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
match cmd.cmd {
IgpSubCmd::PayForGas(payment_details) => { IgpSubCmd::PayForGas(payment_details) => {
let unique_gas_payment_keypair = Keypair::new(); let unique_gas_payment_keypair = Keypair::new();
let salt = H256::zero(); let salt = H256::zero();
@ -1312,8 +1375,8 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
Some(overhead_igp_account), Some(overhead_igp_account),
unique_gas_payment_keypair.pubkey(), unique_gas_payment_keypair.pubkey(),
H256::from_str(&payment_details.message_id).unwrap(), H256::from_str(&payment_details.message_id).unwrap(),
13376, payment_details.destination_domain,
100000, payment_details.gas,
) )
.unwrap(); .unwrap();
@ -1330,7 +1393,7 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
let core_program_ids = let core_program_ids =
read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name); read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name);
match args.cmd { match args.cmd {
GasOracleSubCmd::Set(set_args) => { GetSetCmd::Set(set_args) => {
let remote_gas_data = RemoteGasData { let remote_gas_data = RemoteGasData {
token_exchange_rate: set_args.token_exchange_rate, token_exchange_rate: set_args.token_exchange_rate,
gas_price: set_args.gas_price, gas_price: set_args.gas_price,
@ -1351,8 +1414,7 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
ctx.new_txn().add(instruction).send_with_payer(); ctx.new_txn().add(instruction).send_with_payer();
println!("Set gas oracle for remote domain {:?}", args.remote_domain); println!("Set gas oracle for remote domain {:?}", args.remote_domain);
} }
GasOracleSubCmd::Get => { GetSetCmd::Get(_) => {
// Read the gas oracle config
let igp_account = ctx let igp_account = ctx
.client .client
.get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment) .get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment)

@ -0,0 +1,312 @@
use std::collections::{HashMap, HashSet};
use std::{fs::File, path::Path};
use serde::{Deserialize, Serialize};
use solana_program::pubkey::Pubkey;
use solana_sdk::signature::Signer;
use crate::{
artifacts::{write_json, SingularProgramIdArtifact},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program},
router::ChainMetadata,
Context, MultisigIsmMessageIdCmd, MultisigIsmMessageIdSubCmd,
};
use hyperlane_core::H160;
use hyperlane_sealevel_multisig_ism_message_id::{
access_control_pda_seeds,
accounts::{AccessControlAccount, DomainDataAccount},
domain_data_pda_seeds,
instruction::{set_validators_and_threshold_instruction, ValidatorsAndThreshold},
};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MultisigIsmConfig {
/// Note this type is ignored in this tooling. It'll always assume this
/// relates to a multisig-ism-message-id variant, which is the only type
/// implemented in Sealevel.
#[serde(rename = "type")]
pub module_type: u8,
pub validators: Vec<H160>,
pub threshold: u8,
}
impl From<MultisigIsmConfig> for ValidatorsAndThreshold {
fn from(val: MultisigIsmConfig) -> Self {
ValidatorsAndThreshold {
validators: val.validators,
threshold: val.threshold,
}
}
}
pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: MultisigIsmMessageIdCmd) {
match cmd.cmd {
MultisigIsmMessageIdSubCmd::Deploy(deploy) => {
let environments_dir =
create_new_directory(&deploy.environments_dir, &deploy.environment);
let ism_dir = create_new_directory(&environments_dir, "multisig-ism-message-id");
let chain_dir = create_new_directory(&ism_dir, &deploy.chain);
let context_dir = create_new_directory(&chain_dir, &deploy.context);
let key_dir = create_new_directory(&context_dir, "keys");
let ism_program_id =
deploy_multisig_ism_message_id(&mut ctx, &deploy.built_so_dir, true, &key_dir);
write_json::<SingularProgramIdArtifact>(
&context_dir.join("program-ids.json"),
ism_program_id.into(),
);
}
MultisigIsmMessageIdSubCmd::Init(init) => {
let init_instruction =
hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction(
init.program_id,
ctx.payer_pubkey,
)
.unwrap();
ctx.new_txn().add(init_instruction).send_with_payer();
}
MultisigIsmMessageIdSubCmd::SetValidatorsAndThreshold(set_config) => {
set_validators_and_threshold(
&mut ctx,
set_config.program_id,
set_config.domain,
ValidatorsAndThreshold {
validators: set_config.validators,
threshold: set_config.threshold,
},
);
}
MultisigIsmMessageIdSubCmd::Query(query) => {
let (access_control_pda_key, _access_control_pda_bump) =
Pubkey::find_program_address(access_control_pda_seeds!(), &query.program_id);
let accounts = ctx
.client
.get_multiple_accounts_with_commitment(&[access_control_pda_key], ctx.commitment)
.unwrap()
.value;
let access_control =
AccessControlAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
.unwrap()
.into_inner();
println!("Access control: {:#?}", access_control);
if let Some(domains) = query.domains {
for domain in domains {
println!("Querying domain data for origin domain: {}", domain);
let (domain_data_pda_key, _domain_data_pda_bump) = Pubkey::find_program_address(
domain_data_pda_seeds!(domain),
&query.program_id,
);
let accounts = ctx
.client
.get_multiple_accounts_with_commitment(
&[domain_data_pda_key],
ctx.commitment,
)
.unwrap()
.value;
if let Some(account) = &accounts[0] {
let domain_data = DomainDataAccount::fetch(&mut &account.data[..])
.unwrap()
.into_inner();
println!("Domain data for {}:\n{:#?}", domain, domain_data);
} else {
println!("No domain data for domain {}", domain);
}
}
}
}
MultisigIsmMessageIdSubCmd::TransferOwnership(transfer_ownership) => {
let instruction =
hyperlane_sealevel_multisig_ism_message_id::instruction::transfer_ownership_instruction(
transfer_ownership.program_id,
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Transfer ownership to {}", transfer_ownership.new_owner),
)
.send_with_payer();
}
MultisigIsmMessageIdSubCmd::Configure(configure) => {
configure_multisig_ism_message_id(
&mut ctx,
configure.program_id,
&configure.multisig_config_file,
&configure.chain_config_file,
);
}
}
}
pub(crate) fn deploy_multisig_ism_message_id(
ctx: &mut Context,
built_so_dir: &Path,
use_existing_keys: bool,
key_dir: &Path,
) -> Pubkey {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
"hyperlane_sealevel_multisig_ism_message_id-keypair.json",
use_existing_keys,
);
let program_id = keypair.pubkey();
deploy_program(
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
built_so_dir
.join("hyperlane_sealevel_multisig_ism_message_id.so")
.to_str()
.unwrap(),
&ctx.client.url(),
);
println!(
"Deployed Multisig ISM Message ID at program ID {}",
program_id
);
// Initialize
let instruction = hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Initializing Multisig ISM Message ID with payer & owner {}",
ctx.payer_pubkey
),
)
.send_with_payer();
program_id
}
/// Configures the multisig-ism-message-id program
/// with the validators and thresholds for each of the domains
/// specified in the multisig config file.
fn configure_multisig_ism_message_id(
ctx: &mut Context,
program_id: Pubkey,
multisig_config_file_path: &Path,
chain_config_path: &Path,
) {
let multisig_config_file =
File::open(multisig_config_file_path).expect("Failed to open config file");
let multisig_configs: HashMap<String, MultisigIsmConfig> =
serde_json::from_reader(multisig_config_file).expect("Failed to read config file");
let chain_config_file = File::open(chain_config_path).unwrap();
let chain_configs: HashMap<String, ChainMetadata> =
serde_json::from_reader(chain_config_file).unwrap();
for (chain_name, multisig_ism_config) in multisig_configs {
println!(
"Configuring Multisig ISM Message ID for chain {} and config {:?}",
chain_name, multisig_ism_config
);
let chain_config = chain_configs.get(&chain_name).unwrap();
let matches = multisig_ism_config_matches_chain(
ctx,
program_id,
chain_config.domain_id(),
&multisig_ism_config,
);
if matches {
println!(
"Multisig ISM Message ID already correctly configured for chain {}",
chain_name
);
} else {
println!(
"Multisig ISM Message ID incorrectly configured for chain {}, configuring now",
chain_name
);
set_validators_and_threshold(
ctx,
program_id,
chain_config.domain_id(),
multisig_ism_config.into(),
);
}
}
}
fn multisig_ism_config_matches_chain(
ctx: &mut Context,
program_id: Pubkey,
remote_domain: u32,
expected: &MultisigIsmConfig,
) -> bool {
let (domain_data_key, _domain_data_bump) =
Pubkey::find_program_address(domain_data_pda_seeds!(remote_domain), &program_id);
let domain_data_account = ctx
.client
.get_account_with_commitment(&domain_data_key, ctx.commitment)
.expect("Failed to get domain data account")
.value;
if let Some(domain_data_account) = domain_data_account {
let domain_data = DomainDataAccount::fetch(&mut &domain_data_account.data[..])
.unwrap()
.into_inner();
let expected_validator_set =
HashSet::<H160>::from_iter(expected.validators.iter().cloned());
let actual_validator_set = HashSet::<H160>::from_iter(
domain_data
.validators_and_threshold
.validators
.iter()
.cloned(),
);
expected_validator_set == actual_validator_set
&& expected.threshold == domain_data.validators_and_threshold.threshold
} else {
false
}
}
pub(crate) fn set_validators_and_threshold(
ctx: &mut Context,
program_id: Pubkey,
domain: u32,
validators_and_threshold: ValidatorsAndThreshold,
) {
let description = format!(
"Set for remote domain {} validators and threshold: {:?}",
domain, validators_and_threshold
);
ctx.new_txn()
.add_with_description(
set_validators_and_threshold_instruction(
program_id,
ctx.payer_pubkey,
domain,
validators_and_threshold,
)
.unwrap(),
description,
)
.send_with_payer();
}

@ -0,0 +1,593 @@
use hyperlane_core::{utils::hex_or_base58_to_h256, H256};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs::File,
path::{Path, PathBuf},
};
use solana_client::rpc_client::RpcClient;
use solana_program::instruction::Instruction;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signer};
use account_utils::DiscriminatorData;
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::accounts::{Igp, InterchainGasPaymasterType, OverheadIgp};
use crate::{
artifacts::{write_json, HexAndBase58ProgramIdArtifact},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program_idempotent},
read_core_program_ids, Context, CoreProgramIds,
};
/// Optional connection client configuration.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OptionalConnectionClientConfig {
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
mailbox: Option<Pubkey>,
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
interchain_gas_paymaster: Option<Pubkey>,
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
interchain_security_module: Option<Pubkey>,
}
impl OptionalConnectionClientConfig {
pub fn mailbox(&self, default: Pubkey) -> Pubkey {
self.mailbox.unwrap_or(default)
}
pub fn interchain_security_module(&self) -> Option<Pubkey> {
self.interchain_security_module
}
/// Uses the configured IGP account, if Some, to get the IGP program ID
/// and generate a config of the form Some((program_id, Igp account)).
pub fn interchain_gas_paymaster_config(
&self,
client: &RpcClient,
) -> Option<(Pubkey, InterchainGasPaymasterType)> {
if let Some(igp_pubkey) = self.interchain_gas_paymaster {
let account = client
.get_account(&self.interchain_gas_paymaster.unwrap())
.unwrap();
match &account.data[1..9] {
Igp::DISCRIMINATOR_SLICE => {
Some((account.owner, InterchainGasPaymasterType::Igp(igp_pubkey)))
}
OverheadIgp::DISCRIMINATOR_SLICE => Some((
account.owner,
InterchainGasPaymasterType::OverheadIgp(igp_pubkey),
)),
_ => {
panic!("Invalid IGP account configured {}", igp_pubkey);
}
}
} else {
None
}
}
}
/// Optional ownable configuration.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OptionalOwnableConfig {
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
pub owner: Option<Pubkey>,
}
impl OptionalOwnableConfig {
pub fn owner(&self, default: Pubkey) -> Pubkey {
self.owner.unwrap_or(default)
}
}
/// Router configuration.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RouterConfig {
// Kept as a string to allow for hex or base58
pub foreign_deployment: Option<String>,
#[serde(flatten)]
pub ownable: OptionalOwnableConfig,
#[serde(flatten)]
pub connection_client: OptionalConnectionClientConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RpcUrlConfig {
pub http: String,
}
/// An abridged version of the Typescript ChainMetadata
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ChainMetadata {
chain_id: u32,
/// Hyperlane domain, only required if differs from id above
domain_id: Option<u32>,
name: String,
/// Collection of RPC endpoints
rpc_urls: Vec<RpcUrlConfig>,
}
impl ChainMetadata {
pub fn client(&self) -> RpcClient {
RpcClient::new_with_commitment(self.rpc_urls[0].http.clone(), CommitmentConfig::confirmed())
}
pub fn domain_id(&self) -> u32 {
self.domain_id.unwrap_or(self.chain_id)
}
}
pub trait RouterConfigGetter {
fn router_config(&self) -> &RouterConfig;
}
pub(crate) trait RouterDeployer<Config: RouterConfigGetter + std::fmt::Debug>:
ConnectionClient
{
#[allow(clippy::too_many_arguments)]
fn deploy(
&self,
ctx: &mut Context,
key_dir: &Path,
environments_dir: &Path,
environment: &str,
built_so_dir: &Path,
chain_config: &ChainMetadata,
app_config: &Config,
existing_program_ids: Option<&HashMap<String, Pubkey>>,
) -> Pubkey {
let program_name = self.program_name(app_config);
println!(
"Attempting deploy {} on chain: {}\nApp config: {:?}",
program_name, chain_config.name, app_config
);
let program_id = existing_program_ids
.and_then(|existing_program_ids| {
existing_program_ids.get(&chain_config.name).and_then(|id| {
chain_config
.client()
.get_account_with_commitment(id, ctx.commitment)
.unwrap()
.value
.map(|_| {
println!("Recovered existing program id {}", id);
*id
})
})
})
.unwrap_or_else(|| {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
format!("{}-{}.json", program_name, chain_config.name).as_str(),
true,
);
let program_id = keypair.pubkey();
deploy_program_idempotent(
ctx.payer_keypair_path(),
&keypair,
keypair_path.to_str().unwrap(),
built_so_dir
.join(format!("{}.so", program_name))
.to_str()
.unwrap(),
&chain_config.rpc_urls[0].http,
)
.unwrap();
program_id
});
let core_program_ids =
read_core_program_ids(environments_dir, environment, &chain_config.name);
self.init_program_idempotent(
ctx,
&chain_config.client(),
&core_program_ids,
chain_config,
app_config,
program_id,
);
program_id
}
fn init_program_idempotent(
&self,
ctx: &mut Context,
client: &RpcClient,
core_program_ids: &CoreProgramIds,
chain_config: &ChainMetadata,
app_config: &Config,
program_id: Pubkey,
);
fn post_deploy(
&self,
_ctx: &mut Context,
_app_configs: &HashMap<String, Config>,
_app_configs_to_deploy: &HashMap<&String, &Config>,
_chain_configs: &HashMap<String, ChainMetadata>,
_routers: &HashMap<u32, H256>,
) {
// By default, do nothing.
}
/// The program's name, i.e. the name of the program's .so file (without the .so suffix)
/// and the name that will be used to create the keypair file
fn program_name(&self, config: &Config) -> &str;
fn enroll_remote_routers_instruction(
&self,
program_id: Pubkey,
payer: Pubkey,
router_configs: Vec<RemoteRouterConfig>,
) -> Instruction;
fn get_routers(&self, rpc_client: &RpcClient, program_id: &Pubkey) -> HashMap<u32, H256>;
}
pub(crate) trait Ownable {
/// Gets the owner configured on-chain.
fn get_owner(&self, client: &RpcClient, program_id: &Pubkey) -> Option<Pubkey>;
/// Gets an instruction to set the owner.
fn set_owner_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
new_owner: Option<Pubkey>,
) -> Instruction;
}
pub(crate) trait ConnectionClient: Ownable {
/// Gets the interchain security module configured on-chain.
fn get_interchain_security_module(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<Pubkey>;
/// Gets an instruction to set the interchain security module.
fn set_interchain_security_module_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
ism: Option<Pubkey>,
) -> Instruction;
}
/// Idempotently deploys routers on multiple Sealevel chains and enrolls all routers (including
/// foreign deployments) on each Sealevel chain.
#[allow(clippy::too_many_arguments)]
pub(crate) fn deploy_routers<
Config: for<'a> Deserialize<'a> + RouterConfigGetter + std::fmt::Debug + Clone,
Deployer: RouterDeployer<Config>,
>(
ctx: &mut Context,
deployer: Deployer,
app_name: &str,
deploy_name: &str,
app_config_file_path: PathBuf,
chain_config_file_path: PathBuf,
environments_dir_path: PathBuf,
environment: &str,
built_so_dir_path: PathBuf,
) {
// Load the app configs from the app config file.
let app_config_file = File::open(app_config_file_path).unwrap();
let app_configs: HashMap<String, Config> = serde_json::from_reader(app_config_file).unwrap();
// Load the chain configs from the chain config file.
let chain_config_file = File::open(chain_config_file_path).unwrap();
let chain_configs: HashMap<String, ChainMetadata> =
serde_json::from_reader(chain_config_file).unwrap();
let environments_dir = create_new_directory(&environments_dir_path, environment);
let artifacts_dir = create_new_directory(&environments_dir, app_name);
let deploy_dir = create_new_directory(&artifacts_dir, deploy_name);
let keys_dir = create_new_directory(&deploy_dir, "keys");
let existing_program_ids = read_router_program_ids(&deploy_dir);
// Builds a HashMap of all the foreign deployments from the app config.
// These domains with foreign deployments will not have any txs / deployments
// made directly to them, but the routers will be enrolled on the other chains.
let foreign_deployments = app_configs
.iter()
.filter_map(|(chain_name, app_config)| {
app_config
.router_config()
.foreign_deployment
.as_ref()
.map(|foreign_deployment| {
let chain_config = chain_configs.get(chain_name).unwrap();
(
chain_config.domain_id(),
hex_or_base58_to_h256(foreign_deployment).unwrap(),
)
})
})
.collect::<HashMap<u32, H256>>();
// A map of all the routers, including the foreign deployments.
let mut routers: HashMap<u32, H256> = foreign_deployments;
// Non-foreign app configs to deploy to.
let app_configs_to_deploy = app_configs
.iter()
.filter(|(_, app_config)| app_config.router_config().foreign_deployment.is_none())
.collect::<HashMap<_, _>>();
// Now we deploy to chains that don't have a foreign deployment
for (chain_name, app_config) in app_configs_to_deploy.iter() {
let chain_config = chain_configs
.get(*chain_name)
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name));
if let Some(configured_owner) = app_config.router_config().ownable.owner {
if configured_owner != ctx.payer_pubkey {
println!("WARNING: Ownership transfer is not yet supported in this deploy tooling, ownership is granted to the payer account");
}
}
// Deploy - this is idempotent.
let program_id = deployer.deploy(
ctx,
&keys_dir,
&environments_dir_path,
environment,
&built_so_dir_path,
chain_config,
app_config,
existing_program_ids.as_ref(),
);
// Add the router to the list of routers.
routers.insert(
chain_config.domain_id(),
H256::from_slice(&program_id.to_bytes()[..]),
);
configure_connection_client(
ctx,
&deployer,
&program_id,
app_config.router_config(),
chain_config,
);
configure_owner(
ctx,
&deployer,
&program_id,
app_config.router_config(),
chain_config,
);
}
// Now enroll all the routers.
enroll_all_remote_routers(
&deployer,
ctx,
&app_configs_to_deploy,
&chain_configs,
&routers,
);
// Call the post-deploy hook.
deployer.post_deploy(
ctx,
&app_configs,
&app_configs_to_deploy,
&chain_configs,
&routers,
);
// Now write the program ids to a file!
let routers_by_name: HashMap<String, H256> = routers
.iter()
.map(|(domain_id, router)| {
(
chain_configs
.iter()
.find(|(_, chain_config)| chain_config.domain_id() == *domain_id)
.unwrap()
.0
.clone(),
*router,
)
})
.collect::<HashMap<String, H256>>();
write_router_program_ids(&deploy_dir, &routers_by_name);
}
// Idempotent.
// TODO: This should really be brought out into some nicer abstraction, and we should
// also look for IGP inconsistency etc.
fn configure_connection_client(
ctx: &mut Context,
deployer: &impl ConnectionClient,
program_id: &Pubkey,
router_config: &RouterConfig,
chain_config: &ChainMetadata,
) {
// Just ISM for now
let client = chain_config.client();
let actual_ism = deployer.get_interchain_security_module(&client, program_id);
let expected_ism = router_config.connection_client.interchain_security_module();
if actual_ism != expected_ism {
ctx.new_txn()
.add_with_description(
deployer.set_interchain_security_module_instruction(
&client,
program_id,
expected_ism,
),
format!(
"Setting ISM for chain: {} ({}) to {:?}",
chain_config.name,
chain_config.domain_id(),
expected_ism
),
)
.with_client(&client)
.send_with_payer();
}
}
// Idempotent.
// TODO: This should really be brought out into some nicer abstraction
fn configure_owner(
ctx: &mut Context,
deployer: &impl ConnectionClient,
program_id: &Pubkey,
router_config: &RouterConfig,
chain_config: &ChainMetadata,
) {
let client = chain_config.client();
let actual_owner = deployer.get_owner(&client, program_id);
let expected_owner = Some(router_config.ownable.owner(ctx.payer_pubkey));
if actual_owner != expected_owner {
ctx.new_txn()
.add_with_description(
deployer.set_owner_instruction(&client, program_id, expected_owner),
format!(
"Setting owner for chain: {} ({}) to {:?}",
chain_config.name,
chain_config.domain_id(),
expected_owner,
),
)
.with_client(&client)
.send_with_payer();
}
}
/// For each chain in app_configs_to_deploy, enrolls all the remote routers.
/// Idempotent.
fn enroll_all_remote_routers<
Config: for<'a> Deserialize<'a> + RouterConfigGetter + std::fmt::Debug + Clone,
>(
deployer: &impl RouterDeployer<Config>,
ctx: &mut Context,
app_configs_to_deploy: &HashMap<&String, &Config>,
chain_configs: &HashMap<String, ChainMetadata>,
routers: &HashMap<u32, H256>,
) {
for (chain_name, _) in app_configs_to_deploy.iter() {
let chain_config = chain_configs
.get(*chain_name)
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name));
let domain_id = chain_config.domain_id();
let program_id: Pubkey =
Pubkey::new_from_array(*routers.get(&domain_id).unwrap().as_fixed_bytes());
let enrolled_routers = deployer.get_routers(&chain_config.client(), &program_id);
let expected_routers = routers
.iter()
.filter(|(router_domain_id, _)| *router_domain_id != &domain_id)
.map(|(domain, router)| {
(
*domain,
RemoteRouterConfig {
domain: *domain,
router: Some(*router),
},
)
})
.collect::<HashMap<u32, RemoteRouterConfig>>();
// Routers to enroll (or update to a Some value)
let routers_to_enroll = expected_routers
.iter()
.filter(|(domain, router_config)| {
enrolled_routers.get(domain) != router_config.router.as_ref()
})
.map(|(_, router_config)| router_config.clone());
// Routers to remove
let routers_to_unenroll = enrolled_routers
.iter()
.filter(|(domain, _)| !expected_routers.contains_key(domain))
.map(|(domain, _)| RemoteRouterConfig {
domain: *domain,
router: None,
});
// All router config changes
let router_configs = routers_to_enroll
.chain(routers_to_unenroll)
.collect::<Vec<RemoteRouterConfig>>();
if !router_configs.is_empty() {
println!(
"Enrolling routers for chain: {}, program_id {}, routers: {:?}",
chain_name, program_id, router_configs,
);
ctx.new_txn()
.add(deployer.enroll_remote_routers_instruction(
program_id,
ctx.payer_pubkey,
router_configs,
))
.with_client(&chain_config.client())
.send_with_payer();
} else {
println!(
"No router changes for chain: {}, program_id {}",
chain_name, program_id
);
}
}
}
// Writes router program IDs as hex and base58.
fn write_router_program_ids(deploy_dir: &Path, routers: &HashMap<String, H256>) {
let serialized_program_ids = routers
.iter()
.map(|(chain_name, router)| (chain_name.clone(), (*router).into()))
.collect::<HashMap<String, HexAndBase58ProgramIdArtifact>>();
let program_ids_file = deploy_dir.join("program-ids.json");
write_json(&program_ids_file, serialized_program_ids);
}
fn read_router_program_ids(deploy_dir: &Path) -> Option<HashMap<String, Pubkey>> {
let program_ids_file = deploy_dir.join("program-ids.json");
if !program_ids_file.exists() {
return None;
}
let serialized_program_ids: HashMap<String, HexAndBase58ProgramIdArtifact> =
serde_json::from_reader(File::open(program_ids_file).unwrap()).unwrap();
let existing_program_ids = serialized_program_ids
.iter()
.map(|(chain_name, program_id)| (chain_name.clone(), program_id.into()))
.collect::<HashMap<String, Pubkey>>();
Some(existing_program_ids)
}

@ -0,0 +1,68 @@
/// For serializing and deserializing Pubkey
pub(crate) mod serde_pubkey {
use borsh::BorshDeserialize;
use serde::{Deserialize, Deserializer, Serializer};
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Deserialize)]
#[serde(untagged)]
enum RawPubkey {
String(String),
Bytes(Vec<u8>),
}
pub fn serialize<S: Serializer>(k: &Pubkey, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&k.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Pubkey, D::Error> {
match RawPubkey::deserialize(de)? {
RawPubkey::String(s) => Pubkey::from_str(&s).map_err(serde::de::Error::custom),
RawPubkey::Bytes(b) => Pubkey::try_from_slice(&b).map_err(serde::de::Error::custom),
}
}
}
/// For serializing and deserializing Option<Pubkey>
pub(crate) mod serde_option_pubkey {
use borsh::BorshDeserialize;
use serde::{Deserialize, Deserializer, Serializer};
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Deserialize)]
#[serde(untagged)]
enum RawPubkey {
String(String),
Bytes(Vec<u8>),
}
pub fn serialize<S: Serializer>(k: &Option<Pubkey>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&k.map(|k| k.to_string()).unwrap_or_default())
}
pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Option<Pubkey>, D::Error> {
match Option::<RawPubkey>::deserialize(de)? {
Some(RawPubkey::String(s)) => {
if s.is_empty() {
Ok(None)
} else {
Pubkey::from_str(&s)
.map_err(serde::de::Error::custom)
.map(Some)
}
}
Some(RawPubkey::Bytes(b)) => {
if b.is_empty() {
Ok(None)
} else {
Pubkey::try_from_slice(&b)
.map_err(serde::de::Error::custom)
.map(Some)
}
}
None => Ok(None),
}
}
}

@ -1,30 +1,41 @@
use hyperlane_core::{utils::hex_or_base58_to_h256, H256}; use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::H256;
use hyperlane_sealevel_token_collateral::plugin::CollateralPlugin;
use hyperlane_sealevel_token_native::plugin::NativePlugin;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs::File, path::Path, str::FromStr}; use std::{collections::HashMap, fmt::Debug};
use solana_client::{client_error::ClientError, rpc_client::RpcClient}; use solana_client::{client_error::ClientError, rpc_client::RpcClient};
use solana_program::program_error::ProgramError;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signer}; use solana_sdk::{instruction::Instruction, program_error::ProgramError, pubkey::Pubkey};
use hyperlane_sealevel_connection_client::{ use hyperlane_sealevel_connection_client::{
gas_router::GasRouterConfig, router::RemoteRouterConfig, gas_router::GasRouterConfig, router::RemoteRouterConfig,
}; };
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
use hyperlane_sealevel_token::{hyperlane_token_mint_pda_seeds, spl_token, spl_token_2022}; use hyperlane_sealevel_token::{
hyperlane_token_mint_pda_seeds, plugin::SyntheticPlugin, spl_token, spl_token_2022,
};
use hyperlane_sealevel_token_lib::{ use hyperlane_sealevel_token_lib::{
accounts::HyperlaneTokenAccount, accounts::{HyperlaneToken, HyperlaneTokenAccount},
hyperlane_token_pda_seeds, hyperlane_token_pda_seeds,
instruction::{enroll_remote_routers_instruction, set_destination_gas_configs, Init}, instruction::{
enroll_remote_routers_instruction, set_destination_gas_configs,
set_interchain_security_module_instruction, transfer_ownership_instruction, Init,
},
}; };
use crate::{ use crate::{
cmd_utils::{ cmd_utils::account_exists,
account_exists, create_and_write_keypair, create_new_directory, deploy_program_idempotent, core::CoreProgramIds,
router::{
deploy_routers, ChainMetadata, ConnectionClient, Ownable, RouterConfig, RouterConfigGetter,
RouterDeployer,
}, },
core::{read_core_program_ids, CoreProgramIds}, Context, TokenType as FlatTokenType, WarpRouteCmd, WarpRouteSubCmd,
Context, WarpRouteCmd, WarpRouteSubCmd,
}; };
/// Configuration relating to decimals.
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct DecimalMetadata { struct DecimalMetadata {
@ -38,6 +49,7 @@ impl DecimalMetadata {
} }
} }
/// Configuration relating to a Warp Route token.
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
enum TokenType { enum TokenType {
@ -47,14 +59,6 @@ enum TokenType {
} }
impl TokenType { impl TokenType {
fn program_name(&self) -> &str {
match self {
TokenType::Native => "hyperlane_sealevel_token_native",
TokenType::Synthetic(_) => "hyperlane_sealevel_token",
TokenType::Collateral(_) => "hyperlane_sealevel_token_collateral",
}
}
// Borrowed from HypERC20Deployer's `gasOverheadDefault`. // Borrowed from HypERC20Deployer's `gasOverheadDefault`.
fn gas_overhead_default(&self) -> u64 { fn gas_overhead_default(&self) -> u64 {
// TODO: note these are the amounts specific to the EVM. // TODO: note these are the amounts specific to the EVM.
@ -100,225 +104,233 @@ struct CollateralInfo {
spl_token_program: Option<SplTokenProgramType>, spl_token_program: Option<SplTokenProgramType>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct InterchainGasPaymasterConfig {
program_id: Pubkey,
igp_account: InterchainGasPaymasterType,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct OptionalConnectionClientConfig {
mailbox: Option<String>,
interchain_gas_paymaster: Option<InterchainGasPaymasterConfig>,
interchain_security_module: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct OptionalOwnableConfig {
owner: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct TokenConfig { struct TokenConfig {
#[serde(flatten)] #[serde(flatten)]
token_type: TokenType, token_type: TokenType,
foreign_deployment: Option<String>,
#[serde(flatten)] #[serde(flatten)]
decimal_metadata: DecimalMetadata, decimal_metadata: DecimalMetadata,
#[serde(flatten)] #[serde(flatten)]
ownable: OptionalOwnableConfig, router_config: RouterConfig,
#[serde(flatten)]
connection_client: OptionalConnectionClientConfig,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
#[serde(rename_all = "camelCase")] match cmd.cmd {
pub struct RpcUrlConfig { WarpRouteSubCmd::Deploy(deploy) => {
pub http: String, deploy_routers(
&mut ctx,
WarpRouteDeployer::new(deploy.ata_payer_funding_amount),
"warp-routes",
&deploy.warp_route_name,
deploy.token_config_file,
deploy.chain_config_file,
deploy.environments_dir,
&deploy.environment,
deploy.built_so_dir,
);
}
WarpRouteSubCmd::DestinationGas(args) => {
let destination_gas = get_destination_gas(&ctx.client, &args.program_id).unwrap();
println!(
"Destination gas: {:?}",
destination_gas[&args.destination_domain]
);
}
} }
/// An abridged version of the Typescript ChainMetadata
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ChainMetadata {
chain_id: u32,
/// Hyperlane domain, only required if differs from id above
domain_id: Option<u32>,
name: String,
/// Collection of RPC endpoints
public_rpc_urls: Vec<RpcUrlConfig>,
} }
impl ChainMetadata { struct WarpRouteDeployer {
fn client(&self) -> RpcClient { ata_payer_funding_amount: Option<u64>,
RpcClient::new_with_commitment(
self.public_rpc_urls[0].http.clone(),
CommitmentConfig::confirmed(),
)
} }
fn domain_id(&self) -> u32 { impl WarpRouteDeployer {
self.domain_id.unwrap_or(self.chain_id) fn new(ata_payer_funding_amount: Option<u64>) -> Self {
Self {
ata_payer_funding_amount,
}
} }
} }
pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) { impl WarpRouteDeployer {}
match cmd.cmd {
WarpRouteSubCmd::Deploy(deploy) => {
let token_config_file = File::open(deploy.token_config_file).unwrap();
let token_configs: HashMap<String, TokenConfig> =
serde_json::from_reader(token_config_file).unwrap();
let chain_config_file = File::open(deploy.chain_config_file).unwrap();
let chain_configs: HashMap<String, ChainMetadata> =
serde_json::from_reader(chain_config_file).unwrap();
let environments_dir =
create_new_directory(&deploy.environments_dir, &deploy.environment);
let artifacts_dir = create_new_directory(&environments_dir, "warp-routes");
let warp_route_dir = create_new_directory(&artifacts_dir, &deploy.warp_route_name);
let keys_dir = create_new_directory(&warp_route_dir, "keys");
let foreign_deployments = token_configs
.iter()
.filter(|(_, token_config)| token_config.foreign_deployment.is_some())
.map(|(chain_name, token_config)| {
let chain_config = chain_configs.get(chain_name).unwrap();
(
chain_config.domain_id(),
hex_or_base58_to_h256(token_config.foreign_deployment.as_ref().unwrap())
.unwrap(),
)
})
.collect::<HashMap<u32, H256>>();
let mut routers: HashMap<u32, H256> = foreign_deployments; impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
fn program_name(&self, config: &TokenConfig) -> &str {
match config.token_type {
TokenType::Native => "hyperlane_sealevel_token_native",
TokenType::Synthetic(_) => "hyperlane_sealevel_token",
TokenType::Collateral(_) => "hyperlane_sealevel_token_collateral",
}
}
let token_configs_to_deploy = token_configs fn enroll_remote_routers_instruction(
.clone() &self,
.into_iter() program_id: Pubkey,
.filter(|(_, token_config)| token_config.foreign_deployment.is_none()) payer: Pubkey,
.collect::<HashMap<_, _>>(); router_configs: Vec<RemoteRouterConfig>,
) -> Instruction {
enroll_remote_routers_instruction(program_id, payer, router_configs).unwrap()
}
// Deploy to chains that don't have a foreign deployment fn get_routers(&self, client: &RpcClient, program_id: &Pubkey) -> HashMap<u32, H256> {
for (chain_name, token_config) in token_configs_to_deploy.iter() { let token_data = get_token_data::<()>(client, program_id);
let chain_config = chain_configs
.get(chain_name)
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name));
if token_config.ownable.owner.is_some() { token_data.remote_routers
println!("WARNING: Ownership transfer is not yet supported in this deploy tooling, ownership is granted to the payer account");
} }
let program_id = deploy_warp_route( fn init_program_idempotent(
&mut ctx, &self,
&keys_dir, ctx: &mut Context,
&deploy.environments_dir, client: &RpcClient,
&deploy.environment, core_program_ids: &CoreProgramIds,
&deploy.built_so_dir, chain_config: &ChainMetadata,
chain_config, app_config: &TokenConfig,
token_config, program_id: Pubkey,
deploy.ata_payer_funding_amount, ) {
); if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
routers.insert( app_config.token_type,
chain_config.domain_id(), TokenType::Collateral(_) | TokenType::Synthetic(_)
H256::from_slice(&program_id.to_bytes()[..]), ) {
); fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
}
} }
// Now enroll routers let (token_pda, _token_bump) =
for (chain_name, _) in token_configs_to_deploy { Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
let chain_config = chain_configs if account_exists(client, &token_pda).unwrap() {
.get(&chain_name) println!("Warp route token already exists, skipping init");
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name)); return;
}
let domain_id = chain_config.domain_id(); let domain_id = chain_config.domain_id();
let program_id: Pubkey =
Pubkey::new_from_array(*routers.get(&domain_id).unwrap().as_fixed_bytes());
let enrolled_routers = get_routers(&chain_config.client(), &program_id).unwrap();
let expected_routers = routers // TODO: consider pulling the setting of defaults into router.rs,
.iter() // and possibly have a more distinct connection client abstration.
.filter(|(router_domain_id, _)| *router_domain_id != &domain_id)
.map(|(domain, router)| {
(
*domain,
RemoteRouterConfig {
domain: *domain,
router: Some(*router),
},
)
})
.collect::<HashMap<u32, RemoteRouterConfig>>();
// Routers to enroll (or update to a Some value) let mailbox = app_config
let routers_to_enroll = expected_routers .router_config()
.iter() .connection_client
.filter(|(domain, router_config)| { .mailbox(core_program_ids.mailbox);
enrolled_routers.get(domain) != router_config.router.as_ref() let interchain_security_module = app_config
}) .router_config()
.map(|(_, router_config)| router_config.clone()); .connection_client
.interchain_security_module();
// Routers to remove let owner = Some(app_config.router_config().ownable.owner(ctx.payer_pubkey));
let routers_to_unenroll = enrolled_routers
.iter()
.filter(|(domain, _)| !expected_routers.contains_key(domain))
.map(|(domain, _)| RemoteRouterConfig {
domain: *domain,
router: None,
});
// All router config changes // Default to the Overhead IGP
let router_configs = routers_to_enroll let interchain_gas_paymaster = Some(
.chain(routers_to_unenroll) app_config
.collect::<Vec<RemoteRouterConfig>>(); .router_config()
.connection_client
.interchain_gas_paymaster_config(client)
.unwrap_or((
core_program_ids.igp_program_id,
InterchainGasPaymasterType::OverheadIgp(core_program_ids.overhead_igp_account),
)),
);
if !router_configs.is_empty() {
println!( println!(
"Enrolling routers for chain: {}, program_id {}, routers: {:?}", "Initializing Warp Route program: domain_id: {}, mailbox: {}, ism: {:?}, owner: {:?}, igp: {:?}",
chain_name, program_id, router_configs, domain_id, mailbox, interchain_security_module, owner, interchain_gas_paymaster
); );
ctx.new_txn() let init = Init {
.add( mailbox,
enroll_remote_routers_instruction( interchain_security_module,
interchain_gas_paymaster,
decimals: app_config.decimal_metadata.decimals,
remote_decimals: app_config.decimal_metadata.remote_decimals(),
};
match &app_config.token_type {
TokenType::Native => ctx.new_txn().add(
hyperlane_sealevel_token_native::instruction::init_instruction(
program_id, program_id,
ctx.payer_pubkey, ctx.payer_pubkey,
router_configs, init,
) )
.unwrap(), .unwrap(),
),
TokenType::Synthetic(_token_metadata) => {
let decimals = init.decimals;
let init_txn = ctx.new_txn().add(
hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
) )
.with_client(&chain_config.client()) .unwrap(),
.send_with_payer();
} else {
println!(
"No router changes for chain: {}, program_id {}",
chain_name, program_id
); );
let (mint_account, _mint_bump) =
Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id);
// TODO: Also set Metaplex metadata?
init_txn.add(
spl_token_2022::instruction::initialize_mint2(
&spl_token_2022::id(),
&mint_account,
&mint_account,
None,
decimals,
)
.unwrap(),
)
}
TokenType::Collateral(collateral_info) => ctx.new_txn().add(
hyperlane_sealevel_token_collateral::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
collateral_info
.spl_token_program
.as_ref()
.expect("Cannot initalize collateral warp route without SPL token program")
.program_id(),
collateral_info.mint.parse().expect("Invalid mint address"),
)
.unwrap(),
),
} }
.with_client(client)
.send_with_payer();
}
/// Sets gas router configs on all deployable chains.
fn post_deploy(
&self,
ctx: &mut Context,
app_configs: &HashMap<String, TokenConfig>,
app_configs_to_deploy: &HashMap<&String, &TokenConfig>,
chain_configs: &HashMap<String, ChainMetadata>,
routers: &HashMap<u32, H256>,
) {
// Set gas amounts for each destination chain
for chain_name in app_configs_to_deploy.keys() {
let chain_config = chain_configs
.get(*chain_name)
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name));
let domain_id = chain_config.domain_id();
let program_id: Pubkey =
Pubkey::new_from_array(*routers.get(&domain_id).unwrap().as_fixed_bytes());
// And set destination gas // And set destination gas
let configured_destination_gas = let configured_destination_gas =
get_destination_gas(&chain_config.client(), &program_id).unwrap(); get_destination_gas(&chain_config.client(), &program_id).unwrap();
let expected_destination_gas = token_configs let expected_destination_gas = app_configs
.iter() .iter()
// filter out local chain // filter out local chain
.filter(|(dest_chain_name, _)| *dest_chain_name != &chain_name) .filter(|(dest_chain_name, _)| dest_chain_name != chain_name)
.map(|(dest_chain_name, token_config)| { .map(|(dest_chain_name, app_config)| {
let domain = chain_configs.get(dest_chain_name).unwrap().domain_id(); let domain = chain_configs.get(dest_chain_name).unwrap().domain_id();
( (
domain, domain,
GasRouterConfig { GasRouterConfig {
domain, domain,
gas: Some(token_config.token_type.gas_overhead_default()), gas: Some(app_config.token_type.gas_overhead_default()),
}, },
) )
}) })
@ -347,19 +359,19 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
.collect::<Vec<GasRouterConfig>>(); .collect::<Vec<GasRouterConfig>>();
if !destination_gas_configs.is_empty() { if !destination_gas_configs.is_empty() {
println!( let description = format!(
"Setting destination gas amounts for chain: {}, program_id {}, destination gas: {:?}", "Setting destination gas amounts for chain: {}, program_id {}, destination gas: {:?}",
chain_name, program_id, destination_gas_configs, chain_name, program_id, destination_gas_configs,
); );
ctx.new_txn() ctx.new_txn()
.add( .add_with_description(
set_destination_gas_configs( set_destination_gas_configs(
program_id, program_id,
ctx.payer_pubkey, ctx.payer_pubkey,
destination_gas_configs, destination_gas_configs,
) )
.unwrap(), .unwrap(),
description,
) )
.with_client(&chain_config.client()) .with_client(&chain_config.client())
.send_with_payer(); .send_with_payer();
@ -370,136 +382,83 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
); );
} }
} }
let routers_by_name: HashMap<String, H256> = routers
.iter()
.map(|(domain_id, router)| {
(
chain_configs
.iter()
.find(|(_, chain_config)| chain_config.domain_id() == *domain_id)
.unwrap()
.0
.clone(),
*router,
)
})
.collect::<HashMap<String, H256>>();
write_program_ids(&warp_route_dir, &routers_by_name);
} }
WarpRouteSubCmd::DestinationGas(args) => {
let destination_gas = get_destination_gas(&ctx.client, &args.program_id).unwrap();
println!(
"Destination gas: {:?}",
destination_gas[&args.destination_domain]
);
} }
impl RouterConfigGetter for TokenConfig {
fn router_config(&self) -> &RouterConfig {
&self.router_config
} }
} }
#[allow(clippy::too_many_arguments)] impl Ownable for WarpRouteDeployer {
fn deploy_warp_route( /// Gets the owner configured on-chain.
ctx: &mut Context, fn get_owner(&self, client: &RpcClient, program_id: &Pubkey) -> Option<Pubkey> {
key_dir: &Path, let token = get_token_data::<()>(client, program_id);
environments_dir: &Path,
environment: &str,
built_so_dir: &Path,
chain_config: &ChainMetadata,
token_config: &TokenConfig,
ata_payer_funding_amount: Option<u64>,
) -> Pubkey {
println!(
"Attempting deploy on chain: {}\nToken config: {:?}",
chain_config.name, token_config
);
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
format!(
"{}-{}.json",
token_config.token_type.program_name(),
chain_config.name
)
.as_str(),
true,
);
let program_id = keypair.pubkey();
deploy_program_idempotent(
ctx.payer_keypair_path(),
&keypair,
keypair_path.to_str().unwrap(),
built_so_dir
.join(format!("{}.so", token_config.token_type.program_name()))
.to_str()
.unwrap(),
&chain_config.public_rpc_urls[0].http,
)
.unwrap();
let core_program_ids = read_core_program_ids(environments_dir, environment, &chain_config.name);
init_warp_route_idempotent(
ctx,
&chain_config.client(),
&core_program_ids,
chain_config,
token_config,
program_id,
ata_payer_funding_amount,
)
.unwrap();
match &token_config.token_type { token.owner
TokenType::Native => {
println!("Deploying native token");
} }
TokenType::Synthetic(_token_metadata) => {
println!("Deploying synthetic token"); /// Gets an instruction to set the owner.
} fn set_owner_instruction(
TokenType::Collateral(_collateral_info) => { &self,
println!("Deploying collateral token"); client: &RpcClient,
program_id: &Pubkey,
new_owner: Option<Pubkey>,
) -> Instruction {
let token = get_token_data::<()>(client, program_id);
transfer_ownership_instruction(*program_id, token.owner.unwrap(), new_owner).unwrap()
} }
} }
program_id impl ConnectionClient for WarpRouteDeployer {
fn get_interchain_security_module(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<Pubkey> {
let token_data = get_token_data::<()>(client, program_id);
token_data.interchain_security_module
} }
fn init_warp_route_idempotent( fn set_interchain_security_module_instruction(
ctx: &mut Context, &self,
client: &RpcClient, client: &RpcClient,
core_program_ids: &CoreProgramIds, program_id: &Pubkey,
_chain_config: &ChainMetadata, ism: Option<Pubkey>,
token_config: &TokenConfig, ) -> Instruction {
program_id: Pubkey, let token_data = get_token_data::<()>(client, program_id);
ata_payer_funding_amount: Option<u64>,
) -> Result<(), ProgramError> {
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
if let Some(ata_payer_funding_amount) = ata_payer_funding_amount { set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism)
if matches!( .unwrap()
token_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
} }
} }
if account_exists(client, &token_pda).unwrap() { fn get_token_data<T>(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken<T>
println!("Token PDA already exists, skipping init"); where
return Ok(()); T: BorshDeserialize + BorshSerialize + Default + account_utils::Data,
{
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), program_id);
let account = client.get_account(&token_pda).unwrap();
*HyperlaneTokenAccount::<T>::fetch(&mut &account.data[..])
.unwrap()
.into_inner()
} }
init_warp_route( fn get_destination_gas(
ctx, client: &RpcClient,
client, program_id: &Pubkey,
core_program_ids, ) -> Result<HashMap<u32, u64>, ClientError> {
_chain_config, let token_data = get_token_data::<()>(client, program_id);
token_config,
program_id, Ok(token_data.destination_gas)
)
} }
// Funds the ATA payer up to the specified amount.
fn fund_ata_payer_up_to( fn fund_ata_payer_up_to(
ctx: &mut Context, ctx: &mut Context,
client: &RpcClient, client: &RpcClient,
@ -520,168 +479,42 @@ fn fund_ata_payer_up_to(
return; return;
} }
println!(
"Funding ATA payer {} with funding_amount {} to reach total balance of {}",
ata_payer_account, funding_amount, ata_payer_funding_amount
);
ctx.new_txn() ctx.new_txn()
.add(solana_program::system_instruction::transfer( .add_with_description(
solana_program::system_instruction::transfer(
&ctx.payer_pubkey, &ctx.payer_pubkey,
&ata_payer_account, &ata_payer_account,
funding_amount, funding_amount,
)) ),
format!(
"Funding ATA payer {} with funding_amount {} to reach total balance of {}",
ata_payer_account, funding_amount, ata_payer_funding_amount
),
)
.with_client(client) .with_client(client)
.send_with_payer(); .send_with_payer();
} }
fn init_warp_route( pub fn parse_token_account_data(token_type: FlatTokenType, data: &mut &[u8]) {
ctx: &mut Context, fn print_data_or_err<T: Debug>(data: Result<T, ProgramError>) {
client: &RpcClient, match data {
core_program_ids: &CoreProgramIds, Ok(data) => println!("{:#?}", data),
_chain_config: &ChainMetadata, Err(err) => println!("Failed to deserialize account data: {}", err),
token_config: &TokenConfig,
program_id: Pubkey,
) -> Result<(), ProgramError> {
// If the Mailbox was provided as configuration, use that. Otherwise, default to
// the Mailbox found in the core program ids.
let mailbox = token_config
.connection_client
.mailbox
.as_ref()
.map(|s| Pubkey::from_str(s).unwrap())
.unwrap_or(core_program_ids.mailbox);
// TODO for now not specifying an IGP for compatibility with the warp route UI.
// let interchain_gas_paymaster = Some(token_config
// .connection_client
// .interchain_gas_paymaster
// .clone()
// .map(|config| (config.program_id, config.igp_account))
// .unwrap_or((
// core_program_ids.igp_program_id,
// InterchainGasPaymasterType::OverheadIgp(core_program_ids.overhead_igp_account),
// ))
// );
let interchain_gas_paymaster = None;
let init = Init {
mailbox,
interchain_security_module: token_config
.connection_client
.interchain_security_module
.as_ref()
.map(|s| Pubkey::from_str(s).unwrap()),
interchain_gas_paymaster,
decimals: token_config.decimal_metadata.decimals,
remote_decimals: token_config.decimal_metadata.remote_decimals(),
};
match &token_config.token_type {
TokenType::Native => ctx.new_txn().add(
hyperlane_sealevel_token_native::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
)?,
),
TokenType::Synthetic(_token_metadata) => {
let decimals = init.decimals;
let init_txn =
ctx.new_txn()
.add(hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
)?);
let (mint_account, _mint_bump) =
Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id);
// TODO: Also set Metaplex metadata?
init_txn.add(
spl_token_2022::instruction::initialize_mint2(
&spl_token_2022::id(),
&mint_account,
&mint_account,
None,
decimals,
)
.unwrap(),
)
} }
TokenType::Collateral(collateral_info) => ctx.new_txn().add(
hyperlane_sealevel_token_collateral::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
collateral_info
.spl_token_program
.as_ref()
.expect("Cannot initalize collateral warp route without SPL token program")
.program_id(),
collateral_info.mint.parse().expect("Invalid mint address"),
)?,
),
} }
.with_client(client)
.send_with_payer();
Ok(()) match token_type {
FlatTokenType::Native => {
let res = HyperlaneTokenAccount::<NativePlugin>::fetch(data);
print_data_or_err(res);
} }
FlatTokenType::Synthetic => {
fn get_routers( let res = HyperlaneTokenAccount::<SyntheticPlugin>::fetch(data);
client: &RpcClient, print_data_or_err(res);
token_program_id: &Pubkey,
) -> Result<HashMap<u32, H256>, ClientError> {
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), token_program_id);
let account = client.get_account(&token_pda)?;
let token_data = HyperlaneTokenAccount::<()>::fetch(&mut &account.data[..])
.unwrap()
.into_inner();
Ok(token_data.remote_routers)
} }
FlatTokenType::Collateral => {
fn get_destination_gas( let res = HyperlaneTokenAccount::<CollateralPlugin>::fetch(data);
client: &RpcClient, print_data_or_err(res);
token_program_id: &Pubkey,
) -> Result<HashMap<u32, u64>, ClientError> {
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), token_program_id);
let account = client.get_account(&token_pda)?;
let token_data = HyperlaneTokenAccount::<()>::fetch(&mut &account.data[..])
.unwrap()
.into_inner();
Ok(token_data.destination_gas)
} }
#[derive(Serialize, Deserialize)]
struct SerializedProgramId {
hex: String,
base58: String,
} }
fn write_program_ids(warp_route_dir: &Path, routers: &HashMap<String, H256>) {
let serialized_program_ids = routers
.iter()
.map(|(chain_name, router)| {
(
chain_name.clone(),
SerializedProgramId {
hex: format!("0x{}", hex::encode(router)),
base58: Pubkey::new_from_array(router.to_fixed_bytes()).to_string(),
},
)
})
.collect::<HashMap<String, SerializedProgramId>>();
let program_ids_file = warp_route_dir.join("program-ids.json");
let program_ids_file = File::create(program_ids_file).unwrap();
serde_json::to_writer_pretty(program_ids_file, &serialized_program_ids).unwrap();
} }

@ -1 +0,0 @@
[113,244,152,170,85,122,42,51,10,74,244,18,91,8,135,77,156,19,172,122,139,50,248,3,186,184,186,140,110,165,78,161,76,88,146,213,185,127,121,92,132,2,249,73,19,192,73,170,105,85,247,241,48,175,67,28,165,29,224,252,173,165,38,140]

@ -1 +0,0 @@
[135,153,145,193,50,88,169,205,206,171,48,1,17,242,3,43,225,72,101,163,93,126,105,165,159,44,243,196,182,240,4,87,22,253,47,198,217,75,23,60,181,129,251,103,140,170,111,35,152,97,16,23,64,17,198,239,79,225,120,141,55,38,60,86]

@ -1 +0,0 @@
[252,76,67,201,250,68,86,32,216,136,163,46,192,20,249,175,209,94,101,235,24,240,204,4,246,159,180,138,253,20,48,146,182,104,250,124,231,168,239,248,95,199,219,250,126,156,57,113,83,209,232,171,10,90,153,238,72,138,186,34,77,87,172,211]

@ -1,5 +0,0 @@
{
"mailbox": "692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1",
"validator_announce": "DH43ae1LwemXAboWwSh8zc9pG8j72gKUEXNi57w8fEnn",
"multisig_ism_message_id": "2YjtZDiUoptoSsA5eVrDCcX6wxNK6YoEVW7y82x5Z2fw"
}

@ -1 +0,0 @@
[6,132,236,247,134,194,48,96,56,63,48,146,121,215,228,1,80,199,79,128,232,145,31,24,170,246,162,253,52,12,244,198,141,84,17,12,114,138,14,65,37,185,65,155,156,209,188,73,159,63,157,69,158,103,155,16,217,78,19,53,6,226,115,117]

@ -1 +0,0 @@
[75,122,156,10,143,146,130,96,41,203,245,228,178,140,170,105,167,226,18,171,187,4,70,210,1,234,232,194,206,26,65,248,243,199,245,54,127,196,31,152,114,133,16,172,1,103,105,249,111,240,129,216,26,184,14,131,242,197,189,46,163,142,2,120]

@ -1 +0,0 @@
[60,166,246,212,217,15,197,101,188,59,172,187,217,44,158,58,65,180,5,179,193,73,206,199,134,54,56,70,26,169,141,82,49,9,182,63,146,255,211,243,158,55,120,3,60,23,151,134,195,85,195,50,62,205,7,162,107,106,40,106,220,117,82,91]

@ -1,5 +0,0 @@
{
"mailbox": "AWgqPcY1vjHRoFLHNgs15fdvy4bqEakHmYXW78B8GgYk",
"validator_announce": "4JRZrYJnXJn6KPSCG4tA6GBomP2zwQv8bD65anWnHmNz",
"multisig_ism_message_id": "HQcv2ibNRuJdHU8Lt9t655YUjXj4Rp9nW8mbcA26cYqM"
}

@ -1,29 +0,0 @@
{
"solanadevnet": {
"chainId": 13375,
"name": "solanadevnet",
"publicRpcUrls": [
{
"http": "https://api.devnet.solana.com"
}
]
},
"solanadevnet1": {
"chainId": 13376,
"name": "solanadevnet1",
"publicRpcUrls": [
{
"http": "https://api.devnet.solana.com"
}
]
},
"fuji": {
"chainId": 43113,
"name": "fuji",
"publicRpcUrls": [
{
"http": "https://api.avax-test.network/ext/bc/C/rpc"
}
]
}
}

@ -1 +0,0 @@
[234,24,62,69,201,85,23,105,162,73,39,96,54,24,252,131,65,61,204,71,240,230,98,153,79,12,102,57,135,254,59,159,71,62,216,28,221,183,176,75,40,167,248,151,145,3,242,74,196,153,147,167,98,202,124,87,70,27,115,81,78,50,199,68]

@ -1 +0,0 @@
[39,54,74,37,67,213,195,70,204,38,146,230,111,25,95,162,197,128,223,145,57,112,78,217,51,236,68,252,254,70,26,37,135,224,112,242,167,101,9,162,147,37,98,70,138,147,6,126,136,247,145,107,228,139,68,251,82,120,107,18,4,102,190,221]

@ -1,14 +0,0 @@
{
"solanadevnet1": {
"hex": "0x473ed81cddb7b04b28a7f8979103f24ac49993a762ca7c57461b73514e32c744",
"base58": "5o7XXLy8N67cgCjSt4zKNnzAbDRpkVruu8BbpPNehBrK"
},
"fuji": {
"hex": "0x000000000000000000000000b3af04fc8b461138eca4f5fc1d5955bbe6d20fca",
"base58": "1111111111113WC5zqqJzmcsiZZcai6ZxbvW84do"
},
"solanadevnet": {
"hex": "0x87e070f2a76509a2932562468a93067e88f7916be48b44fb52786b120466bedd",
"base58": "A9QY6ZQ3t1T3Pk58gTx1vsSeH2B2AywwK2V7SpH2w2cC"
}
}

@ -1,23 +0,0 @@
{
"solanadevnet": {
"type": "collateral",
"token": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr",
"splTokenProgram": "token",
"decimals": 6,
"name": "USD Coin Dev",
"symbol": "USDC"
},
"solanadevnet1": {
"type": "synthetic",
"decimals": 6,
"name": "USD Coin Dev",
"symbol": "USDC"
},
"fuji": {
"type": "synthetic",
"decimals": 6,
"name": "USD Coin Dev",
"symbol": "USDC",
"foreignDeployment": "0xb3AF04Fc8b461138eCA4F5fC1D5955Bbe6D20Fca"
}
}

@ -1 +0,0 @@
[108,3,96,252,94,52,97,146,193,61,252,4,209,156,21,178,42,234,170,70,97,252,167,156,146,209,57,48,52,224,211,72,40,108,141,225,165,106,19,22,48,134,115,13,111,173,228,116,229,5,197,167,246,245,139,19,60,75,183,152,49,124,190,33]

@ -1 +0,0 @@
[169,25,77,166,171,9,74,84,180,104,209,80,36,170,223,85,56,255,50,104,185,250,53,188,65,168,235,7,176,81,99,182,113,69,170,191,248,224,191,67,181,13,107,166,133,126,157,101,165,157,24,202,25,96,195,132,107,100,86,78,48,232,7,142]

@ -1,14 +0,0 @@
{
"solanadevnet": {
"hex": "0x7145aabff8e0bf43b50d6ba6857e9d65a59d18ca1960c3846b64564e30e8078e",
"base58": "8dAgevgBAnhvxoB5mWNUfmXi6H8WLC3ZaP8poaRHkzaR"
},
"solanadevnet1": {
"hex": "0x286c8de1a56a13163086730d6fade474e505c5a7f6f58b133c4bb798317cbe21",
"base58": "3ioKCgR4pyrtkJvsB3zketopnR3mqjjBszSgtiQXQz7i"
},
"fuji": {
"hex": "0x00000000000000000000000011cf63c916263d6bbd710f43816ee6703e1c5da3",
"base58": "111111111111FPh8wLMbV6vLtqUcvxKR496PAXL"
}
}

@ -1,19 +0,0 @@
{
"solanadevnet": {
"type": "native",
"decimals": 9
},
"solanadevnet1": {
"type": "synthetic",
"decimals": 9,
"name": "Solana (solanadevnet)",
"symbol": "SOL"
},
"fuji": {
"type": "synthetic",
"decimals": 9,
"name": "Solana (solanadevnet)",
"symbol": "SOL",
"foreignDeployment": "0x11CF63c916263d6BBD710F43816ee6703e1C5da3"
}
}

@ -2,7 +2,7 @@
"sealeveltest1": { "sealeveltest1": {
"chainId": 13375, "chainId": 13375,
"name": "sealeveltest1", "name": "sealeveltest1",
"publicRpcUrls": [ "rpcUrls": [
{ {
"http": "http://localhost:8899" "http": "http://localhost:8899"
} }
@ -11,7 +11,7 @@
"sealeveltest2": { "sealeveltest2": {
"chainId": 13376, "chainId": 13376,
"name": "sealeveltest2", "name": "sealeveltest2",
"publicRpcUrls": [ "rpcUrls": [
{ {
"http": "http://localhost:8899" "http": "http://localhost:8899"
} }

@ -0,0 +1,350 @@
{
"bsc": {
"chainId": 56,
"domainId": 56,
"name": "bsc",
"protocol": "ethereum",
"displayName": "Binance Smart Chain",
"displayNameShort": "Binance",
"nativeToken": {
"decimals": 18,
"name": "BNB",
"symbol": "BNB"
},
"rpcUrls": [
{
"http": "https://bsc-dataseed.binance.org"
},
{
"http": "https://rpc.ankr.com/bsc"
}
],
"blockExplorers": [
{
"name": "BscScan",
"url": "https://bscscan.com",
"apiUrl": "https://api.bscscan.com/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 15,
"estimateBlockTime": 3
},
"gasCurrencyCoinGeckoId": "binancecoin",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-bsc.safe.global/",
"transactionOverrides": {
"gasPrice": 7000000000
}
},
"avalanche": {
"chainId": 43114,
"domainId": 43114,
"name": "avalanche",
"protocol": "ethereum",
"displayName": "Avalanche",
"nativeToken": {
"decimals": 18,
"name": "Avalanche",
"symbol": "AVAX"
},
"rpcUrls": [
{
"http": "https://api.avax.network/ext/bc/C/rpc",
"pagination": {
"maxBlockRange": 100000,
"minBlockNumber": 6765067
}
}
],
"blockExplorers": [
{
"name": "SnowTrace",
"url": "https://snowtrace.io",
"apiUrl": "https://api.snowtrace.io/api",
"family": "other"
}
],
"blocks": {
"confirmations": 3,
"reorgPeriod": 3,
"estimateBlockTime": 2
},
"gasCurrencyCoinGeckoId": "avalanche-2",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-avalanche.safe.global/"
},
"polygon": {
"chainId": 137,
"domainId": 137,
"name": "polygon",
"protocol": "ethereum",
"displayName": "Polygon",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://rpc-mainnet.matic.quiknode.pro",
"pagination": {
"maxBlockRange": 10000,
"minBlockNumber": 19657100
}
},
{
"http": "https://polygon-rpc.com"
}
],
"blockExplorers": [
{
"name": "PolygonScan",
"url": "https://polygonscan.com",
"apiUrl": "https://api.polygonscan.com/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 3,
"reorgPeriod": 256,
"estimateBlockTime": 2
},
"gasCurrencyCoinGeckoId": "matic-network",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-polygon.safe.global/",
"transactionOverrides": {
"maxFeePerGas": 500000000000,
"maxPriorityFeePerGas": 100000000000
}
},
"celo": {
"chainId": 42220,
"domainId": 42220,
"name": "celo",
"protocol": "ethereum",
"displayName": "Celo",
"nativeToken": {
"decimals": 18,
"name": "CELO",
"symbol": "CELO"
},
"rpcUrls": [
{
"http": "https://forno.celo.org"
}
],
"blockExplorers": [
{
"name": "CeloScan",
"url": "https://celoscan.io",
"apiUrl": "https://api.celoscan.io/api",
"family": "etherscan"
},
{
"name": "Blockscout",
"url": "https://explorer.celo.org",
"apiUrl": "https://explorer.celo.org/mainnet/api",
"family": "blockscout"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 0,
"estimateBlockTime": 5
},
"gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/"
},
"arbitrum": {
"chainId": 42161,
"domainId": 42161,
"name": "arbitrum",
"protocol": "ethereum",
"displayName": "Arbitrum",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://arb1.arbitrum.io/rpc"
}
],
"blockExplorers": [
{
"name": "Arbiscan",
"url": "https://arbiscan.io",
"apiUrl": "https://api.arbiscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 0,
"estimateBlockTime": 3
},
"gasCurrencyCoinGeckoId": "ethereum",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-arbitrum.safe.global/"
},
"optimism": {
"chainId": 10,
"domainId": 10,
"name": "optimism",
"protocol": "ethereum",
"displayName": "Optimism",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://mainnet.optimism.io"
}
],
"blockExplorers": [
{
"name": "Etherscan",
"url": "https://optimistic.etherscan.io",
"apiUrl": "https://api-optimistic.etherscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 0,
"estimateBlockTime": 3
},
"gasCurrencyCoinGeckoId": "ethereum",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-optimism.safe.global/"
},
"ethereum": {
"chainId": 1,
"domainId": 1,
"name": "ethereum",
"protocol": "ethereum",
"displayName": "Ethereum",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
},
{
"http": "https://cloudflare-eth.com"
}
],
"blockExplorers": [
{
"name": "Etherscan",
"url": "https://etherscan.io",
"apiUrl": "https://api.etherscan.io/api",
"family": "etherscan"
},
{
"name": "Blockscout",
"url": "https://blockscout.com/eth/mainnet",
"apiUrl": "https://blockscout.com/eth/mainnet/api",
"family": "blockscout"
}
],
"blocks": {
"confirmations": 3,
"reorgPeriod": 14,
"estimateBlockTime": 13
},
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/",
"transactionOverrides": {
"maxFeePerGas": 150000000000,
"maxPriorityFeePerGas": 5000000000
}
},
"moonbeam": {
"chainId": 1284,
"domainId": 1284,
"name": "moonbeam",
"protocol": "ethereum",
"displayName": "Moonbeam",
"nativeToken": {
"decimals": 18,
"name": "GLMR",
"symbol": "GLMR"
},
"rpcUrls": [
{
"http": "https://rpc.api.moonbeam.network"
}
],
"blockExplorers": [
{
"name": "MoonScan",
"url": "https://moonscan.io",
"apiUrl": "https://api-moonbeam.moonscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 2,
"reorgPeriod": 2,
"estimateBlockTime": 12
},
"gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network"
},
"gnosis": {
"chainId": 100,
"domainId": 100,
"name": "gnosis",
"protocol": "ethereum",
"displayName": "Gnosis",
"nativeToken": {
"name": "xDai",
"symbol": "xDai",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://rpc.gnosischain.com",
"pagination": {
"maxBlockRange": 10000,
"minBlockNumber": 25997478
}
}
],
"blockExplorers": [
{
"name": "GnosisScan",
"url": "https://gnosisscan.io",
"apiUrl": "https://api.gnosisscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 14,
"estimateBlockTime": 5
},
"gasCurrencyCoinGeckoId": "xdai",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-gnosis-chain.safe.global/"
},
"solana": {
"chainId": 1399811149,
"name": "solana",
"rpcUrls": [
{
"http": "https://api.mainnet-beta.solana.com"
}
]
},
"nautilus": {
"chainId": 22222,
"name": "nautilus",
"rpcUrls": [
{
"http": "https://api.nautilus.nautchain.xyz"
}
]
}
}

@ -0,0 +1,30 @@
{
"solana": {},
"bsc": {
"foreignDeployment": "0xB97d3bF2fC296c2cAC4056bBC8A783ff39408e20"
},
"avalanche": {
"foreignDeployment": "0x2A925CD8a5d919c5c6599633090c37fe38A561b6"
},
"polygon": {
"foreignDeployment": "0x6c0aC8cEA75232aa7BeD8cbe9C4f820E7a77a9C3"
},
"celo": {
"foreignDeployment": "0x4151773Db70C0b2D4c43Ea44A5FB5803ff1d3e0B"
},
"arbitrum": {
"foreignDeployment": "0x96271cA0ab9eeFB3Ca481749c0Ca4c705fD4F523"
},
"optimism": {
"foreignDeployment": "0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8"
},
"ethereum": {
"foreignDeployment": "0x9311cEE522A7C122B843b66cC31C6a63e2F92641"
},
"moonbeam": {
"foreignDeployment": "0xAe067C08703508230357025B38c35Cd12793628c"
},
"gnosis": {
"foreignDeployment": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4"
}
}

@ -0,0 +1,42 @@
{
"moonbeam": {
"hex": "0x000000000000000000000000ae067c08703508230357025b38c35cd12793628c",
"base58": "1111111111113RcuHPfDctyAnFWHvj8tS1q8UHPh"
},
"bsc": {
"hex": "0x000000000000000000000000b97d3bf2fc296c2cac4056bbc8a783ff39408e20",
"base58": "1111111111113atAoP8gQ2GYeue77ETUPAf8w9zw"
},
"optimism": {
"hex": "0x000000000000000000000000a6f0a37dfde9c2c8f46f010989c47d9edb3a9fa8",
"base58": "1111111111113KtqevvpYv7NCiadmp6tRRfivB8K"
},
"avalanche": {
"hex": "0x0000000000000000000000002a925cd8a5d919c5c6599633090c37fe38a561b6",
"base58": "111111111111bQB6b7XVDHSyvi7XmLrQMT8C3xH"
},
"ethereum": {
"hex": "0x0000000000000000000000009311cee522a7c122b843b66cc31c6a63e2f92641",
"base58": "11111111111133qb6DzNiJ7whNaYGud2WqqtjxFS"
},
"solana": {
"hex": "0x3797d0096b18b5b645c346a66d7f18c6c5738782c6bce24da57a3462bdef82b1",
"base58": "4k1gruSdH1r57V9QQK4aunzfMYzLFfF83jdYkkEwyem6"
},
"celo": {
"hex": "0x0000000000000000000000004151773db70c0b2d4c43ea44a5fb5803ff1d3e0b",
"base58": "111111111111unDVQcjdeHntE83qvf1vsKCZ4av"
},
"polygon": {
"hex": "0x0000000000000000000000006c0ac8cea75232aa7bed8cbe9c4f820e7a77a9c3",
"base58": "1111111111112WJXE3PCAsCXYZxU9Kh51sSZEa5G"
},
"arbitrum": {
"hex": "0x00000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523",
"base58": "11111111111136L61X7cdT9tPZ4GKBtzJtrjFAd8"
},
"gnosis": {
"hex": "0x00000000000000000000000026f32245fcf5ad53159e875d5cae62aecf19c2d4",
"base58": "111111111111YURfyMRiiTWy8X6pYHAqmYPmBpf"
}
}

@ -0,0 +1,32 @@
{
"solana": {
"interchainSecurityModule": "BYTsxBuKVbwgsZFswzB91nrxveQySghwXzaKqn8exNnC"
},
"gnosis": {
"foreignDeployment": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a"
},
"bsc": {
"foreignDeployment": "0xe5554478F167936dB253f79f57c41770bfa00Bae"
},
"avalanche": {
"foreignDeployment": "0xe1De9910fe71cC216490AC7FCF019e13a34481D7"
},
"polygon": {
"foreignDeployment": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868"
},
"celo": {
"foreignDeployment": "0xfE29f6a4468536029Fc9c97d3a9669b9fe38E114"
},
"arbitrum": {
"foreignDeployment": "0x414B67F62b143d6db6E9b633168Dd6fd4DA20642"
},
"optimism": {
"foreignDeployment": "0xB4caf2CA864B413DAA502fA18A8D48cD0740fC52"
},
"ethereum": {
"foreignDeployment": "0xed31c20c5517EaC05decD5F6dCd01Fe6d16fD09D"
},
"moonbeam": {
"foreignDeployment": "0x3eB9eE2CFC8DCB6F58B5869D33336CFcBf1dC354"
}
}

@ -0,0 +1,42 @@
{
"solana": {
"hex": "0x29dacc0e7124ea39b1fd43ab0fd30e038cf405c0229890229d0086d0b6516f9c",
"base58": "3pPDp16iVTJFge2sm85Q61hW61UN5xNqeG24gqFhzLFV"
},
"avalanche": {
"hex": "0x000000000000000000000000e1de9910fe71cc216490ac7fcf019e13a34481d7",
"base58": "11111111111149We9K5tM8ijcyNy9zDMG9RyDBCJ"
},
"arbitrum": {
"hex": "0x000000000000000000000000414b67f62b143d6db6e9b633168dd6fd4da20642",
"base58": "111111111111um79Yc6Evs5e1m2fdD2x7T1cpXb"
},
"moonbeam": {
"hex": "0x0000000000000000000000003eb9ee2cfc8dcb6f58b5869d33336cfcbf1dc354",
"base58": "111111111111sgjzaeuHfqhExkdPQ1gJdhcSr4j"
},
"optimism": {
"hex": "0x000000000000000000000000b4caf2ca864b413daa502fa18a8d48cd0740fc52",
"base58": "1111111111113X64nhkfMi9X5MbxKsiDTeeTmjsw"
},
"ethereum": {
"hex": "0x000000000000000000000000ed31c20c5517eac05decd5f6dcd01fe6d16fd09d",
"base58": "1111111111114JfPmRiKEsR445qonVzCpsAvXCR2"
},
"gnosis": {
"hex": "0x00000000000000000000000099ca8c74ce7cfa9d72a51fbb05f9821f5f826b3a",
"base58": "11111111111139Gc7eyQjpZrmWkkYQRyA2Grcvmf"
},
"bsc": {
"hex": "0x000000000000000000000000e5554478f167936db253f79f57c41770bfa00bae",
"base58": "1111111111114CJxuV4VoAh5NsJy9qCGHqryoTCy"
},
"polygon": {
"hex": "0x000000000000000000000000ab65c41a1bc580a52f0b166879122efdce0cb868",
"base58": "1111111111113PVkHAU9H7moDSoQvhC3Y2wgmovX"
},
"celo": {
"hex": "0x000000000000000000000000fe29f6a4468536029fc9c97d3a9669b9fe38e114",
"base58": "1111111111114YNh3uhCWh2NjyPttobeNRyuDHYo"
}
}

@ -0,0 +1,106 @@
{
"celo": {
"type": 3,
"threshold": 4,
"validators": [
"0x1f20274b1210046769d48174c2f0e7c25ca7d5c5",
"0x3bc014bafa43f93d534aed34f750997cdffcf007",
"0xd79d506d741fa735938f7b7847a926e34a6fe6b0",
"0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b",
"0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2",
"0xc0085e1a49bcc69e534272adb82c74c0e007e1ca"
]
},
"ethereum": {
"type": 3,
"threshold": 4,
"validators": [
"0x4c327ccb881a7542be77500b2833dc84c839e7b7",
"0x84cb373148ef9112b277e68acf676fefa9a9a9a0",
"0x0d860c2b28bec3af4fd3a5997283e460ff6f2789",
"0xd4c1211f0eefb97a846c4e6d6589832e52fc03db",
"0x600c90404d5c9df885404d2cc5350c9b314ea3a2",
"0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6"
]
},
"avalanche": {
"type": 3,
"threshold": 4,
"validators": [
"0xa7aa52623fe3d78c343008c95894be669e218b8d",
"0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c",
"0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2",
"0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6",
"0xbd2e136cda02ba627ca882e49b184cbe976081c8",
"0x1418126f944a44dad9edbab32294a8c890e7a9e3"
]
},
"polygon": {
"type": 3,
"threshold": 4,
"validators": [
"0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1",
"0x009fb042d28944017177920c1d40da02bfebf474",
"0xba4b13e23705a5919c1901150d9697e8ffb3ea71",
"0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6",
"0x5ae9b0f833dfe09ef455562a1f603f1634504dd6",
"0x6a163d312f7352a95c9b81dca15078d5bf77a442"
]
},
"bsc": {
"type": 3,
"threshold": 4,
"validators": [
"0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e",
"0xefe34eae2bca1846b895d2d0762ec21796aa196a",
"0x662674e80e189b0861d6835c287693f50ee0c2ff",
"0x8a0f59075af466841808c529624807656309c9da",
"0xdd2ff046ccd748a456b4757a73d47f165469669f",
"0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf"
]
},
"arbitrum": {
"type": 3,
"threshold": 4,
"validators": [
"0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7",
"0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20",
"0xb8085c954b75b7088bcce69e61d12fcef797cd8d",
"0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7",
"0x505dff4e0827aa5065f5e001db888e0569d46490",
"0x25c6779d4610f940bf2488732e10bcffb9d36f81"
]
},
"optimism": {
"type": 3,
"threshold": 4,
"validators": [
"0x9f2296d5cfc6b5176adc7716c7596898ded13d35",
"0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097",
"0x62144d4a52a0a0335ea5bb84392ef9912461d9dd",
"0xaff4718d5d637466ad07441ee3b7c4af8e328dbd",
"0xc64d1efeab8ae222bc889fe669f75d21b23005d9",
"0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c"
]
},
"moonbeam": {
"type": 3,
"threshold": 3,
"validators": [
"0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea",
"0x9509c8cf0a06955f27342262af501b74874e98fb",
"0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc",
"0x26725501597d47352a23cd26f122709f69ad53bc"
]
},
"gnosis": {
"type": 3,
"threshold": 3,
"validators": [
"0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6",
"0x8a72ff8571c53c62c7ca02e8c97a443cd5674383",
"0x4075c2f6bd6d9562067cfe551d49c2bcafa7d692",
"0xa18580444eaeb1c5957e7b66a6bf84b6519f904d"
]
}
}

@ -0,0 +1,18 @@
{
"bsc": {
"type": 3,
"threshold": 1,
"validators": [
"0x0000000000000000000000000000000000000001"
]
},
"nautilus": {
"type": 3,
"threshold": 2,
"validators": [
"0x9c920af9467595a23cb3433adefc3854d498a437",
"0x87611503e37ce041527c11c24263e8760fccf81f",
"0x573443248cf9929af0001b88f62131f2de29fe9f"
]
}
}

@ -0,0 +1,3 @@
{
"program_id": "9k74DkJvS2x9QhG4XfnKsLkqaCDyVfaj8s6FyJyhAeEP"
}

@ -0,0 +1,65 @@
{
"celo": {
"type": 3,
"threshold": 1,
"validators": [
"0xe7a82e210f512f8e9900d6bc2acbf7981c63e66e"
]
},
"ethereum": {
"type": 3,
"threshold": 1,
"validators": [
"0xaea1adb1c687b061e5b60b9da84cb69e7b5fab44"
]
},
"avalanche": {
"type": 3,
"threshold": 1,
"validators": [
"0x706976391e23dea28152e0207936bd942aba01ce"
]
},
"polygon": {
"type": 3,
"threshold": 1,
"validators": [
"0xef372f6ff7775989b3ac884506ee31c79638c989"
]
},
"bsc": {
"type": 3,
"threshold": 1,
"validators": [
"0x0823081031a4a6f97c6083775c191d17ca96d0ab"
]
},
"arbitrum": {
"type": 3,
"threshold": 1,
"validators": [
"0x1a95b35fb809d57faf1117c1cc29a6c5df289df1"
]
},
"optimism": {
"type": 3,
"threshold": 1,
"validators": [
"0x60e938bf280bbc21bacfd8bf435459d9003a8f98"
]
},
"moonbeam": {
"type": 3,
"threshold": 1,
"validators": [
"0x0df7140811e309dc69638352545151ebb9d5e0fd"
]
},
"gnosis": {
"type": 3,
"threshold": 1,
"validators": [
"0x15f48e78092a4f79febface509cfd76467c6cdbb"
]
}
}

@ -0,0 +1,3 @@
{
"program_id": "BYTsxBuKVbwgsZFswzB91nrxveQySghwXzaKqn8exNnC"
}

@ -1,29 +0,0 @@
{
"solana": {
"chainId": 1399811149,
"name": "solana",
"publicRpcUrls": [
{
"http": "https://api.mainnet-beta.solana.com"
}
]
},
"bsc": {
"chainId": 56,
"name": "bsc",
"publicRpcUrls": [
{
"http": "https://bsc-dataseed.binance.org"
}
]
},
"nautilus": {
"chainId": 22222,
"name": "nautilus",
"publicRpcUrls": [
{
"http": "https://api.nautilus.nautchain.xyz"
}
]
}
}

@ -3,10 +3,6 @@
"hex": "0xc5ba229fa2822fe65ac2bd0a93d8371d75292c3415dd381923c1088a3308528b", "hex": "0xc5ba229fa2822fe65ac2bd0a93d8371d75292c3415dd381923c1088a3308528b",
"base58": "EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa" "base58": "EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa"
}, },
"bsc": {
"hex": "0x000000000000000000000000c27980812e2e66491fd457d488509b7e04144b98",
"base58": "1111111111113i9HKBuaFrAQeGvhv3fJnCCDkg7h"
},
"nautilus": { "nautilus": {
"hex": "0x0000000000000000000000004501bbe6e731a4bc5c60c03a77435b2f6d5e9fe7", "hex": "0x0000000000000000000000004501bbe6e731a4bc5c60c03a77435b2f6d5e9fe7",
"base58": "111111111111xm5qkrK7gZ8Cmjr4ggPLRxy2T8a" "base58": "111111111111xm5qkrK7gZ8Cmjr4ggPLRxy2T8a"

@ -4,15 +4,9 @@
"decimals": 9, "decimals": 9,
"remoteDecimals": 9, "remoteDecimals": 9,
"token": "wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59", "token": "wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59",
"splTokenProgram": "token" "splTokenProgram": "token",
}, "interchainSecurityModule": "9k74DkJvS2x9QhG4XfnKsLkqaCDyVfaj8s6FyJyhAeEP",
"bsc": { "owner": "EzppBFV2taxWw8kEjxNYvby6q7W1biJEqwP3iC7YgRe3"
"type": "collateral",
"decimals": 9,
"token": "0x37a56cdcD83Dce2868f721De58cB3830C44C6303",
"name": "Zebec",
"symbol": "ZBC",
"foreignDeployment": "0xC27980812E2E66491FD457D488509b7E04144b98"
}, },
"nautilus": { "nautilus": {
"type": "native", "type": "native",

@ -0,0 +1,335 @@
{
"alfajores": {
"chainId": 44787,
"domainId": 44787,
"name": "alfajores",
"protocol": "ethereum",
"displayName": "Alfajores",
"nativeToken": {
"decimals": 18,
"name": "CELO",
"symbol": "CELO"
},
"rpcUrls": [
{
"http": "https://alfajores-forno.celo-testnet.org"
}
],
"blockExplorers": [
{
"name": "CeloScan",
"url": "https://alfajores.celoscan.io",
"apiUrl": "https://api-alfajores.celoscan.io/api",
"family": "etherscan"
},
{
"name": "Blockscout",
"url": "https://explorer.celo.org/alfajores",
"apiUrl": "https://explorer.celo.org/alfajores/api",
"family": "blockscout"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 0,
"estimateBlockTime": 5
},
"isTestnet": true
},
"fuji": {
"chainId": 43113,
"domainId": 43113,
"name": "fuji",
"protocol": "ethereum",
"displayName": "Fuji",
"nativeToken": {
"decimals": 18,
"name": "Avalanche",
"symbol": "AVAX"
},
"rpcUrls": [
{
"http": "https://api.avax-test.network/ext/bc/C/rpc",
"pagination": {
"maxBlockRange": 2048
}
}
],
"blockExplorers": [
{
"name": "SnowTrace",
"url": "https://testnet.snowtrace.io",
"apiUrl": "https://api-testnet.snowtrace.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 3,
"reorgPeriod": 3,
"estimateBlockTime": 2
},
"isTestnet": true
},
"mumbai": {
"chainId": 80001,
"domainId": 80001,
"name": "mumbai",
"protocol": "ethereum",
"displayName": "Mumbai",
"nativeToken": {
"name": "MATIC",
"symbol": "MATIC",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://rpc.ankr.com/polygon_mumbai",
"pagination": {
"maxBlockRange": 10000,
"minBlockNumber": 22900000
}
},
{
"http": "https://matic-mumbai.chainstacklabs.com"
}
],
"blockExplorers": [
{
"name": "PolygonScan",
"url": "https://mumbai.polygonscan.com",
"apiUrl": "https://api-testnet.polygonscan.com/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 3,
"reorgPeriod": 32,
"estimateBlockTime": 5
},
"isTestnet": true
},
"bsctestnet": {
"chainId": 97,
"domainId": 97,
"name": "bsctestnet",
"protocol": "ethereum",
"displayName": "BSC Testnet",
"nativeToken": {
"decimals": 18,
"name": "BNB",
"symbol": "BNB"
},
"rpcUrls": [
{
"http": "https://data-seed-prebsc-1-s3.binance.org:8545"
}
],
"blockExplorers": [
{
"name": "BscScan",
"url": "https://testnet.bscscan.com",
"apiUrl": "https://api-testnet.bscscan.com/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 9,
"estimateBlockTime": 3
},
"isTestnet": true
},
"goerli": {
"chainId": 5,
"domainId": 5,
"name": "goerli",
"protocol": "ethereum",
"displayName": "Goerli",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
},
{
"http": "https://rpc.ankr.com/eth_goerli"
},
{
"http": "https://eth-goerli.public.blastapi.io"
}
],
"blockExplorers": [
{
"name": "Etherscan",
"url": "https://goerli.etherscan.io",
"apiUrl": "https://api-goerli.etherscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 2,
"estimateBlockTime": 13
},
"isTestnet": true
},
"moonbasealpha": {
"chainId": 1287,
"domainId": 1287,
"name": "moonbasealpha",
"protocol": "ethereum",
"displayName": "Moonbase Alpha",
"displayNameShort": "Moonbase",
"nativeToken": {
"decimals": 18,
"name": "DEV",
"symbol": "DEV"
},
"rpcUrls": [
{
"http": "https://rpc.api.moonbase.moonbeam.network"
}
],
"blockExplorers": [
{
"name": "MoonScan",
"url": "https://moonbase.moonscan.io",
"apiUrl": "https://api-moonbase.moonscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 1,
"estimateBlockTime": 12
},
"isTestnet": true
},
"optimismgoerli": {
"chainId": 420,
"domainId": 420,
"name": "optimismgoerli",
"protocol": "ethereum",
"displayName": "Optimism Goerli",
"displayNameShort": "Opt. Goerli",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://goerli.optimism.io"
}
],
"blockExplorers": [
{
"name": "Etherscan",
"url": "https://goerli-optimism.etherscan.io",
"apiUrl": "https://api-goerli-optimism.etherscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 1,
"estimateBlockTime": 3
},
"isTestnet": true
},
"arbitrumgoerli": {
"chainId": 421613,
"domainId": 421613,
"name": "arbitrumgoerli",
"protocol": "ethereum",
"displayName": "Arbitrum Goerli",
"displayNameShort": "Arb. Goerli",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://goerli-rollup.arbitrum.io/rpc"
}
],
"blockExplorers": [
{
"name": "Arbiscan",
"url": "https://goerli.arbiscan.io",
"apiUrl": "https://api-goerli.arbiscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 1,
"estimateBlockTime": 3
},
"isTestnet": true
},
"sepolia": {
"chainId": 11155111,
"domainId": 11155111,
"name": "sepolia",
"protocol": "ethereum",
"displayName": "Sepolia",
"nativeToken": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://endpoints.omniatech.io/v1/eth/sepolia/public"
},
{
"http": "https://rpc.sepolia.org"
}
],
"blockExplorers": [
{
"name": "Etherscan",
"url": "https://sepolia.etherscan.io",
"apiUrl": "https://api-sepolia.etherscan.io/api",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 2,
"estimateBlockTime": 13
},
"isTestnet": true
},
"solanadevnet": {
"chainId": 1399811151,
"name": "solanadevnet",
"rpcUrls": [
{
"http": "https://api.devnet.solana.com"
}
]
},
"proteustestnet": {
"chainId": 88002,
"domainId": 88002,
"name": "proteustestnet",
"protocol": "ethereum",
"displayName": "Proteus Testnet",
"nativeToken": {
"name": "Zebec",
"symbol": "ZBC",
"decimals": 18
},
"rpcUrls": [
{
"http": "https://api.proteus.nautchain.xyz/solana"
}
]
}
}

@ -0,0 +1,32 @@
{
"solanadevnet": {
"interchainSecurityModule": "64xkGhsZbxgP5rBJfpPcpmzkzTGkpSVHiDLcMKS5gmQw"
},
"alfajores": {
"foreignDeployment": "0x477D860f8F41bC69dDD32821F2Bf2C2Af0243F16"
},
"fuji": {
"foreignDeployment": "0x5da3b8d6F73dF6003A490072106730218c475AAd"
},
"mumbai": {
"foreignDeployment": "0x1A4d8a5eD6C93Af828655e15C44eeE2c2851F0D6"
},
"bsctestnet": {
"foreignDeployment": "0xE09BF59dCA6e622efC33f6fbd8EF85dE45233388"
},
"goerli": {
"foreignDeployment": "0x405BFdEcB33230b4Ad93C29ba4499b776CfBa189"
},
"moonbasealpha": {
"foreignDeployment": "0x89e02C3C7b97bCBa63279E10E2a44e6cEF69E6B2"
},
"optimismgoerli": {
"foreignDeployment": "0x3582d1238cBC812165981E4fFaB0E8D9a4518910"
},
"arbitrumgoerli": {
"foreignDeployment": "0x339B46496D60b1b6B42e9715DeD8B3D2154dA0Bb"
},
"sepolia": {
"foreignDeployment": "0x5d56B8a669F50193b54319442c6EEE5edD662381"
}
}

@ -0,0 +1 @@
[42,226,42,33,87,42,251,0,57,248,173,166,139,84,91,50,218,150,183,254,74,195,88,116,92,195,145,231,63,39,9,98,171,58,146,166,209,139,158,82,151,114,58,235,5,25,129,244,219,192,239,35,53,229,191,115,243,59,174,210,94,26,161,101]

@ -0,0 +1,42 @@
{
"bsctestnet": {
"hex": "0x000000000000000000000000e09bf59dca6e622efc33f6fbd8ef85de45233388",
"base58": "11111111111148VaL9DFuVc9DbDjRR7c3qyCEjyy"
},
"optimismgoerli": {
"hex": "0x0000000000000000000000003582d1238cbc812165981e4ffab0e8d9a4518910",
"base58": "111111111111kEreeMSXc3Nh2JoYtisyZj8pb6X"
},
"fuji": {
"hex": "0x0000000000000000000000005da3b8d6f73df6003a490072106730218c475aad",
"base58": "1111111111112JfXZf7EYaEMM1st6wFZbcLN2uwA"
},
"solanadevnet": {
"hex": "0xab3a92a6d18b9e5297723aeb051981f4dbc0ef2335e5bf73f33baed25e1aa165",
"base58": "CXQX54kdkU5GqdRJjCmHpwHfEMgFb5SeBmMWntP2Ds7J"
},
"mumbai": {
"hex": "0x0000000000000000000000001a4d8a5ed6c93af828655e15c44eee2c2851f0d6",
"base58": "111111111111NFiXSaSzEqfGUNtwSd5dSDgCymP"
},
"alfajores": {
"hex": "0x000000000000000000000000477d860f8f41bc69ddd32821f2bf2c2af0243f16",
"base58": "111111111111zmUjMVNXAe5bcqPR8cvaPz5SrQu"
},
"goerli": {
"hex": "0x000000000000000000000000405bfdecb33230b4ad93c29ba4499b776cfba189",
"base58": "111111111111u1H27LrKRuu1G7bDpPWUXKphQSt"
},
"sepolia": {
"hex": "0x0000000000000000000000005d56b8a669f50193b54319442c6eee5edd662381",
"base58": "1111111111112JRRxgtLh6eyMDsTHUehn6bJcPJ8"
},
"arbitrumgoerli": {
"hex": "0x000000000000000000000000339b46496d60b1b6b42e9715ded8b3d2154da0bb",
"base58": "111111111111ihbsGG5PRTKTSYSGewGtDFs2vfc"
},
"moonbasealpha": {
"hex": "0x00000000000000000000000089e02c3c7b97bcba63279e10e2a44e6cef69e6b2",
"base58": "1111111111112vQhuwgKwhQ7SM1HZEm6yXQkzCau"
}
}

@ -0,0 +1,32 @@
{
"solanadevnet": {
"interchainSecurityModule": "2NE6Y1rXp1Kpp6vBNqDHYL7HNk7iqh8BKmvCoZtUcZLn"
},
"alfajores": {
"foreignDeployment": "0x40Adcb03F3C58170b4751c4140636FC6085Ff475"
},
"fuji": {
"foreignDeployment": "0xAc003FcDD0EE223664F2A000B5A59D082745700b"
},
"mumbai": {
"foreignDeployment": "0xaB0892029C3E7dD4c0235590dc296E618A7b4d03"
},
"bsctestnet": {
"foreignDeployment": "0xd259b0e793535325786675542aB296c451535c27"
},
"goerli": {
"foreignDeployment": "0x03e9531ae74e8F0f96DE26788a22d35bdaD24185"
},
"moonbasealpha": {
"foreignDeployment": "0xE9D6317a10860340f035f3d09052D9d376855bE8"
},
"optimismgoerli": {
"foreignDeployment": "0x057d38d184d74192B96840D8FbB37e584dDb569A"
},
"arbitrumgoerli": {
"foreignDeployment": "0xaAF1BF6f2BfaE290ea8615066fd167e396a2f578"
},
"sepolia": {
"foreignDeployment": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217"
}
}

@ -0,0 +1 @@
[158,232,241,234,223,84,236,122,65,31,146,220,11,236,43,97,184,113,181,80,237,157,204,188,166,199,112,171,77,38,68,13,187,162,244,131,230,66,68,157,10,57,239,229,249,96,63,124,85,148,35,172,235,211,200,84,208,117,96,204,208,67,146,40]

@ -0,0 +1,42 @@
{
"sepolia": {
"hex": "0x0000000000000000000000006ad4deba8a147d000c09de6465267a9047d1c217",
"base58": "1111111111112VKnX2KMsqSTDw9YoXRsZJTwTcUW"
},
"goerli": {
"hex": "0x00000000000000000000000003e9531ae74e8f0f96de26788a22d35bdad24185",
"base58": "1111111111114AKBbRbDjAP93LQgmXJPvfVU7SC"
},
"solanadevnet": {
"hex": "0xbba2f483e642449d0a39efe5f9603f7c559423acebd3c854d07560ccd0439228",
"base58": "DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7"
},
"optimismgoerli": {
"hex": "0x000000000000000000000000057d38d184d74192b96840d8fbb37e584ddb569a",
"base58": "1111111111115SFp65pWdvPTRK5fmHa3sc4Eq6Z"
},
"fuji": {
"hex": "0x000000000000000000000000ac003fcdd0ee223664f2a000b5a59d082745700b",
"base58": "1111111111113Pz2bmxxVNgkKkZPpxgouHiZAjTx"
},
"moonbasealpha": {
"hex": "0x000000000000000000000000e9d6317a10860340f035f3d09052d9d376855be8",
"base58": "1111111111114Fx2onL6wvVgGmyjgzGhy48HzCZM"
},
"arbitrumgoerli": {
"hex": "0x000000000000000000000000aaf1bf6f2bfae290ea8615066fd167e396a2f578",
"base58": "1111111111113P8WPEsejkHP1Zysy1xXafVFFnaT"
},
"bsctestnet": {
"hex": "0x000000000000000000000000d259b0e793535325786675542ab296c451535c27",
"base58": "1111111111113vyKMMTb6aSQDhDLqEvqcPBcTtRC"
},
"alfajores": {
"hex": "0x00000000000000000000000040adcb03f3c58170b4751c4140636fc6085ff475",
"base58": "111111111111uGFbQYrmpk8K5cfeu9x438LAGiQ"
},
"mumbai": {
"hex": "0x000000000000000000000000ab0892029c3e7dd4c0235590dc296e618a7b4d03",
"base58": "1111111111113PCgiXuWFu2FmvhykJp51x5y5jyC"
}
}

@ -0,0 +1,92 @@
{
"alfajores": {
"type": 3,
"threshold": 2,
"validators": [
"0xe6072396568e73ce6803b12b7e04164e839f1e54",
"0x9f177f51289b22515f41f95872e1511391b8e105",
"0x15f77400845eb1c971ad08de050861d5508cad6c"
]
},
"fuji": {
"type": 3,
"threshold": 2,
"validators": [
"0x9fa19ead5ec76e437948b35e227511b106293c40",
"0x227e7d6507762ece0c94678f8c103eff9d682476",
"0x2379e43740e4aa4fde48cf4f00a3106df1d8420d"
]
},
"mumbai": {
"type": 3,
"threshold": 2,
"validators": [
"0x0a664ea799447da6b15645cf8b9e82072a68343f",
"0x6ae6f12929a960aba24ba74ea310e3d37d0ac045",
"0x51f70c047cd73bc7873273707501568857a619c4"
]
},
"bsctestnet": {
"type": 3,
"threshold": 2,
"validators": [
"0x23338c8714976dd4a57eaeff17cbd26d7e275c08",
"0x85a618d7450ebc37e0d682371f08dac94eec7a76",
"0x95b76562e4ba1791a27ba4236801271c9115b141"
]
},
"goerli": {
"type": 3,
"threshold": 2,
"validators": [
"0xf43fbd072fd38e1121d4b3b0b8a35116bbb01ea9",
"0xa33020552a21f35e75bd385c6ab95c3dfa82d930",
"0x0bba4043ff242f8bf3f39bafa8930a84d644d947"
]
},
"sepolia": {
"type": 3,
"threshold": 2,
"validators": [
"0xbc748ee311f5f2d1975d61cdf531755ce8ce3066",
"0xc4233b2bfe5aec08964a94b403052abb3eafcf07",
"0x6b36286c19f5c10bdc139ea9ee7f82287303f61d"
]
},
"moonbasealpha": {
"type": 3,
"threshold": 2,
"validators": [
"0x890c2aeac157c3f067f3e42b8afc797939c59a32",
"0x1b06d6fe69b972ed7420c83599d5a5c0fc185904",
"0xe70b85206a968a99a597581f0fa09c99e7681093"
]
},
"optimismgoerli": {
"type": 3,
"threshold": 2,
"validators": [
"0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189",
"0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65",
"0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453"
]
},
"arbitrumgoerli": {
"type": 3,
"threshold": 2,
"validators": [
"0xce798fa21e323f6b24d9838a10ffecdefdfc4f30",
"0xa792d39dca4426927e0f00c1618d61c9cb41779d",
"0xdf181fcc11dfac5d01467e4547101a856dd5aa04"
]
},
"proteustestnet": {
"type": 3,
"threshold": 2,
"validators": [
"0x79fc73656abb9eeaa5ee853c4569124f5bdaf9d8",
"0x72840388d5ab57323bc4f6e6d3ddedfd5cc911f0",
"0xd4b2a50c53fc6614bb3cd3198e0fdc03f5da973f"
]
}
}

@ -0,0 +1 @@
[187,239,78,162,24,178,190,184,243,9,66,169,19,139,40,129,55,222,218,2,184,14,122,68,163,6,144,157,76,14,169,237,20,75,176,226,241,81,96,106,31,68,222,130,94,67,105,175,112,84,241,60,117,11,107,135,95,48,20,213,115,123,100,3]

@ -0,0 +1,65 @@
{
"alfajores": {
"type": 3,
"threshold": 1,
"validators": [
"0x45e5c228b38e1cf09e9a3423ed0cf4862c4bf3de"
]
},
"fuji": {
"type": 3,
"threshold": 1,
"validators": [
"0xd81ba169170a9b582812cf0e152d2c168572e21f"
]
},
"mumbai": {
"type": 3,
"threshold": 1,
"validators": [
"0xb537c4ce34e1cad718be52aa30b095e416eae46a"
]
},
"bsctestnet": {
"type": 3,
"threshold": 1,
"validators": [
"0x77f80ef5b18977e15d81aea8dd3a88e7df4bc0eb"
]
},
"goerli": {
"type": 3,
"threshold": 1,
"validators": [
"0x9597ddb4ad2af237665559574b820596bb77ae7a"
]
},
"sepolia": {
"type": 3,
"threshold": 1,
"validators": [
"0x183f15924f3a464c54c9393e8d268eb44d2b208c"
]
},
"moonbasealpha": {
"type": 3,
"threshold": 1,
"validators": [
"0xbeaf158f85d7b64ced36b8aea0bbc4cd0f2d1a5d"
]
},
"optimismgoerli": {
"type": 3,
"threshold": 1,
"validators": [
"0x1d6798671ac532f2bf30c3a5230697a4695705e4"
]
},
"arbitrumgoerli": {
"type": 3,
"threshold": 1,
"validators": [
"0x6d13367c7cd713a4ea79a2552adf824bf1ecdd5e"
]
}
}

@ -0,0 +1,3 @@
{
"program_id": "2NE6Y1rXp1Kpp6vBNqDHYL7HNk7iqh8BKmvCoZtUcZLn"
}

@ -1 +1 @@
[63,21,4,198,48,27,204,153,114,92,118,116,234,163,49,14,128,10,0,19,56,226,121,151,6,205,21,108,169,125,212,113,29,16,150,112,133,212,123,146,110,230,188,148,124,117,183,159,93,85,69,97,122,78,86,187,44,166,129,154,160,73,41,186] [247,149,169,2,196,128,74,124,111,206,244,112,63,16,180,19,219,212,45,229,21,114,33,11,202,148,12,47,22,26,192,78,75,78,53,149,190,51,57,253,29,141,136,215,159,45,181,164,239,148,140,163,30,108,158,76,94,113,11,4,142,0,192,20]

@ -1,7 +1,7 @@
{ {
"mailbox": "4v25Dz9RccqUrTzmfHzJMsjd1iVoNrWzeJ4o6GYuJrVn", "mailbox": "4v25Dz9RccqUrTzmfHzJMsjd1iVoNrWzeJ4o6GYuJrVn",
"validator_announce": "CMHKvdq4CopDf7qXnDCaTybS15QekQeRt4oUB219yxsp", "validator_announce": "CMHKvdq4CopDf7qXnDCaTybS15QekQeRt4oUB219yxsp",
"multisig_ism_message_id": "2xTVcwDWZgBu69aawCdYHXqH7xQP36iBQ7rN2px1g7ms", "multisig_ism_message_id": "64xkGhsZbxgP5rBJfpPcpmzkzTGkpSVHiDLcMKS5gmQw",
"igp_program_id": "HyPQPLfGXDTAQTxzGp7r1uy18KxS89GKgreSHpjeuYDn", "igp_program_id": "HyPQPLfGXDTAQTxzGp7r1uy18KxS89GKgreSHpjeuYDn",
"overhead_igp_account": "AR4hjWPqXEobLvzmv8MTh5k4Se49iTDzbvNX4DpdQGJZ", "overhead_igp_account": "AR4hjWPqXEobLvzmv8MTh5k4Se49iTDzbvNX4DpdQGJZ",
"igp_account": "7hMPEGdgBQFsjEz3aaNwZp8WMFHs615zAM3erXBDJuJR" "igp_account": "7hMPEGdgBQFsjEz3aaNwZp8WMFHs615zAM3erXBDJuJR"

@ -1,29 +0,0 @@
{
"solanadevnet": {
"chainId": 1399811151,
"name": "solanadevnet",
"publicRpcUrls": [
{
"http": "https://api.devnet.solana.com"
}
]
},
"bsctestnet": {
"chainId": 97,
"name": "bsctestnet",
"publicRpcUrls": [
{
"http": "https://rpc.ankr.com/bsc_testnet_chapel"
}
]
},
"proteustestnet": {
"chainId": 88002,
"name": "proteustestnet",
"publicRpcUrls": [
{
"http": "https://api.proteus.nautchain.xyz/solana"
}
]
}
}

@ -1,14 +1,14 @@
{ {
"bsctestnet": { "solanadevnet": {
"hex": "0x00000000000000000000000031b5234a896fbc4b3e2f7237592d054716762131", "hex": "0x05b6502b1d91c60ca0c0d0ab20a16ec40c66f2559becc7888a4fc3c0cefff9a5",
"base58": "111111111111hAc1aTgvQGRBFHrYpXpfUqGyqgk" "base58": "PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx"
}, },
"proteustestnet": { "proteustestnet": {
"hex": "0x00000000000000000000000034a9af13c5555bad0783c220911b9ef59cfdbcef", "hex": "0x00000000000000000000000034a9af13c5555bad0783c220911b9ef59cfdbcef",
"base58": "111111111111jZ775N1rpEpJ2M8RAzLNNr9Lh7U" "base58": "111111111111jZ775N1rpEpJ2M8RAzLNNr9Lh7U"
}, },
"solanadevnet": { "bsctestnet": {
"hex": "0x05b6502b1d91c60ca0c0d0ab20a16ec40c66f2559becc7888a4fc3c0cefff9a5", "hex": "0x00000000000000000000000031b5234a896fbc4b3e2f7237592d054716762131",
"base58": "PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx" "base58": "111111111111hAc1aTgvQGRBFHrYpXpfUqGyqgk"
} }
} }

@ -154,11 +154,15 @@ where
{ {
/// Stores the account data in the given account, reallocing the account /// Stores the account data in the given account, reallocing the account
/// if necessary, and ensuring it is rent exempt. /// if necessary, and ensuring it is rent exempt.
/// Requires `_system_program_info` to be passed in despite not being directly
/// used to ensure that a CPI to the system program in the case of a realloc
/// that requires additional lamports for rent exemption will be successful.
pub fn store_with_rent_exempt_realloc<'a, 'b>( pub fn store_with_rent_exempt_realloc<'a, 'b>(
&self, &self,
account_info: &'a AccountInfo<'b>, account_info: &'a AccountInfo<'b>,
rent: &Rent, rent: &Rent,
payer_info: &'a AccountInfo<'b>, payer_info: &'a AccountInfo<'b>,
_system_program_info: &'a AccountInfo<'b>,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
let required_size = self.size(); let required_size = self.size();

@ -200,3 +200,61 @@ pub fn transfer_ownership_instruction(
Ok(instruction) Ok(instruction)
} }
/// Gets an instruction to set the ISM.
pub fn set_interchain_security_module_instruction(
program_id: Pubkey,
owner_payer: Pubkey,
new_interchain_security_module: Option<Pubkey>,
) -> Result<SolanaInstruction, ProgramError> {
let (token_key, _token_bump) =
Pubkey::try_find_program_address(hyperlane_token_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
let ixn = Instruction::SetInterchainSecurityModule(new_interchain_security_module);
// Accounts:
// 0. [writeable] The token PDA account.
// 1. [signer] The current owner.
let accounts = vec![
AccountMeta::new(token_key, false),
AccountMeta::new_readonly(owner_payer, true),
];
let instruction = SolanaInstruction {
program_id,
data: ixn.encode()?,
accounts,
};
Ok(instruction)
}
/// Sets the igp for a warp route
pub fn set_igp_instruction(
program_id: Pubkey,
owner_payer: Pubkey,
igp_program_and_account: Option<(Pubkey, InterchainGasPaymasterType)>,
) -> Result<SolanaInstruction, ProgramError> {
let (token_key, _token_bump) =
Pubkey::try_find_program_address(hyperlane_token_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
let ixn = Instruction::SetInterchainGasPaymaster(igp_program_and_account);
// Accounts:
// 0. [writeable] The token PDA account.
// 1. [signer] The current owner.
let accounts = vec![
AccountMeta::new(token_key, false),
AccountMeta::new_readonly(owner_payer, true),
];
let instruction = SolanaInstruction {
program_id,
data: ixn.encode()?,
accounts,
};
Ok(instruction)
}

@ -661,6 +661,7 @@ where
token_account, token_account,
&Rent::get()?, &Rent::get()?,
owner_account, owner_account,
system_program,
)?; )?;
Ok(()) Ok(())
@ -709,6 +710,7 @@ where
token_account, token_account,
&Rent::get()?, &Rent::get()?,
owner_account, owner_account,
system_program,
)?; )?;
Ok(()) Ok(())
@ -849,6 +851,7 @@ where
token_account, token_account,
&Rent::get()?, &Rent::get()?,
owner_account, owner_account,
system_program,
)?; )?;
Ok(()) Ok(())

@ -0,0 +1,30 @@
cargo-features = ["workspace-inheritance"]
[package]
name = "hyperlane-sealevel-hello-world"
version = "0.1.0"
edition = "2021"
[features]
no-entrypoint = []
test-client = ["dep:solana-program-test", "dep:solana-sdk", "dep:hyperlane-test-utils", "dep:spl-noop"]
[dependencies]
borsh.workspace = true
solana-program-test = { workspace = true, optional = true }
solana-program.workspace = true
solana-sdk = { workspace = true, optional = true }
spl-noop = { workspace = true, optional = true }
access-control = { path = "../../libraries/access-control" }
account-utils = { path = "../../libraries/account-utils" }
hyperlane-core = { path = "../../../hyperlane-core" }
hyperlane-sealevel-mailbox = { path = "../mailbox", features = ["no-entrypoint"] }
hyperlane-sealevel-igp = { path = "../hyperlane-sealevel-igp", features = ["no-entrypoint"] }
hyperlane-sealevel-connection-client = { path = "../../libraries/hyperlane-sealevel-connection-client" }
hyperlane-sealevel-message-recipient-interface = { path = "../../libraries/message-recipient-interface" }
hyperlane-test-utils = { path = "../../libraries/test-utils", optional = true }
serializable-account-meta = { path = "../../libraries/serializable-account-meta" }
[lib]
crate-type = ["cdylib", "lib"]

@ -0,0 +1,104 @@
//! HelloWorld accounts.
use std::collections::HashMap;
use access_control::AccessControl;
use account_utils::{AccountData, SizedData};
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::H256;
use hyperlane_sealevel_connection_client::{
router::{HyperlaneRouter, RemoteRouterConfig},
HyperlaneConnectionClient,
};
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
/// The storage account.
pub type HelloWorldStorageAccount = AccountData<HelloWorldStorage>;
/// The storage account's data.
#[derive(BorshSerialize, BorshDeserialize, Debug, Default)]
pub struct HelloWorldStorage {
/// The local domain.
pub local_domain: u32,
/// The mailbox.
pub mailbox: Pubkey,
/// The ISM.
pub ism: Option<Pubkey>,
/// The IGP.
pub igp: Option<(Pubkey, InterchainGasPaymasterType)>,
/// The owner.
pub owner: Option<Pubkey>,
/// A counter of how many messages have been sent from this contract.
pub sent: u64,
/// A counter of how many messages have been received by this contract.
pub received: u64,
/// Keyed by domain, a counter of how many messages that have been sent
/// from this contract to the domain.
pub sent_to: HashMap<u32, u64>,
/// Keyed by domain, a counter of how many messages that have been received
/// by this contract from the domain.
pub received_from: HashMap<u32, u64>,
/// Keyed by domain, the router for the remote domain.
pub routers: HashMap<u32, H256>,
}
impl SizedData for HelloWorldStorage {
fn size(&self) -> usize {
// local domain
std::mem::size_of::<u32>() +
// mailbox
32 +
// ism
1 + 32 +
// igp
1 + 32 + 1 + 32 +
// owner
1 + 32 +
// sent
std::mem::size_of::<u64>() +
// received
std::mem::size_of::<u64>() +
// sent_to
(self.sent_to.len() * (std::mem::size_of::<u32>() + std::mem::size_of::<u64>())) +
// received_from
(self.received_from.len() * (std::mem::size_of::<u32>() + std::mem::size_of::<u64>())) +
// routers
(self.routers.len() * (std::mem::size_of::<u32>() + 32))
}
}
impl AccessControl for HelloWorldStorage {
fn owner(&self) -> Option<&Pubkey> {
self.owner.as_ref()
}
fn set_owner(&mut self, new_owner: Option<Pubkey>) -> Result<(), ProgramError> {
self.owner = new_owner;
Ok(())
}
}
impl HyperlaneRouter for HelloWorldStorage {
fn router(&self, origin: u32) -> Option<&H256> {
self.routers.get(&origin)
}
fn enroll_remote_router(&mut self, config: RemoteRouterConfig) {
self.routers.insert(config.domain, config.router.unwrap());
}
}
impl HyperlaneConnectionClient for HelloWorldStorage {
fn mailbox(&self) -> &Pubkey {
&self.mailbox
}
fn interchain_gas_paymaster(&self) -> Option<&(Pubkey, InterchainGasPaymasterType)> {
self.igp.as_ref()
}
fn interchain_security_module(&self) -> Option<&Pubkey> {
self.ism.as_ref()
}
}

@ -0,0 +1,146 @@
//! HelloWorld instructions.
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
use solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
};
use crate::program_storage_pda_seeds;
/// Init instruction data.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct Init {
/// The local domain.
pub local_domain: u32,
/// The mailbox.
pub mailbox: Pubkey,
/// The ISM.
pub ism: Option<Pubkey>,
/// The IGP.
pub igp: Option<(Pubkey, InterchainGasPaymasterType)>,
/// The owner.
pub owner: Option<Pubkey>,
}
/// A HelloWorld message.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloWorldMessage {
/// The destination domain.
pub destination: u32,
/// The message.
pub message: String,
}
/// Instructions for the program.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum HelloWorldInstruction {
/// Initializes the program.
Init(Init),
/// Dispatches a message using the dispatch authority.
SendHelloWorld(HelloWorldMessage),
/// Sets the ISM.
SetInterchainSecurityModule(Option<Pubkey>),
/// Enrolls remote routers
EnrollRemoteRouters(Vec<RemoteRouterConfig>),
}
/// Gets an instruction to initialize the program.
pub fn init_instruction(
program_id: Pubkey,
payer: Pubkey,
local_domain: u32,
mailbox: Pubkey,
ism: Option<Pubkey>,
igp: Option<(Pubkey, InterchainGasPaymasterType)>,
owner: Option<Pubkey>,
) -> Result<Instruction, ProgramError> {
let (program_storage_account, _program_storage_bump) =
Pubkey::try_find_program_address(program_storage_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
let init = Init {
local_domain,
mailbox,
ism,
igp,
owner,
};
// Accounts:
// 0. [executable] System program.
// 1. [signer] Payer.
// 2. [writeable] Storage PDA.
let accounts = vec![
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(payer, true),
AccountMeta::new(program_storage_account, false),
];
let instruction = Instruction {
program_id,
data: HelloWorldInstruction::Init(init).try_to_vec()?,
accounts,
};
Ok(instruction)
}
/// Gets an instruction to enroll remote routers.
pub fn enroll_remote_routers_instruction(
program_id: Pubkey,
owner: Pubkey,
configs: Vec<RemoteRouterConfig>,
) -> Result<Instruction, ProgramError> {
let (program_storage_account, _program_storage_bump) =
Pubkey::try_find_program_address(program_storage_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
// Accounts:
// 0. [executable] System program.
// 1. [signer] Payer.
// 2. [writeable] Storage PDA.
let accounts = vec![
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new(program_storage_account, false),
AccountMeta::new(owner, true),
];
let instruction = Instruction {
program_id,
data: HelloWorldInstruction::EnrollRemoteRouters(configs).try_to_vec()?,
accounts,
};
Ok(instruction)
}
/// Gets an instruction to set the interchain security module.
pub fn set_interchain_security_module_instruction(
program_id: Pubkey,
owner: Pubkey,
ism: Option<Pubkey>,
) -> Result<Instruction, ProgramError> {
let (program_storage_account, _program_storage_bump) =
Pubkey::try_find_program_address(program_storage_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
// Accounts:
// 0. [writeable] Storage PDA account.
// 1. [signer] Owner.
let accounts = vec![
AccountMeta::new(program_storage_account, false),
AccountMeta::new(owner, true),
];
let instruction = Instruction {
program_id,
data: HelloWorldInstruction::SetInterchainSecurityModule(ism).try_to_vec()?,
accounts,
};
Ok(instruction)
}

@ -0,0 +1,9 @@
//! A HelloWorld program that sends and receives messages to & from other routers.
#![deny(warnings)]
#![deny(missing_docs)]
#![deny(unsafe_code)]
pub mod accounts;
pub mod instruction;
pub mod processor;

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

Loading…
Cancel
Save