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. 32
      rust/agents/relayer/src/msg/pending_message.rs
  10. 47
      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. 36
      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. 75
      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. 56
      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. 476
      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. 891
      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
*.ts @yorhodes @jmrossy @nambrot
*.rs @mattiecnvr @tkporter @daniel-savu
*.rs @tkporter @daniel-savu
*.md @Skunkchain @nambrot @avious00
# Package owners
@ -11,7 +11,7 @@
solidity/ @yorhodes @tkporter @aroralanuk
## Agents
rust/ @mattiecnvr @tkporter @daniel-savu
rust/ @tkporter @daniel-savu
## SDK
typescript/sdk @yorhodes @jmrossy
@ -23,4 +23,4 @@ typescript/token @yorhodes @jmrossy @tkporter @aroralanuk
typescript/helloworld @yorhodes @nambrot
## 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.
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

@ -1,3 +1,3 @@
[toolchain]
channel = "1.71.1"
channel = "1.72.1"
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/test-transaction-utils",
"sealevel/libraries/test-utils",
"sealevel/programs/helloworld",
"sealevel/programs/hyperlane-sealevel-token",
"sealevel/programs/hyperlane-sealevel-token-collateral",
"sealevel/programs/hyperlane-sealevel-token-native",
@ -55,7 +56,7 @@ auto_impl = "1.0"
backtrace = "0.3"
base64 = "0.21.2"
bincode = "1.3"
borsh = "0.9"
borsh = "0.9" # 0.9 is needed for solana 1.14
bs58 = "0.5.0"
bytes = "1"
clap = "4"
@ -76,7 +77,7 @@ fuels = "0.38"
fuels-code-gen = "0.38"
futures = "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
hex = "0.4"
itertools = "0.11.0"
@ -85,7 +86,7 @@ log = "0.4"
macro_rules_attribute = "0.2"
maplit = "1.0"
mockall = "0.11"
nix = { version = "0.26", default-features = false }
nix = { version = "0.27", default-features = false }
num = "0.4"
num-bigint = "0.4"
num-derive = "0.4.0"
@ -99,8 +100,8 @@ regex = "1.5"
reqwest = "0.11"
rlp = "=0.5.2"
rocksdb = "0.21.0"
sea-orm = { version = "0.11.1", 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 = { version = "0.12.3", features = ["sqlx-postgres", "runtime-tokio-native-tls", "with-bigdecimal", "with-time", "macros"] }
sea-orm-migration = { version = "0.12.3", features = ["sqlx-postgres", "runtime-tokio-native-tls"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11"

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

@ -5,14 +5,14 @@
- install `rustup`
- [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:
```
$ rustup --version
rustup 1.26.0 (5af9b9484 2023-04-05)
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

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

@ -1,17 +1,17 @@
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::{
fmt::{Debug, Formatter},
sync::Arc,
time::{Duration, Instant},
};
use async_trait::async_trait;
use derive_new::new;
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 tracing::{debug, error, info, instrument, trace, warn};
use hyperlane_base::CoreMetrics;
use hyperlane_core::{HyperlaneChain, HyperlaneDomain, HyperlaneMessage, Mailbox, U256};
use super::{
gas_payment::GasPaymentEnforcer,
metadata::{BaseMetadataBuilder, MetadataBuilder},
@ -183,11 +183,10 @@ impl PendingOperation for PendingMessage {
.message_meets_gas_payment_requirement(&self.message, &tx_cost_estimate)
.await,
"checking if message meets gas payment requirement"
)
else {
info!(?tx_cost_estimate, "Gas payment requirement not met yet");
return self.on_reprepare();
};
) else {
info!(?tx_cost_estimate, "Gas payment requirement not met yet");
return self.on_reprepare();
};
// Go ahead and attempt processing of message to destination chain.
debug!(
@ -393,9 +392,12 @@ impl PendingMessage {
i if (1..12).contains(&i) => 10,
// wait 90s to 19.5min with a linear increase
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
// ~60min timeout (64min to be more precise).
i => (2u64).pow(i - 21) + 60 * 30,
// wait 30min for the next 12 attempts
i if (24..36).contains(&i) => 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::time::Instant;
use std::{cmp::Ordering, time::Instant};
use async_trait::async_trait;
use enum_dispatch::enum_dispatch;
use eyre::Report;
use hyperlane_core::HyperlaneDomain;
#[allow(unused_imports)] // required for enum_dispatch
@ -123,29 +121,30 @@ macro_rules! make_op_try {
/// Handle a result and either return early with retry or a critical failure on
/// error.
macro_rules! op_try {
(critical: $e:expr, $ctx:literal) => {
match $e {
Ok(v) => v,
Err(e) => {
error!(error=?e, concat!("Error when ", $ctx));
return PendingOperationResult::CriticalFailure(
Err::<(), _>(e)
.context(concat!("When ", $ctx))
.unwrap_err()
);
(critical: $e:expr, $ctx:literal) => {
match $e {
Ok(v) => v,
Err(e) => {
error!(error=?e, concat!("Error when ", $ctx));
return PendingOperationResult::CriticalFailure(
Err::<(), _>(e)
.context(concat!("When ", $ctx))
.unwrap_err()
);
}
}
}
};
($e:expr, $ctx:literal) => {
match $e {
Ok(v) => v,
Err(e) => {
warn!(error=?e, concat!("Error when ", $ctx));
return $on_retry();
};
($e:expr, $ctx:literal) => {
match $e {
Ok(v) => v,
Err(e) => {
warn!(error=?e, concat!("Error when ", $ctx));
#[allow(clippy::redundant_closure_call)]
return $on_retry();
}
}
}
};
}
};
}
};
}

@ -390,7 +390,7 @@ mod test {
.await;
// 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
.into_iter()
.enumerate()

@ -18,7 +18,7 @@ sea-orm.workspace = true
sea-orm-migration.workspace = true
serde.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
tracing-subscriber.workspace = true

@ -1,18 +1,13 @@
use std::path::Path;
use std::process::Stdio;
use std::time::Duration;
use tokio::fs::remove_dir_all;
use tokio::process::Command;
use tokio::time::sleep;
use std::{path::Path, process::Stdio, time::Duration};
use common::*;
use tokio::{fs::remove_dir_all, process::Command, time::sleep};
mod common;
const RAW_DB_PATH: &str = "./agents/scraper/src/db/generated";
const DOCKER_NAME: &str = "scraper-entity-generator";
const CLI_VERSION: &str = "0.11.1";
const CLI_VERSION: &str = "0.12.3";
struct PostgresDockerContainer;
@ -58,7 +53,7 @@ impl Drop for PostgresDockerContainer {
}
}
#[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), DbErr> {
assert_eq!(
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::*;
@ -56,9 +56,7 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(),
Self::Domain => ColumnType::Integer.def(),
Self::Hash => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
.def()
.unique(),
Self::Hash => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
Self::Height => ColumnType::BigInteger.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::*;

@ -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::*;
@ -55,13 +55,9 @@ impl ColumnTrait for Column {
match self {
Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(),
Self::MsgId => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
.def()
.unique(),
Self::MsgId => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
Self::Domain => ColumnType::Integer.def(),
Self::DestinationMailbox => {
ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def()
}
Self::DestinationMailbox => ColumnType::Binary(BlobSize::Blob(None)).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::*;

@ -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::*;
@ -60,7 +60,7 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.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::GasAmount => ColumnType::Decimal(Some((78u32, 0u32))).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::*;
@ -65,18 +65,14 @@ impl ColumnTrait for Column {
match self {
Self::Id => ColumnType::BigInteger.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::Destination => ColumnType::Integer.def(),
Self::Nonce => ColumnType::Integer.def(),
Self::Sender => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(),
Self::MsgBody => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
.def()
.null(),
Self::OriginMailbox => {
ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def()
}
Self::Sender => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::MsgBody => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
Self::OriginMailbox => ColumnType::Binary(BlobSize::Blob(None)).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;

@ -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::cursor::Entity as Cursor;
pub use super::delivered_message::Entity as DeliveredMessage;
pub use super::domain::Entity as Domain;
pub use super::gas_payment::Entity as GasPayment;
pub use super::message::Entity as Message;
pub use super::transaction::Entity as Transaction;
pub use super::{
block::Entity as Block, cursor::Entity as Cursor,
delivered_message::Entity as DeliveredMessage, domain::Entity as Domain,
gas_payment::Entity as GasPayment, message::Entity as Message,
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::*;
@ -73,9 +73,7 @@ impl ColumnTrait for Column {
match self {
Self::Id => ColumnType::BigInteger.def(),
Self::TimeCreated => ColumnType::DateTime.def(),
Self::Hash => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
.def()
.unique(),
Self::Hash => ColumnType::Binary(BlobSize::Blob(None)).def().unique(),
Self::BlockId => ColumnType::BigInteger.def(),
Self::GasLimit => ColumnType::Decimal(Some((78u32, 0u32))).def(),
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::EffectiveGasPrice => ColumnType::Decimal(Some((78u32, 0u32))).def().null(),
Self::Nonce => ColumnType::BigInteger.def(),
Self::Sender => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
.def()
.null(),
Self::Sender => ColumnType::Binary(BlobSize::Blob(None)).def(),
Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
Self::GasUsed => 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::time::Duration;
use ethers::abi::Detokenize;
use ethers::prelude::{NameOrAddress, TransactionReceipt};
use ethers::types::Eip1559TransactionRequest;
use ethers::{
abi::Detokenize,
prelude::{NameOrAddress, TransactionReceipt},
types::Eip1559TransactionRequest,
};
use ethers_contract::builders::ContractCall;
use ethers_core::types::BlockNumber;
use hyperlane_core::{
utils::fmt_bytes, ChainCommunicationError, ChainResult, KnownHyperlaneDomain, H256, U256,
};
use tracing::{error, info};
use hyperlane_core::utils::fmt_bytes;
use hyperlane_core::{ChainCommunicationError, ChainResult, KnownHyperlaneDomain, H256, U256};
use crate::Middleware;
/// 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 {
// Is not EIP 1559 chain
return Ok(tx.gas(gas_limit))
return Ok(tx.gas(gas_limit));
};
let max_priority_fee = if matches!(
KnownHyperlaneDomain::try_from(domain),

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

@ -23,7 +23,9 @@ use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
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;
/// A reference to an IGP contract on some Sealevel chain
@ -126,6 +128,7 @@ impl SealevelInterchainGasPaymasterIndexer {
Ok(Self { rpc_client, igp })
}
#[instrument(err, skip(self))]
async fn get_payment_with_sequence(
&self,
sequence_number: u64,
@ -163,12 +166,15 @@ impl SealevelInterchainGasPaymasterIndexer {
},
with_context: Some(false),
};
tracing::debug!(config=?config, "Fetching program accounts");
let accounts = self
.rpc_client
.get_program_accounts_with_config(&self.igp.program_id, config)
.await
.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
// that proves it's an actual gas payment PDA.
let mut valid_payment_pda_pubkey = Option::<Pubkey>::None;
@ -208,6 +214,8 @@ impl SealevelInterchainGasPaymasterIndexer {
.map_err(ChainCommunicationError::from_other)?
.into_inner();
tracing::debug!(gas_payment_account=?gas_payment_account, "Found gas payment account");
let igp_payment = InterchainGasPayment {
message_id: gas_payment_account.message_id,
destination: gas_payment_account.destination_domain,
@ -251,6 +259,8 @@ impl Indexer<InterchainGasPayment> for SealevelInterchainGasPaymasterIndexer {
let igp_account_filter = self.igp.igp_account;
if igp_account_filter == sealevel_payment.igp_account_pubkey {
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 }
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-fuel = { path = "../chains/hyperlane-fuel" }
hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" }

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

@ -1,7 +1,7 @@
use std::time::{Duration, Instant};
use derive_new::new;
use tracing::debug;
use tracing::warn;
/// Calculates the expected time to catch up to the tip of the blockchain.
#[derive(new)]
@ -38,10 +38,13 @@ impl SyncerEtaCalculator {
self.last_block = current_block;
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;
// 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.
let effective_rate = if let Some(old_rate) = self.effective_rate {
let new_coeff = f64::min(elapsed / self.time_window, 0.9);
@ -66,7 +69,7 @@ impl SyncerEtaCalculator {
{
Ok(eta) => eta,
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
}
}

@ -59,7 +59,9 @@ where
loop {
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 {
CursorAction::Query(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()
.map(|(k, v)| recase_pair(k, v, case))
.collect_vec();
table.extend(tmp.into_iter());
table.extend(tmp);
}
ValueKind::Array(ary) => {
let tmp = ary
.drain(..)
.map(|v| recase_pair(String::new(), v, case).1)
.collect_vec();
ary.extend(tmp.into_iter())
ary.extend(tmp)
}
_ => {}
}

@ -2,9 +2,8 @@ use std::path::PathBuf;
use async_trait::async_trait;
use eyre::{Context, Result};
use prometheus::IntGauge;
use hyperlane_core::{SignedAnnouncement, SignedCheckpoint, SignedCheckpointWithMessageId};
use prometheus::IntGauge;
use crate::traits::CheckpointSyncer;
@ -87,7 +86,7 @@ impl CheckpointSyncer for LocalStorage {
async fn fetch_checkpoint(&self, index: u32) -> Result<Option<SignedCheckpointWithMessageId>> {
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)?;
Ok(Some(checkpoint))

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

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

@ -38,7 +38,7 @@ pub trait Indexer<T: Sized>: Send + Sync + Debug {
/// Interface for indexing data in sequence.
#[async_trait]
#[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
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 borsh::{BorshDeserialize, BorshSerialize};
use num_derive::FromPrimitive;
use serde::{Deserialize, Serialize};
use crate::{ChainResult, HyperlaneContract, HyperlaneMessage, U256};
/// Enumeration of all known module types
#[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))]
pub enum ModuleType {

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

@ -75,6 +75,7 @@ pub fn fmt_domain(domain: u32) -> String {
}
/// Formats the duration in the most appropriate time units.
#[cfg(feature = "float")]
pub fn fmt_duration(dur: Duration) -> String {
const MIN: f64 = 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
/// the duration is 0.
#[cfg(feature = "float")]
pub fn fmt_sync_time(dur: Duration) -> String {
if dur.as_secs() == 0 {
"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-native = { path = "../programs/hyperlane-sealevel-token-native", 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>,
pub commitment: CommitmentConfig,
pub initial_instructions: RefCell<Vec<InstructionWithDescription>>,
pub require_tx_approval: bool,
}
pub(crate) struct InstructionWithDescription {
@ -58,6 +59,7 @@ impl Context {
payer_keypair: Option<PayerKeypair>,
commitment: CommitmentConfig,
initial_instructions: RefCell<Vec<InstructionWithDescription>>,
require_tx_approval: bool,
) -> Self {
Self {
client,
@ -65,6 +67,7 @@ impl Context {
payer_keypair,
commitment,
initial_instructions,
require_tx_approval,
}
}
@ -183,6 +186,13 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
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 recent_blockhash = client.get_latest_blockhash().unwrap();

@ -4,10 +4,12 @@ use solana_program::pubkey::Pubkey;
use solana_sdk::signature::Signer;
use std::collections::HashMap;
use std::{fs::File, io::Write, path::Path};
use std::{fs::File, path::Path};
use crate::{
artifacts::{read_json, write_json},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program},
multisig_ism::deploy_multisig_ism_message_id,
Context, CoreCmd, CoreDeploy, CoreSubCmd,
};
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 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);
@ -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(
ctx: &mut Context,
core: &CoreDeploy,
@ -325,55 +295,23 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct CoreProgramIds {
#[serde(with = "serde_pubkey")]
pub struct CoreProgramIds {
#[serde(with = "crate::serde::serde_pubkey")]
pub mailbox: Pubkey,
#[serde(with = "serde_pubkey")]
#[serde(with = "crate::serde::serde_pubkey")]
pub validator_announce: Pubkey,
#[serde(with = "serde_pubkey")]
#[serde(with = "crate::serde::serde_pubkey")]
pub multisig_ism_message_id: Pubkey,
#[serde(with = "serde_pubkey")]
#[serde(with = "crate::serde::serde_pubkey")]
pub igp_program_id: Pubkey,
#[serde(with = "serde_pubkey")]
#[serde(with = "crate::serde::serde_pubkey")]
pub overhead_igp_account: Pubkey,
#[serde(with = "serde_pubkey")]
#[serde(with = "crate::serde::serde_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) {
let json = serde_json::to_string_pretty(&program_ids).unwrap();
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");
write_json(&core_dir.join("program-ids.json"), program_ids);
}
pub(crate) fn read_core_program_ids(
@ -386,6 +324,5 @@ pub(crate) fn read_core_program_ids(
.join(chain)
.join("core")
.join("program-ids.json");
let file = File::open(path).expect("Failed to open program IDs file");
serde_json::from_reader(file).expect("Failed to read program IDs file")
read_json(&path)
}

@ -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 hyperlane_core::{Encode, HyperlaneMessage, H160, H256};
use hyperlane_core::{H160, H256};
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::{
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,
instruction::{GasOracleConfig, GasOverheadConfig},
};
use hyperlane_sealevel_mailbox::{
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_message_dispatch_authority_pda_seeds, mailbox_outbox_pda_seeds,
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::{
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,
};
use hyperlane_sealevel_token_collateral::{
@ -56,9 +50,7 @@ use hyperlane_sealevel_token_lib::{
hyperlane_token_pda_seeds,
instruction::{Instruction as HtInstruction, TransferRemote as HtTransferRemote},
};
use hyperlane_sealevel_token_native::{
hyperlane_token_native_collateral_pda_seeds, plugin::NativePlugin,
};
use hyperlane_sealevel_token_native::hyperlane_token_native_collateral_pda_seeds;
use hyperlane_sealevel_validator_announce::{
accounts::ValidatorStorageLocationsAccount,
instruction::{
@ -68,15 +60,23 @@ use hyperlane_sealevel_validator_announce::{
replay_protection_pda_seeds, validator_announce_pda_seeds,
validator_storage_locations_pda_seeds,
};
use warp_route::parse_token_account_data;
use crate::warp_route::process_warp_route_cmd;
pub(crate) use crate::{context::*, core::*};
mod artifacts;
mod cmd_utils;
mod context;
mod r#core;
mod helloworld;
mod multisig_ism;
mod router;
mod serde;
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
const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
@ -99,6 +99,8 @@ struct Cli {
heap_size: Option<u32>,
#[arg(long, short = 'C')]
config: Option<String>,
#[arg(long, default_value_t = false)]
require_tx_approval: bool,
}
#[derive(Subcommand)]
@ -110,6 +112,7 @@ enum HyperlaneSealevelCmd {
ValidatorAnnounce(ValidatorAnnounceCmd),
MultisigIsmMessageId(MultisigIsmMessageIdCmd),
WarpRoute(WarpRouteCmd),
HelloWorld(HelloWorldCmd),
}
#[derive(Args)]
@ -194,9 +197,9 @@ enum MailboxSubCmd {
Init(Init),
Query(Query),
Send(Outbox),
Receive(Inbox),
Delivered(Delivered),
TransferOwnership(TransferOwnership),
SetDefaultIsm(SetDefaultIsm),
}
const MAILBOX_PROG_ID: Pubkey = pubkey!("692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1");
@ -221,6 +224,14 @@ struct Query {
program_id: Pubkey,
}
#[derive(Args)]
struct SetDefaultIsm {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, short)]
default_ism: Pubkey,
}
#[derive(Args)]
struct Outbox {
#[arg(long, short, default_value_t = ECLIPSE_DOMAIN)]
@ -259,32 +270,6 @@ struct Delivered {
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)]
struct TokenCmd {
#[command(subcommand)]
@ -297,10 +282,12 @@ enum TokenSubCmd {
TransferRemote(TokenTransferRemote),
EnrollRemoteRouter(TokenEnrollRemoteRouter),
TransferOwnership(TransferOwnership),
SetInterchainSecurityModule(SetInterchainSecurityModule),
Igp(Igp),
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum TokenType {
pub enum TokenType {
Native,
Synthetic,
Collateral,
@ -336,6 +323,14 @@ struct TokenEnrollRemoteRouter {
router: H256,
}
#[derive(Args)]
struct SetInterchainSecurityModule {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, short)]
ism: Option<Pubkey>,
}
#[derive(Args)]
struct TransferOwnership {
#[arg(long, short)]
@ -345,6 +340,39 @@ struct TransferOwnership {
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)]
struct IgpCmd {
#[command(subcommand)]
@ -353,6 +381,7 @@ struct IgpCmd {
#[derive(Subcommand)]
enum IgpSubCmd {
Query(IgpQueryArgs),
PayForGas(PayForGasArgs),
GasOracleConfig(GasOracleConfigArgs),
DestinationGasOverhead(DestinationGasOverheadArgs),
@ -360,6 +389,16 @@ enum IgpSubCmd {
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)]
struct TransferIgpOwnership {
#[arg(long, short)]
@ -373,8 +412,14 @@ struct TransferIgpOwnership {
#[derive(Args)]
struct PayForGasArgs {
#[arg(long)]
program_id: Pubkey,
#[arg(long)]
message_id: String,
#[arg(long)]
destination_domain: u32,
#[arg(long)]
gas: u64,
}
#[derive(Args)]
@ -388,13 +433,7 @@ struct GasOracleConfigArgs {
#[arg(long)]
remote_domain: u32,
#[command(subcommand)]
cmd: GasOracleSubCmd,
}
#[derive(Subcommand)]
enum GasOracleSubCmd {
Set(SetGasOracleArgs),
Get,
cmd: GetSetCmd<GetGasOracleArgs, SetGasOracleArgs>,
}
#[derive(Args)]
@ -407,6 +446,9 @@ struct SetGasOracleArgs {
token_decimals: u8,
}
#[derive(Args)]
struct GetGasOracleArgs;
#[derive(Args)]
struct DestinationGasOverheadArgs {
#[arg(long)]
@ -483,10 +525,36 @@ struct MultisigIsmMessageIdCmd {
#[derive(Subcommand)]
enum MultisigIsmMessageIdSubCmd {
Deploy(MultisigIsmMessageIdDeploy),
Init(MultisigIsmMessageIdInit),
SetValidatorsAndThreshold(MultisigIsmMessageIdSetValidatorsAndThreshold),
Query(MultisigIsmMessageIdInit),
Query(MultisigIsmMessageIdQuery),
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)]
@ -495,6 +563,14 @@ struct MultisigIsmMessageIdInit {
program_id: Pubkey,
}
#[derive(Args)]
struct MultisigIsmMessageIdQuery {
#[arg(long, short)]
program_id: Pubkey,
#[arg(long, value_delimiter = ',')]
domains: Option<Vec<u32>>,
}
#[derive(Args)]
struct MultisigIsmMessageIdSetValidatorsAndThreshold {
#[arg(long, short, default_value_t = MULTISIG_ISM_MESSAGE_ID_PROG_ID)]
@ -507,6 +583,40 @@ struct MultisigIsmMessageIdSetValidatorsAndThreshold {
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() {
pretty_env_logger::init();
@ -569,6 +679,7 @@ fn main() {
payer_keypair,
commitment,
instructions.into(),
cli.require_tx_approval,
);
match cli.cmd {
HyperlaneSealevelCmd::Mailbox(cmd) => process_mailbox_cmd(ctx, cmd),
@ -579,6 +690,7 @@ fn main() {
}
HyperlaneSealevelCmd::Core(cmd) => process_core_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),
}
}
@ -642,7 +754,6 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
destination_domain: outbox.destination,
recipient: H256(outbox.recipient.to_bytes()),
message_body: outbox.message.into(),
// message_body: std::iter::repeat(0x41).take(outbox.message_len).collect(),
});
let outbox_instruction = Instruction {
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();
}
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) => {
let (processed_message_account_key, _processed_message_account_bump) =
Pubkey::find_program_address(
@ -731,6 +798,20 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
)
.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] {
println!("{:#?}", info);
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),
}
}
}
parse_token_account_data(query.token_type, &mut info.data.as_ref());
} else {
println!("Not yet created?");
}
@ -1108,6 +1164,59 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
)
.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 {
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) => {
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(
multisig_ism_message_id_domain_data_pda_seeds!(set_config.domain),
&set_config.program_id,
);
let ixn = MultisigIsmMessageIdInstruction::SetValidatorsAndThreshold(Domained {
domain: set_config.domain,
data: ValidatorsAndThreshold {
validators: set_config.validators,
threshold: set_config.threshold,
},
});
// Accounts:
// 0. `[signer]` The access control owner and payer of the domain PDA.
// 1. `[]` The access control PDA account.
// 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 {
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,
);
IgpSubCmd::Query(query) => {
let (program_data_account_pda, _program_data_account_bump) =
Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id);
let accounts = ctx
.client
.get_multiple_accounts_with_commitment(&[access_control_pda_key], ctx.commitment)
.get_multiple_accounts_with_commitment(
&[program_data_account_pda, query.igp_account],
ctx.commitment,
)
.unwrap()
.value;
let access_control =
AccessControlAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
let igp_program_data =
IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
.unwrap()
.into_inner();
println!("Access control: {:#?}", access_control);
}
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();
}
}
}
println!("IGP program data: {:?}", igp_program_data);
fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
match cmd.cmd {
let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..])
.unwrap()
.into_inner();
println!("IGP account: {:?}", igp);
if let Some(gas_payment_account_pubkey) = query.gas_payment_account {
let account = ctx
.client
.get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment)
.unwrap()
.value
.unwrap();
let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..])
.unwrap()
.into_inner();
println!("Gas payment account: {:?}", gas_payment_account);
}
}
IgpSubCmd::PayForGas(payment_details) => {
let unique_gas_payment_keypair = Keypair::new();
let salt = H256::zero();
@ -1312,8 +1375,8 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
Some(overhead_igp_account),
unique_gas_payment_keypair.pubkey(),
H256::from_str(&payment_details.message_id).unwrap(),
13376,
100000,
payment_details.destination_domain,
payment_details.gas,
)
.unwrap();
@ -1330,7 +1393,7 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
let core_program_ids =
read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name);
match args.cmd {
GasOracleSubCmd::Set(set_args) => {
GetSetCmd::Set(set_args) => {
let remote_gas_data = RemoteGasData {
token_exchange_rate: set_args.token_exchange_rate,
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();
println!("Set gas oracle for remote domain {:?}", args.remote_domain);
}
GasOracleSubCmd::Get => {
// Read the gas oracle config
GetSetCmd::Get(_) => {
let igp_account = ctx
.client
.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 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_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::{
gas_router::GasRouterConfig, router::RemoteRouterConfig,
};
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::{
accounts::HyperlaneTokenAccount,
accounts::{HyperlaneToken, HyperlaneTokenAccount},
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::{
cmd_utils::{
account_exists, create_and_write_keypair, create_new_directory, deploy_program_idempotent,
cmd_utils::account_exists,
core::CoreProgramIds,
router::{
deploy_routers, ChainMetadata, ConnectionClient, Ownable, RouterConfig, RouterConfigGetter,
RouterDeployer,
},
core::{read_core_program_ids, CoreProgramIds},
Context, WarpRouteCmd, WarpRouteSubCmd,
Context, TokenType as FlatTokenType, WarpRouteCmd, WarpRouteSubCmd,
};
/// Configuration relating to decimals.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct DecimalMetadata {
@ -38,6 +49,7 @@ impl DecimalMetadata {
}
}
/// Configuration relating to a Warp Route token.
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
enum TokenType {
@ -47,14 +59,6 @@ enum 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`.
fn gas_overhead_default(&self) -> u64 {
// TODO: note these are the amounts specific to the EVM.
@ -100,406 +104,361 @@ struct CollateralInfo {
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)]
#[serde(rename_all = "camelCase")]
struct TokenConfig {
#[serde(flatten)]
token_type: TokenType,
foreign_deployment: Option<String>,
#[serde(flatten)]
decimal_metadata: DecimalMetadata,
#[serde(flatten)]
ownable: OptionalOwnableConfig,
#[serde(flatten)]
connection_client: OptionalConnectionClientConfig,
router_config: RouterConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RpcUrlConfig {
pub http: String,
pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
match cmd.cmd {
WarpRouteSubCmd::Deploy(deploy) => {
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>,
struct WarpRouteDeployer {
ata_payer_funding_amount: Option<u64>,
}
impl ChainMetadata {
fn client(&self) -> RpcClient {
RpcClient::new_with_commitment(
self.public_rpc_urls[0].http.clone(),
CommitmentConfig::confirmed(),
)
impl WarpRouteDeployer {
fn new(ata_payer_funding_amount: Option<u64>) -> Self {
Self {
ata_payer_funding_amount,
}
}
}
fn domain_id(&self) -> u32 {
self.domain_id.unwrap_or(self.chain_id)
impl WarpRouteDeployer {}
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",
}
}
}
pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
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();
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 token_data = get_token_data::<()>(client, program_id);
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();
token_data.remote_routers
}
let environments_dir =
create_new_directory(&deploy.environments_dir, &deploy.environment);
fn init_program_idempotent(
&self,
ctx: &mut Context,
client: &RpcClient,
core_program_ids: &CoreProgramIds,
chain_config: &ChainMetadata,
app_config: &TokenConfig,
program_id: Pubkey,
) {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
app_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
}
}
let 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 (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
if account_exists(client, &token_pda).unwrap() {
println!("Warp route token already exists, skipping init");
return;
}
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;
let token_configs_to_deploy = token_configs
.clone()
.into_iter()
.filter(|(_, token_config)| token_config.foreign_deployment.is_none())
.collect::<HashMap<_, _>>();
// Deploy to chains that don't have a foreign deployment
for (chain_name, token_config) in token_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 token_config.ownable.owner.is_some() {
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(
&mut ctx,
&keys_dir,
&deploy.environments_dir,
&deploy.environment,
&deploy.built_so_dir,
chain_config,
token_config,
deploy.ata_payer_funding_amount,
);
let domain_id = chain_config.domain_id();
// TODO: consider pulling the setting of defaults into router.rs,
// and possibly have a more distinct connection client abstration.
let mailbox = app_config
.router_config()
.connection_client
.mailbox(core_program_ids.mailbox);
let interchain_security_module = app_config
.router_config()
.connection_client
.interchain_security_module();
let owner = Some(app_config.router_config().ownable.owner(ctx.payer_pubkey));
// Default to the Overhead IGP
let interchain_gas_paymaster = Some(
app_config
.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),
)),
);
println!(
"Initializing Warp Route program: domain_id: {}, mailbox: {}, ism: {:?}, owner: {:?}, igp: {:?}",
domain_id, mailbox, interchain_security_module, owner, interchain_gas_paymaster
);
let init = Init {
mailbox,
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,
ctx.payer_pubkey,
init,
)
.unwrap(),
),
TokenType::Synthetic(_token_metadata) => {
let decimals = init.decimals;
routers.insert(
chain_config.domain_id(),
H256::from_slice(&program_id.to_bytes()[..]),
let init_txn = ctx.new_txn().add(
hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
)
.unwrap(),
);
}
// Now enroll routers
for (chain_name, _) in token_configs_to_deploy {
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 = get_routers(&chain_config.client(), &program_id).unwrap();
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(
enroll_remote_routers_instruction(
program_id,
ctx.payer_pubkey,
router_configs,
)
.unwrap(),
)
.with_client(&chain_config.client())
.send_with_payer();
} else {
println!(
"No router changes for chain: {}, program_id {}",
chain_name, program_id
);
}
// And set destination gas
let configured_destination_gas =
get_destination_gas(&chain_config.client(), &program_id).unwrap();
let expected_destination_gas = token_configs
.iter()
// filter out local chain
.filter(|(dest_chain_name, _)| *dest_chain_name != &chain_name)
.map(|(dest_chain_name, token_config)| {
let domain = chain_configs.get(dest_chain_name).unwrap().domain_id();
(
domain,
GasRouterConfig {
domain,
gas: Some(token_config.token_type.gas_overhead_default()),
},
)
})
.collect::<HashMap<u32, GasRouterConfig>>();
// Destination gas to set or update to a Some value
let destination_gas_to_set = expected_destination_gas
.iter()
.filter(|(domain, expected_config)| {
configured_destination_gas.get(domain) != expected_config.gas.as_ref()
})
.map(|(_, expected_config)| expected_config.clone());
// Destination gas to remove
let destination_gas_to_unset = configured_destination_gas
.iter()
.filter(|(domain, _)| !expected_destination_gas.contains_key(domain))
.map(|(domain, _)| GasRouterConfig {
domain: *domain,
gas: None,
});
// All destination gas config changes
let destination_gas_configs = destination_gas_to_set
.chain(destination_gas_to_unset)
.collect::<Vec<GasRouterConfig>>();
if !destination_gas_configs.is_empty() {
println!(
"Setting destination gas amounts for chain: {}, program_id {}, destination gas: {:?}",
chain_name, program_id, destination_gas_configs,
);
ctx.new_txn()
.add(
set_destination_gas_configs(
program_id,
ctx.payer_pubkey,
destination_gas_configs,
)
.unwrap(),
)
.with_client(&chain_config.client())
.send_with_payer();
} else {
println!(
"No destination gas amount 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();
}
let routers_by_name: HashMap<String, H256> = routers
/// 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
let configured_destination_gas =
get_destination_gas(&chain_config.client(), &program_id).unwrap();
let expected_destination_gas = app_configs
.iter()
.map(|(domain_id, router)| {
// filter out local chain
.filter(|(dest_chain_name, _)| dest_chain_name != chain_name)
.map(|(dest_chain_name, app_config)| {
let domain = chain_configs.get(dest_chain_name).unwrap().domain_id();
(
chain_configs
.iter()
.find(|(_, chain_config)| chain_config.domain_id() == *domain_id)
.unwrap()
.0
.clone(),
*router,
domain,
GasRouterConfig {
domain,
gas: Some(app_config.token_type.gas_overhead_default()),
},
)
})
.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]
);
.collect::<HashMap<u32, GasRouterConfig>>();
// Destination gas to set or update to a Some value
let destination_gas_to_set = expected_destination_gas
.iter()
.filter(|(domain, expected_config)| {
configured_destination_gas.get(domain) != expected_config.gas.as_ref()
})
.map(|(_, expected_config)| expected_config.clone());
// Destination gas to remove
let destination_gas_to_unset = configured_destination_gas
.iter()
.filter(|(domain, _)| !expected_destination_gas.contains_key(domain))
.map(|(domain, _)| GasRouterConfig {
domain: *domain,
gas: None,
});
// All destination gas config changes
let destination_gas_configs = destination_gas_to_set
.chain(destination_gas_to_unset)
.collect::<Vec<GasRouterConfig>>();
if !destination_gas_configs.is_empty() {
let description = format!(
"Setting destination gas amounts for chain: {}, program_id {}, destination gas: {:?}",
chain_name, program_id, destination_gas_configs,
);
ctx.new_txn()
.add_with_description(
set_destination_gas_configs(
program_id,
ctx.payer_pubkey,
destination_gas_configs,
)
.unwrap(),
description,
)
.with_client(&chain_config.client())
.send_with_payer();
} else {
println!(
"No destination gas amount changes for chain: {}, program_id {}",
chain_name, program_id
);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn deploy_warp_route(
ctx: &mut Context,
key_dir: &Path,
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
);
impl RouterConfigGetter for TokenConfig {
fn router_config(&self) -> &RouterConfig {
&self.router_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 {
TokenType::Native => {
println!("Deploying native token");
}
TokenType::Synthetic(_token_metadata) => {
println!("Deploying synthetic token");
}
TokenType::Collateral(_collateral_info) => {
println!("Deploying collateral token");
}
impl Ownable for WarpRouteDeployer {
/// Gets the owner configured on-chain.
fn get_owner(&self, client: &RpcClient, program_id: &Pubkey) -> Option<Pubkey> {
let token = get_token_data::<()>(client, program_id);
token.owner
}
program_id
/// Gets an instruction to set the owner.
fn set_owner_instruction(
&self,
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()
}
}
fn init_warp_route_idempotent(
ctx: &mut Context,
client: &RpcClient,
core_program_ids: &CoreProgramIds,
_chain_config: &ChainMetadata,
token_config: &TokenConfig,
program_id: Pubkey,
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 {
if matches!(
token_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
}
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
}
if account_exists(client, &token_pda).unwrap() {
println!("Token PDA already exists, skipping init");
return Ok(());
fn set_interchain_security_module_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
ism: Option<Pubkey>,
) -> Instruction {
let token_data = get_token_data::<()>(client, program_id);
set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism)
.unwrap()
}
}
fn get_token_data<T>(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken<T>
where
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()
}
fn get_destination_gas(
client: &RpcClient,
program_id: &Pubkey,
) -> Result<HashMap<u32, u64>, ClientError> {
let token_data = get_token_data::<()>(client, program_id);
init_warp_route(
ctx,
client,
core_program_ids,
_chain_config,
token_config,
program_id,
)
Ok(token_data.destination_gas)
}
// Funds the ATA payer up to the specified amount.
fn fund_ata_payer_up_to(
ctx: &mut Context,
client: &RpcClient,
@ -520,168 +479,42 @@ fn fund_ata_payer_up_to(
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()
.add(solana_program::system_instruction::transfer(
&ctx.payer_pubkey,
&ata_payer_account,
funding_amount,
))
.add_with_description(
solana_program::system_instruction::transfer(
&ctx.payer_pubkey,
&ata_payer_account,
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)
.send_with_payer();
}
fn init_warp_route(
ctx: &mut Context,
client: &RpcClient,
core_program_ids: &CoreProgramIds,
_chain_config: &ChainMetadata,
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(),
)
pub fn parse_token_account_data(token_type: FlatTokenType, data: &mut &[u8]) {
fn print_data_or_err<T: Debug>(data: Result<T, ProgramError>) {
match data {
Ok(data) => println!("{:#?}", data),
Err(err) => println!("Failed to deserialize account data: {}", err),
}
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(())
}
fn get_routers(
client: &RpcClient,
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)
}
fn get_destination_gas(
client: &RpcClient,
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();
match token_type {
FlatTokenType::Native => {
let res = HyperlaneTokenAccount::<NativePlugin>::fetch(data);
print_data_or_err(res);
}
FlatTokenType::Synthetic => {
let res = HyperlaneTokenAccount::<SyntheticPlugin>::fetch(data);
print_data_or_err(res);
}
FlatTokenType::Collateral => {
let res = HyperlaneTokenAccount::<CollateralPlugin>::fetch(data);
print_data_or_err(res);
}
}
}

@ -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": {
"chainId": 13375,
"name": "sealeveltest1",
"publicRpcUrls": [
"rpcUrls": [
{
"http": "http://localhost:8899"
}
@ -11,7 +11,7 @@
"sealeveltest2": {
"chainId": 13376,
"name": "sealeveltest2",
"publicRpcUrls": [
"rpcUrls": [
{
"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",
"base58": "EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa"
},
"bsc": {
"hex": "0x000000000000000000000000c27980812e2e66491fd457d488509b7e04144b98",
"base58": "1111111111113i9HKBuaFrAQeGvhv3fJnCCDkg7h"
},
"nautilus": {
"hex": "0x0000000000000000000000004501bbe6e731a4bc5c60c03a77435b2f6d5e9fe7",
"base58": "111111111111xm5qkrK7gZ8Cmjr4ggPLRxy2T8a"

@ -4,15 +4,9 @@
"decimals": 9,
"remoteDecimals": 9,
"token": "wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59",
"splTokenProgram": "token"
},
"bsc": {
"type": "collateral",
"decimals": 9,
"token": "0x37a56cdcD83Dce2868f721De58cB3830C44C6303",
"name": "Zebec",
"symbol": "ZBC",
"foreignDeployment": "0xC27980812E2E66491FD457D488509b7E04144b98"
"splTokenProgram": "token",
"interchainSecurityModule": "9k74DkJvS2x9QhG4XfnKsLkqaCDyVfaj8s6FyJyhAeEP",
"owner": "EzppBFV2taxWw8kEjxNYvby6q7W1biJEqwP3iC7YgRe3"
},
"nautilus": {
"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",
"validator_announce": "CMHKvdq4CopDf7qXnDCaTybS15QekQeRt4oUB219yxsp",
"multisig_ism_message_id": "2xTVcwDWZgBu69aawCdYHXqH7xQP36iBQ7rN2px1g7ms",
"multisig_ism_message_id": "64xkGhsZbxgP5rBJfpPcpmzkzTGkpSVHiDLcMKS5gmQw",
"igp_program_id": "HyPQPLfGXDTAQTxzGp7r1uy18KxS89GKgreSHpjeuYDn",
"overhead_igp_account": "AR4hjWPqXEobLvzmv8MTh5k4Se49iTDzbvNX4DpdQGJZ",
"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": {
"hex": "0x00000000000000000000000031b5234a896fbc4b3e2f7237592d054716762131",
"base58": "111111111111hAc1aTgvQGRBFHrYpXpfUqGyqgk"
"solanadevnet": {
"hex": "0x05b6502b1d91c60ca0c0d0ab20a16ec40c66f2559becc7888a4fc3c0cefff9a5",
"base58": "PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx"
},
"proteustestnet": {
"hex": "0x00000000000000000000000034a9af13c5555bad0783c220911b9ef59cfdbcef",
"base58": "111111111111jZ775N1rpEpJ2M8RAzLNNr9Lh7U"
},
"solanadevnet": {
"hex": "0x05b6502b1d91c60ca0c0d0ab20a16ec40c66f2559becc7888a4fc3c0cefff9a5",
"base58": "PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx"
"bsctestnet": {
"hex": "0x00000000000000000000000031b5234a896fbc4b3e2f7237592d054716762131",
"base58": "111111111111hAc1aTgvQGRBFHrYpXpfUqGyqgk"
}
}

@ -154,11 +154,15 @@ where
{
/// Stores the account data in the given account, reallocing the account
/// 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>(
&self,
account_info: &'a AccountInfo<'b>,
rent: &Rent,
payer_info: &'a AccountInfo<'b>,
_system_program_info: &'a AccountInfo<'b>,
) -> Result<(), ProgramError> {
let required_size = self.size();

@ -200,3 +200,61 @@ pub fn transfer_ownership_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,
&Rent::get()?,
owner_account,
system_program,
)?;
Ok(())
@ -709,6 +710,7 @@ where
token_account,
&Rent::get()?,
owner_account,
system_program,
)?;
Ok(())
@ -849,6 +851,7 @@ where
token_account,
&Rent::get()?,
owner_account,
system_program,
)?;
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