Deploy eclipse testnet (#3172)

### Description

<!--
What's included in this PR?
-->

### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->

### Testing

<!--
What kind of testing have these changes undergone?

None/Manual/Unit Tests
-->

---------

Co-authored-by: Trevor Porter <trkporter@ucdavis.edu>
pull/3180/head
Daniel Savu 10 months ago committed by GitHub
parent 5b4af6bf1d
commit 528a19022b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      rust/Cargo.lock
  2. 46
      rust/config/testnet4_config.json
  3. 1
      rust/sealevel/.gitignore
  4. 1
      rust/sealevel/client/Cargo.toml
  5. 11
      rust/sealevel/client/src/artifacts.rs
  6. 3
      rust/sealevel/client/src/context.rs
  7. 3
      rust/sealevel/client/src/core.rs
  8. 25
      rust/sealevel/client/src/helloworld.rs
  9. 610
      rust/sealevel/client/src/igp.rs
  10. 373
      rust/sealevel/client/src/main.rs
  11. 6
      rust/sealevel/client/src/multisig_ism.rs
  12. 63
      rust/sealevel/client/src/router.rs
  13. 2
      rust/sealevel/client/src/serde.rs
  14. 27
      rust/sealevel/client/src/warp_route.rs
  15. 465
      rust/sealevel/environments/testnet4/chain-config.json
  16. 8
      rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json
  17. 29
      rust/sealevel/environments/testnet4/gas-oracle-configs.json
  18. 5
      rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json
  19. 5
      rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json
  20. 117
      rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json
  21. 117
      rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json
  22. 8
      rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json
  23. 10
      rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json
  24. 14
      rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json
  25. 53
      rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs
  26. 8
      typescript/infra/config/environments/testnet4/agent.ts
  27. 5
      typescript/infra/config/environments/testnet4/chains.ts
  28. 6
      typescript/infra/config/environments/testnet4/gas-oracle.ts
  29. 41
      typescript/infra/config/environments/testnet4/validators.ts
  30. 28
      typescript/sdk/src/consts/chainMetadata.ts
  31. 4
      typescript/sdk/src/consts/chains.ts
  32. 10
      typescript/sdk/src/consts/multisigIsm.ts

1
rust/Cargo.lock generated

@ -4314,6 +4314,7 @@ dependencies = [
"borsh 0.9.3", "borsh 0.9.3",
"bs58 0.5.0", "bs58 0.5.0",
"clap 4.4.11", "clap 4.4.11",
"ethers",
"hex 0.4.3", "hex 0.4.3",
"hyperlane-core", "hyperlane-core",
"hyperlane-sealevel-connection-client", "hyperlane-sealevel-connection-client",

@ -1007,6 +1007,52 @@
"index": { "index": {
"from": 4558491 "from": 4558491
} }
},
"solanatestnet": {
"name": "solanatestnet",
"chainId": 1399811150,
"domainId": 1399811150,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://api.testnet.solana.com"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
},
"eclipsetestnet": {
"name": "eclipsetestnet",
"chainId": 239092742,
"domainId": 239092742,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://testnet.dev2.eclipsenetwork.xyz"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
} }
}, },
"defaultRpcConsensusType": "fallback" "defaultRpcConsensusType": "fallback"

@ -1,2 +1,3 @@
/target /target
environments/**/deploy-logs.txt environments/**/deploy-logs.txt
**/**/keys

@ -10,6 +10,7 @@ borsh.workspace = true
bs58.workspace = true bs58.workspace = true
bincode.workspace = true bincode.workspace = true
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
ethers.workspace = true
hex.workspace = true hex.workspace = true
pretty_env_logger.workspace = true pretty_env_logger.workspace = true
serde.workspace = true serde.workspace = true

@ -54,6 +54,13 @@ pub(crate) fn read_json<T>(path: &Path) -> T
where where
T: DeserializeOwned, T: DeserializeOwned,
{ {
let file = File::open(path).expect("Failed to open JSON file"); try_read_json(path).expect("Failed to read JSON from file")
serde_json::from_reader(file).expect("Failed to read JSON file") }
pub(crate) fn try_read_json<T>(path: &Path) -> std::io::Result<T>
where
T: DeserializeOwned,
{
let file = File::open(path)?;
Ok(serde_json::from_reader(file)?)
} }

@ -239,14 +239,17 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
fn wait_for_user_confirmation() { fn wait_for_user_confirmation() {
println!("Continue? [y/n] then press Enter"); println!("Continue? [y/n] then press Enter");
let mut input = [0u8; 1]; let mut input = [0u8; 1];
loop {
std::io::stdin().read_exact(&mut input).unwrap(); std::io::stdin().read_exact(&mut input).unwrap();
match input[0] { match input[0] {
b'y' => { b'y' => {
println!("Continuing..."); println!("Continuing...");
break;
} }
b'n' => { b'n' => {
panic!("User requested exit"); panic!("User requested exit");
} }
_ => {} _ => {}
} }
}
} }

@ -18,7 +18,8 @@ use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE};
pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) {
match cmd.cmd { match cmd.cmd {
CoreSubCmd::Deploy(core) => { CoreSubCmd::Deploy(core) => {
let environments_dir = create_new_directory(&core.environments_dir, &core.environment); let environments_dir =
create_new_directory(&core.env_args.environments_dir, &core.env_args.environment);
let chain_dir = create_new_directory(&environments_dir, &core.chain); let chain_dir = create_new_directory(&environments_dir, &core.chain);
let core_dir = create_new_directory(&chain_dir, "core"); let core_dir = create_new_directory(&chain_dir, "core");
let key_dir = create_new_directory(&core_dir, "keys"); let key_dir = create_new_directory(&core_dir, "keys");

@ -10,6 +10,7 @@ use hyperlane_sealevel_hello_world::{
}, },
program_storage_pda_seeds, program_storage_pda_seeds,
}; };
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
@ -180,6 +181,26 @@ impl ConnectionClient for HelloWorldDeployer {
set_interchain_security_module_instruction(*program_id, storage.owner.unwrap(), ism) set_interchain_security_module_instruction(*program_id, storage.owner.unwrap(), ism)
.unwrap() .unwrap()
} }
fn get_interchain_gas_paymaster(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<(Pubkey, InterchainGasPaymasterType)> {
let storage = self.get_storage(client, program_id);
storage.igp
}
fn set_interchain_gas_paymaster_instruction(
&self,
_client: &RpcClient,
_program_id: &Pubkey,
_igp_config: Option<(Pubkey, InterchainGasPaymasterType)>,
) -> Option<Instruction> {
// There is no way to set the IGP on HelloWorld
None
}
} }
fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) { fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) {
@ -190,8 +211,8 @@ fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) {
&deploy.context, &deploy.context,
deploy.config_file, deploy.config_file,
deploy.chain_config_file, deploy.chain_config_file,
deploy.environments_dir, deploy.env_args.environments_dir,
&deploy.environment, &deploy.env_args.environment,
deploy.built_so_dir, deploy.built_so_dir,
) )
} }

@ -0,0 +1,610 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{
artifacts::{read_json, try_read_json, write_json, SingularProgramIdArtifact},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program},
read_core_program_ids,
router::ChainMetadata,
Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd,
};
use std::{
fs::File,
path::{Path, PathBuf},
str::FromStr,
};
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer as _},
};
use hyperlane_core::H256;
use hyperlane_sealevel_igp::{
accounts::{
GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount,
ProgramDataAccount as IgpProgramDataAccount, RemoteGasData,
},
igp_program_data_pda_seeds,
instruction::{GasOracleConfig, GasOverheadConfig},
};
#[derive(Debug, Serialize, Deserialize, Default)]
struct IgpAccountsArtifacts {
salt: H256,
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
igp_account: Option<Pubkey>,
#[serde(default)]
#[serde(with = "crate::serde::serde_option_pubkey")]
overhead_igp_account: Option<Pubkey>,
}
fn get_context_salt(context: Option<&String>) -> H256 {
context
.map(|c| {
if c == "default" {
H256::zero()
} else {
ethers::utils::keccak256(c.as_bytes()).into()
}
})
.unwrap_or_else(H256::zero)
}
fn get_context_dir_name(context: Option<&String>) -> &str {
context.map(|c| c.as_str()).unwrap_or("default")
}
pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) {
match cmd.cmd {
IgpSubCmd::DeployProgram(deploy) => {
let environments_dir = create_new_directory(
&deploy.env_args.environments_dir,
&deploy.env_args.environment,
);
let ism_dir = create_new_directory(&environments_dir, "igp");
let chain_dir = create_new_directory(&ism_dir, &deploy.chain);
let key_dir = create_new_directory(&chain_dir, "keys");
let program_id = deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir);
write_json::<SingularProgramIdArtifact>(
&chain_dir.join("program-ids.json"),
program_id.into(),
);
}
IgpSubCmd::InitIgpAccount(init) => {
let environments_dir =
create_new_directory(&init.env_args.environments_dir, &init.env_args.environment);
let ism_dir = create_new_directory(&environments_dir, "igp");
let chain_dir = create_new_directory(&ism_dir, &init.chain);
let context_dir =
create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref()));
let artifacts_path = context_dir.join("igp-accounts.json");
let existing_artifacts = try_read_json::<IgpAccountsArtifacts>(&artifacts_path).ok();
let salt = get_context_salt(init.context.as_ref());
let chain_configs =
read_json::<HashMap<String, ChainMetadata>>(&init.chain_config_file);
let igp_account = init_and_configure_igp_account(
&mut ctx,
init.program_id,
chain_configs.get(&init.chain).unwrap().domain_id(),
salt,
init.gas_oracle_config_file,
);
let artifacts = IgpAccountsArtifacts {
salt,
igp_account: Some(igp_account),
overhead_igp_account: existing_artifacts.and_then(|a| a.overhead_igp_account),
};
write_json(&artifacts_path, artifacts);
}
IgpSubCmd::InitOverheadIgpAccount(init) => {
let environments_dir =
create_new_directory(&init.env_args.environments_dir, &init.env_args.environment);
let ism_dir = create_new_directory(&environments_dir, "igp");
let chain_dir = create_new_directory(&ism_dir, &init.chain);
let context_dir =
create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref()));
let artifacts_path = context_dir.join("igp-accounts.json");
let existing_artifacts = try_read_json::<IgpAccountsArtifacts>(&artifacts_path).ok();
let salt = get_context_salt(init.context.as_ref());
let chain_configs =
read_json::<HashMap<String, ChainMetadata>>(&init.chain_config_file);
let overhead_igp_account = init_and_configure_overhead_igp_account(
&mut ctx,
init.program_id,
init.inner_igp_account,
chain_configs.get(&init.chain).unwrap().domain_id(),
salt,
init.overhead_config_file,
);
let artifacts = IgpAccountsArtifacts {
salt,
igp_account: existing_artifacts.and_then(|a| a.igp_account),
overhead_igp_account: Some(overhead_igp_account),
};
write_json(&artifacts_path, artifacts);
}
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(
&[program_data_account_pda, query.igp_account],
ctx.commitment,
)
.unwrap()
.value;
let igp_program_data =
IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
.unwrap()
.into_inner();
println!("IGP program data: {:?}", igp_program_data);
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();
let (igp_account, _igp_account_bump) = Pubkey::find_program_address(
hyperlane_sealevel_igp::igp_pda_seeds!(salt),
&payment_details.program_id,
);
let (overhead_igp_account, _) = Pubkey::find_program_address(
hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt),
&payment_details.program_id,
);
let (ixn, gas_payment_data_account) =
hyperlane_sealevel_igp::instruction::pay_for_gas_instruction(
payment_details.program_id,
ctx.payer_pubkey,
igp_account,
Some(overhead_igp_account),
unique_gas_payment_keypair.pubkey(),
H256::from_str(&payment_details.message_id).unwrap(),
payment_details.destination_domain,
payment_details.gas,
)
.unwrap();
ctx.new_txn()
.add(ixn)
.send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]);
println!(
"Made a payment for message {} with gas payment data account {}",
payment_details.message_id, gas_payment_data_account
);
}
IgpSubCmd::Claim(claim) => {
let igp_account = ctx
.client
.get_account_with_commitment(&claim.igp_account, ctx.commitment)
.unwrap()
.value
.unwrap();
let igp_account = IgpAccount::fetch(&mut &igp_account.data[..])
.unwrap()
.into_inner();
let ixn = hyperlane_sealevel_igp::instruction::claim_instruction(
claim.program_id,
claim.igp_account,
igp_account.beneficiary,
)
.unwrap();
ctx.new_txn()
.add_with_description(
ixn,
format!(
"Claiming from IGP account {} to beneficiary {}",
claim.igp_account, igp_account.beneficiary
),
)
.send_with_payer();
}
IgpSubCmd::SetIgpBeneficiary(set_beneficiary) => {
let igp_account = ctx
.client
.get_account_with_commitment(&set_beneficiary.igp_account, ctx.commitment)
.unwrap()
.value
.unwrap();
let igp_account = IgpAccount::fetch(&mut &igp_account.data[..])
.unwrap()
.into_inner();
let ixn = hyperlane_sealevel_igp::instruction::set_beneficiary_instruction(
set_beneficiary.program_id,
set_beneficiary.igp_account,
igp_account.owner.unwrap(),
set_beneficiary.new_beneficiary,
)
.unwrap();
ctx.new_txn()
.add_with_description(
ixn,
format!(
"Change beneficiary of IGP account {} to beneficiary {}",
set_beneficiary.igp_account, set_beneficiary.new_beneficiary
),
)
.send_with_payer();
}
IgpSubCmd::GasOracleConfig(args) => {
let core_program_ids = read_core_program_ids(
&args.env_args.environments_dir,
&args.env_args.environment,
&args.chain_name,
);
match args.cmd {
GetSetCmd::Set(set_args) => {
let remote_gas_data = RemoteGasData {
token_exchange_rate: set_args.token_exchange_rate,
gas_price: set_args.gas_price,
token_decimals: set_args.token_decimals,
};
let gas_oracle_config = GasOracleConfig {
domain: args.remote_domain,
gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)),
};
let instruction =
hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction(
core_program_ids.igp_program_id,
core_program_ids.igp_account,
ctx.payer_pubkey,
vec![gas_oracle_config],
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!("Set gas oracle for remote domain {:?}", args.remote_domain);
}
GetSetCmd::Get(_) => {
let igp_account = ctx
.client
.get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment)
.unwrap()
.value
.expect(
"IGP account not found. Make sure you are connected to the right RPC.",
);
let igp_account = IgpAccount::fetch(&mut &igp_account.data[..])
.unwrap()
.into_inner();
println!(
"IGP account gas oracle: {:#?}",
igp_account.gas_oracles.get(&args.remote_domain)
);
}
}
}
IgpSubCmd::DestinationGasOverhead(args) => {
let core_program_ids = read_core_program_ids(
&args.env_args.environments_dir,
&args.env_args.environment,
&args.chain_name,
);
match args.cmd {
GasOverheadSubCmd::Get => {
// Read the gas overhead config
let overhead_igp_account = ctx
.client
.get_account_with_commitment(
&core_program_ids.overhead_igp_account,
ctx.commitment,
)
.unwrap()
.value
.expect("Overhead IGP account not found. Make sure you are connected to the right RPC.");
let overhead_igp_account =
OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..])
.unwrap()
.into_inner();
println!(
"Overhead IGP account gas oracle: {:#?}",
overhead_igp_account.gas_overheads.get(&args.remote_domain)
);
}
GasOverheadSubCmd::Set(set_args) => {
let overhead_config = GasOverheadConfig {
destination_domain: args.remote_domain,
gas_overhead: Some(set_args.gas_overhead),
};
// Set the gas overhead config
let instruction =
hyperlane_sealevel_igp::instruction::set_destination_gas_overheads(
core_program_ids.igp_program_id,
core_program_ids.overhead_igp_account,
ctx.payer_pubkey,
vec![overhead_config],
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!(
"Set gas overheads for remote domain {:?}",
args.remote_domain
)
}
}
}
IgpSubCmd::TransferIgpOwnership(ref transfer_ownership)
| IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => {
let igp_account_type = match cmd.cmd {
IgpSubCmd::TransferIgpOwnership(_) => {
InterchainGasPaymasterType::Igp(transfer_ownership.igp_account)
}
IgpSubCmd::TransferOverheadIgpOwnership(_) => {
InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account)
}
_ => unreachable!(),
};
let instruction =
hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction(
transfer_ownership.program_id,
igp_account_type.clone(),
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Transfer ownership of {:?} to {}",
igp_account_type, transfer_ownership.new_owner
),
)
.send_with_payer();
}
}
}
#[allow(clippy::too_many_arguments)]
fn deploy_igp_program(
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_igp-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_igp.so")
.to_str()
.unwrap(),
&ctx.client.url(),
);
println!("Deployed IGP at program ID {}", program_id);
let (program_data_account, _program_data_bump) = Pubkey::find_program_address(
hyperlane_sealevel_igp::igp_program_data_pda_seeds!(),
&program_id,
);
// Initialize the program data
let instruction =
hyperlane_sealevel_igp::instruction::init_instruction(program_id, ctx.payer_pubkey)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Initializing IGP program data {}", program_data_account),
)
.send_with_payer();
program_id
}
fn init_and_configure_igp_account(
ctx: &mut Context,
program_id: Pubkey,
local_domain: u32,
salt: H256,
gas_oracle_config_file: Option<PathBuf>,
) -> Pubkey {
let gas_oracle_configs = gas_oracle_config_file
.as_deref()
.map(|p| {
let file = File::open(p).expect("Failed to open oracle config file");
serde_json::from_reader::<_, Vec<GasOracleConfig>>(file)
.expect("Failed to parse oracle config file")
})
.unwrap_or_default()
.into_iter()
.filter(|c| c.domain != local_domain)
.collect::<Vec<_>>();
// Initialize IGP with the given salt
let (igp_account_pda, _igp_account_bump) =
Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id);
if ctx
.client
.get_account_with_commitment(&igp_account_pda, ctx.commitment)
.unwrap()
.value
.is_none()
{
let instruction = hyperlane_sealevel_igp::instruction::init_igp_instruction(
program_id,
ctx.payer_pubkey,
salt,
Some(ctx.payer_pubkey),
ctx.payer_pubkey,
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Initializing IGP account {}", igp_account_pda),
)
.send_with_payer();
} else {
println!(
"IGP account {} already exists, not creating",
igp_account_pda
);
}
if !gas_oracle_configs.is_empty() {
// TODO: idempotency
let domains = gas_oracle_configs
.iter()
.map(|c| c.domain)
.collect::<Vec<_>>();
let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction(
program_id,
igp_account_pda,
ctx.payer_pubkey,
gas_oracle_configs,
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!("Set gas oracle for remote domains {domains:?}",);
} else {
println!("Skipping settings gas oracle config");
}
igp_account_pda
}
fn init_and_configure_overhead_igp_account(
ctx: &mut Context,
program_id: Pubkey,
inner_igp_account: Pubkey,
local_domain: u32,
salt: H256,
overhead_config_file: Option<PathBuf>,
) -> Pubkey {
let overhead_configs = overhead_config_file
.as_deref()
.map(|p| {
let file = File::open(p).expect("Failed to open overhead config file");
serde_json::from_reader::<_, Vec<GasOverheadConfig>>(file)
.expect("Failed to parse overhead config file")
})
.unwrap_or_default()
.into_iter()
.filter(|c| c.destination_domain != local_domain)
.map(|c| (c.destination_domain, c))
.collect::<HashMap<_, _>>() // dedup
.into_values()
.collect::<Vec<_>>();
let (overhead_igp_account, _) = Pubkey::find_program_address(
hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt),
&program_id,
);
if ctx
.client
.get_account_with_commitment(&overhead_igp_account, ctx.commitment)
.unwrap()
.value
.is_none()
{
let instruction = hyperlane_sealevel_igp::instruction::init_overhead_igp_instruction(
program_id,
ctx.payer_pubkey,
salt,
Some(ctx.payer_pubkey),
inner_igp_account,
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Initializing overhead IGP account {}", overhead_igp_account),
)
.send_with_payer();
} else {
println!(
"Overhead IGP account {} already exists, not creating",
overhead_igp_account
);
}
if !overhead_configs.is_empty() {
// TODO: idempotency
let domains = overhead_configs
.iter()
.map(|c| c.destination_domain)
.collect::<Vec<_>>();
let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads(
program_id,
overhead_igp_account,
ctx.payer_pubkey,
overhead_configs,
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!("Set gas overheads for remote domains {domains:?}",)
} else {
println!("Skipping setting gas overheads");
}
overhead_igp_account
}

@ -23,12 +23,8 @@ use account_utils::DiscriminatorEncode;
use hyperlane_core::{H160, H256}; use hyperlane_core::{H160, H256};
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::{ use hyperlane_sealevel_igp::{
accounts::{ accounts::{InterchainGasPaymasterType, OverheadIgpAccount},
GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount,
ProgramDataAccount as IgpProgramDataAccount, RemoteGasData,
},
igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds,
instruction::{GasOracleConfig, GasOverheadConfig},
}; };
use hyperlane_sealevel_mailbox::{ use hyperlane_sealevel_mailbox::{
accounts::{InboxAccount, OutboxAccount}, accounts::{InboxAccount, OutboxAccount},
@ -67,12 +63,14 @@ mod cmd_utils;
mod context; mod context;
mod r#core; mod r#core;
mod helloworld; mod helloworld;
mod igp;
mod multisig_ism; mod multisig_ism;
mod router; mod router;
mod serde; mod serde;
mod warp_route; mod warp_route;
use crate::helloworld::process_helloworld_cmd; use crate::helloworld::process_helloworld_cmd;
use crate::igp::process_igp_cmd;
use crate::multisig_ism::process_multisig_ism_message_id_cmd; use crate::multisig_ism::process_multisig_ism_message_id_cmd;
use crate::warp_route::process_warp_route_cmd; use crate::warp_route::process_warp_route_cmd;
pub(crate) use crate::{context::*, core::*}; pub(crate) use crate::{context::*, core::*};
@ -115,6 +113,14 @@ enum HyperlaneSealevelCmd {
HelloWorld(HelloWorldCmd), HelloWorld(HelloWorldCmd),
} }
#[derive(Args)]
struct EnvironmentArgs {
#[arg(long)]
environment: String,
#[arg(long)]
environments_dir: PathBuf,
}
#[derive(Args)] #[derive(Args)]
pub(crate) struct WarpRouteCmd { pub(crate) struct WarpRouteCmd {
#[command(subcommand)] #[command(subcommand)]
@ -129,10 +135,8 @@ pub(crate) enum WarpRouteSubCmd {
#[derive(Args)] #[derive(Args)]
pub(crate) struct WarpRouteDeploy { pub(crate) struct WarpRouteDeploy {
#[arg(long)] #[command(flatten)]
environment: String, env_args: EnvironmentArgs,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)] #[arg(long)]
built_so_dir: PathBuf, built_so_dir: PathBuf,
#[arg(long)] #[arg(long)]
@ -168,8 +172,8 @@ enum CoreSubCmd {
struct CoreDeploy { struct CoreDeploy {
#[arg(long)] #[arg(long)]
local_domain: u32, local_domain: u32,
#[arg(long)] #[command(flatten)]
environment: String, env_args: EnvironmentArgs,
#[arg(long)] #[arg(long)]
gas_oracle_config_file: Option<PathBuf>, gas_oracle_config_file: Option<PathBuf>,
#[arg(long)] #[arg(long)]
@ -178,8 +182,6 @@ struct CoreDeploy {
chain: String, chain: String,
#[arg(long)] #[arg(long)]
use_existing_keys: bool, use_existing_keys: bool,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long, num_args = 1.., value_delimiter = ',')] #[arg(long, num_args = 1.., value_delimiter = ',')]
remote_domains: Vec<u32>, remote_domains: Vec<u32>,
#[arg(long)] #[arg(long)]
@ -381,14 +383,63 @@ struct IgpCmd {
#[derive(Subcommand)] #[derive(Subcommand)]
enum IgpSubCmd { enum IgpSubCmd {
DeployProgram(IgpDeployProgramArgs),
InitIgpAccount(InitIgpAccountArgs),
InitOverheadIgpAccount(InitOverheadIgpAccountArgs),
Query(IgpQueryArgs), Query(IgpQueryArgs),
PayForGas(PayForGasArgs), PayForGas(PayForGasArgs),
Claim(ClaimArgs),
SetIgpBeneficiary(SetIgpBeneficiaryArgs),
GasOracleConfig(GasOracleConfigArgs), GasOracleConfig(GasOracleConfigArgs),
DestinationGasOverhead(DestinationGasOverheadArgs), DestinationGasOverhead(DestinationGasOverheadArgs),
TransferIgpOwnership(TransferIgpOwnership), TransferIgpOwnership(TransferIgpOwnership),
TransferOverheadIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership),
} }
#[derive(Args)]
struct IgpDeployProgramArgs {
#[command(flatten)]
env_args: EnvironmentArgs,
#[arg(long)]
chain: String,
#[arg(long)]
built_so_dir: PathBuf,
}
#[derive(Args)]
struct InitIgpAccountArgs {
#[arg(long)]
program_id: Pubkey,
#[command(flatten)]
env_args: EnvironmentArgs,
#[arg(long)]
chain: String,
#[arg(long)]
chain_config_file: PathBuf,
#[arg(long)]
context: Option<String>,
#[arg(long)]
gas_oracle_config_file: Option<PathBuf>,
}
#[derive(Args)]
struct InitOverheadIgpAccountArgs {
#[arg(long)]
program_id: Pubkey,
#[command(flatten)]
env_args: EnvironmentArgs,
#[arg(long)]
chain: String,
#[arg(long)]
chain_config_file: PathBuf,
#[arg(long)]
inner_igp_account: Pubkey,
#[arg(long)]
context: Option<String>,
#[arg(long)]
overhead_config_file: Option<PathBuf>,
}
#[derive(Args)] #[derive(Args)]
struct IgpQueryArgs { struct IgpQueryArgs {
#[arg(long)] #[arg(long)]
@ -423,11 +474,26 @@ struct PayForGasArgs {
} }
#[derive(Args)] #[derive(Args)]
struct GasOracleConfigArgs { struct ClaimArgs {
#[arg(long)] #[arg(long)]
environment: String, program_id: Pubkey,
#[arg(long)] #[arg(long)]
environments_dir: PathBuf, igp_account: Pubkey,
}
#[derive(Args)]
struct SetIgpBeneficiaryArgs {
#[arg(long)]
program_id: Pubkey,
#[arg(long)]
igp_account: Pubkey,
new_beneficiary: Pubkey,
}
#[derive(Args)]
struct GasOracleConfigArgs {
#[command(flatten)]
env_args: EnvironmentArgs,
#[arg(long)] #[arg(long)]
chain_name: String, chain_name: String,
#[arg(long)] #[arg(long)]
@ -451,10 +517,8 @@ struct GetGasOracleArgs;
#[derive(Args)] #[derive(Args)]
struct DestinationGasOverheadArgs { struct DestinationGasOverheadArgs {
#[arg(long)] #[command(flatten)]
environment: String, env_args: EnvironmentArgs,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)] #[arg(long)]
chain_name: String, chain_name: String,
#[arg(long)] #[arg(long)]
@ -535,10 +599,8 @@ enum MultisigIsmMessageIdSubCmd {
#[derive(Args)] #[derive(Args)]
struct MultisigIsmMessageIdDeploy { struct MultisigIsmMessageIdDeploy {
#[arg(long)] #[command(flatten)]
environment: String, env_args: EnvironmentArgs,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)] #[arg(long)]
built_so_dir: PathBuf, built_so_dir: PathBuf,
#[arg(long)] #[arg(long)]
@ -597,10 +659,8 @@ pub(crate) enum HelloWorldSubCmd {
#[derive(Args)] #[derive(Args)]
pub(crate) struct HelloWorldDeploy { pub(crate) struct HelloWorldDeploy {
#[arg(long)] #[command(flatten)]
environment: String, env_args: EnvironmentArgs,
#[arg(long)]
environments_dir: PathBuf,
#[arg(long)] #[arg(long)]
built_so_dir: PathBuf, built_so_dir: PathBuf,
#[arg(long)] #[arg(long)]
@ -991,23 +1051,23 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
// Burns the tokens from the sender's associated token account and // Burns the tokens from the sender's associated token account and
// then dispatches a message to the remote recipient. // then dispatches a message to the remote recipient.
// //
// 0. `[executable]` The system program. // 0. [executable] The system program.
// 1. `[executable]` The spl_noop program. // 1. [executable] The spl_noop program.
// 2. `[]` The token PDA account. // 2. [] The token PDA account.
// 3. `[executable]` The mailbox program. // 3. [executable] The mailbox program.
// 4. `[writeable]` The mailbox outbox account. // 4. [writeable] The mailbox outbox account.
// 5. `[]` Message dispatch authority. // 5. [] Message dispatch authority.
// 6. `[signer]` The token sender and mailbox payer. // 6. [signer] The token sender and mailbox payer.
// 7. `[signer]` Unique message / gas payment account. // 7. [signer] Unique message / gas payment account.
// 8. `[writeable]` Message storage PDA. // 8. [writeable] Message storage PDA.
// ---- If using an IGP ---- // ---- If using an IGP ----
// 9. `[executable]` The IGP program. // 9. [executable] The IGP program.
// 10. `[writeable]` The IGP program data. // 10. [writeable] The IGP program data.
// 11. `[writeable]` Gas payment PDA. // 11. [writeable] Gas payment PDA.
// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP.
// 13. `[writeable]` The IGP account. // 13. [writeable] The IGP account.
// ---- End if ---- // ---- End if ----
// 14..N `[??..??]` Plugin-specific accounts. // 14..N [??..??] Plugin-specific accounts.
let mut accounts = vec![ let mut accounts = vec![
AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(spl_noop::id(), false), AccountMeta::new_readonly(spl_noop::id(), false),
@ -1059,8 +1119,8 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
match xfer.token_type { match xfer.token_type {
TokenType::Native => { TokenType::Native => {
// 5. `[executable]` The system program. // 5. [executable] The system program.
// 6. `[writeable]` The native token collateral PDA account. // 6. [writeable] The native token collateral PDA account.
let (native_collateral_account, _native_collateral_bump) = let (native_collateral_account, _native_collateral_bump) =
Pubkey::find_program_address( Pubkey::find_program_address(
hyperlane_token_native_collateral_pda_seeds!(), hyperlane_token_native_collateral_pda_seeds!(),
@ -1072,9 +1132,9 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
]); ]);
} }
TokenType::Synthetic => { TokenType::Synthetic => {
// 5. `[executable]` The spl_token_2022 program. // 5. [executable] The spl_token_2022 program.
// 6. `[writeable]` The mint / mint authority PDA account. // 6. [writeable] The mint / mint authority PDA account.
// 7. `[writeable]` The token sender's associated token account, from which tokens will be burned. // 7. [writeable] The token sender's associated token account, from which tokens will be burned.
let (mint_account, _mint_bump) = Pubkey::find_program_address( let (mint_account, _mint_bump) = Pubkey::find_program_address(
hyperlane_token_mint_pda_seeds!(), hyperlane_token_mint_pda_seeds!(),
&xfer.program_id, &xfer.program_id,
@ -1092,10 +1152,10 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
]); ]);
} }
TokenType::Collateral => { TokenType::Collateral => {
// 5. `[executable]` The SPL token program for the mint. // 5. [executable] The SPL token program for the mint.
// 6. `[writeable]` The mint. // 6. [writeable] The mint.
// 7. `[writeable]` The token sender's associated token account, from which tokens will be sent. // 7. [writeable] The token sender's associated token account, from which tokens will be sent.
// 8. `[writeable]` The escrow PDA account. // 8. [writeable] The escrow PDA account.
let token = HyperlaneTokenAccount::<CollateralPlugin>::fetch( let token = HyperlaneTokenAccount::<CollateralPlugin>::fetch(
&mut &fetched_token_account.data[..], &mut &fetched_token_account.data[..],
) )
@ -1266,11 +1326,11 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) {
let ixn = ValidatorAnnounceInstruction::Announce(announce_instruction); let ixn = ValidatorAnnounceInstruction::Announce(announce_instruction);
// Accounts: // Accounts:
// 0. `[signer]` The payer. // 0. [signer] The payer.
// 1. `[executable]` The system program. // 1. [executable] The system program.
// 2. `[]` The ValidatorAnnounce PDA account. // 2. [] The ValidatorAnnounce PDA account.
// 3. `[writeable]` The validator-specific ValidatorStorageLocationsAccount PDA account. // 3. [writeable] The validator-specific ValidatorStorageLocationsAccount PDA account.
// 4. `[writeable]` The ReplayProtection PDA account specific to the announcement being made. // 4. [writeable] The ReplayProtection PDA account specific to the announcement being made.
let accounts = vec![ let accounts = vec![
AccountMeta::new_readonly(ctx.payer_pubkey, true), AccountMeta::new_readonly(ctx.payer_pubkey, true),
AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(system_program::id(), false),
@ -1313,202 +1373,3 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) {
} }
} }
} }
fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
match cmd.cmd {
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(
&[program_data_account_pda, query.igp_account],
ctx.commitment,
)
.unwrap()
.value;
let igp_program_data =
IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..])
.unwrap()
.into_inner();
println!("IGP program data: {:?}", igp_program_data);
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();
let (igp_account, _igp_account_bump) = Pubkey::find_program_address(
hyperlane_sealevel_igp::igp_pda_seeds!(salt),
&payment_details.program_id,
);
let (overhead_igp_account, _) = Pubkey::find_program_address(
hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt),
&payment_details.program_id,
);
let (ixn, gas_payment_data_account) =
hyperlane_sealevel_igp::instruction::pay_for_gas_instruction(
payment_details.program_id,
ctx.payer_pubkey,
igp_account,
Some(overhead_igp_account),
unique_gas_payment_keypair.pubkey(),
H256::from_str(&payment_details.message_id).unwrap(),
payment_details.destination_domain,
payment_details.gas,
)
.unwrap();
ctx.new_txn()
.add(ixn)
.send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]);
println!(
"Made a payment for message {} with gas payment data account {}",
payment_details.message_id, gas_payment_data_account
);
}
IgpSubCmd::GasOracleConfig(args) => {
let core_program_ids =
read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name);
match args.cmd {
GetSetCmd::Set(set_args) => {
let remote_gas_data = RemoteGasData {
token_exchange_rate: set_args.token_exchange_rate,
gas_price: set_args.gas_price,
token_decimals: set_args.token_decimals,
};
let gas_oracle_config = GasOracleConfig {
domain: args.remote_domain,
gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)),
};
let instruction =
hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction(
core_program_ids.igp_program_id,
core_program_ids.igp_account,
ctx.payer_pubkey,
vec![gas_oracle_config],
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!("Set gas oracle for remote domain {:?}", args.remote_domain);
}
GetSetCmd::Get(_) => {
let igp_account = ctx
.client
.get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment)
.unwrap()
.value
.expect(
"IGP account not found. Make sure you are connected to the right RPC.",
);
let igp_account = IgpAccount::fetch(&mut &igp_account.data[..])
.unwrap()
.into_inner();
println!(
"IGP account gas oracle: {:#?}",
igp_account.gas_oracles.get(&args.remote_domain)
);
}
}
}
IgpSubCmd::DestinationGasOverhead(args) => {
let core_program_ids =
read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name);
match args.cmd {
GasOverheadSubCmd::Get => {
// Read the gas overhead config
let overhead_igp_account = ctx
.client
.get_account_with_commitment(
&core_program_ids.overhead_igp_account,
ctx.commitment,
)
.unwrap()
.value
.expect("Overhead IGP account not found. Make sure you are connected to the right RPC.");
let overhead_igp_account =
OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..])
.unwrap()
.into_inner();
println!(
"Overhead IGP account gas oracle: {:#?}",
overhead_igp_account.gas_overheads.get(&args.remote_domain)
);
}
GasOverheadSubCmd::Set(set_args) => {
let overhead_config = GasOverheadConfig {
destination_domain: args.remote_domain,
gas_overhead: Some(set_args.gas_overhead),
};
// Set the gas overhead config
let instruction =
hyperlane_sealevel_igp::instruction::set_destination_gas_overheads(
core_program_ids.igp_program_id,
core_program_ids.overhead_igp_account,
ctx.payer_pubkey,
vec![overhead_config],
)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
println!(
"Set gas overheads for remote domain {:?}",
args.remote_domain
)
}
}
}
IgpSubCmd::TransferIgpOwnership(ref transfer_ownership)
| IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => {
let igp_account_type = match cmd.cmd {
IgpSubCmd::TransferIgpOwnership(_) => {
InterchainGasPaymasterType::Igp(transfer_ownership.igp_account)
}
IgpSubCmd::TransferOverheadIgpOwnership(_) => {
InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account)
}
_ => unreachable!(),
};
let instruction =
hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction(
transfer_ownership.program_id,
igp_account_type.clone(),
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Transfer ownership of {:?} to {}",
igp_account_type, transfer_ownership.new_owner
),
)
.send_with_payer();
}
}
}

@ -45,8 +45,10 @@ impl From<MultisigIsmConfig> for ValidatorsAndThreshold {
pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: MultisigIsmMessageIdCmd) { pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: MultisigIsmMessageIdCmd) {
match cmd.cmd { match cmd.cmd {
MultisigIsmMessageIdSubCmd::Deploy(deploy) => { MultisigIsmMessageIdSubCmd::Deploy(deploy) => {
let environments_dir = let environments_dir = create_new_directory(
create_new_directory(&deploy.environments_dir, &deploy.environment); &deploy.env_args.environments_dir,
&deploy.env_args.environment,
);
let ism_dir = create_new_directory(&environments_dir, "multisig-ism-message-id"); let ism_dir = create_new_directory(&environments_dir, "multisig-ism-message-id");
let chain_dir = create_new_directory(&ism_dir, &deploy.chain); let chain_dir = create_new_directory(&ism_dir, &deploy.chain);
let context_dir = create_new_directory(&chain_dir, &deploy.context); let context_dir = create_new_directory(&chain_dir, &deploy.context);

@ -268,6 +268,21 @@ pub(crate) trait ConnectionClient: Ownable {
program_id: &Pubkey, program_id: &Pubkey,
ism: Option<Pubkey>, ism: Option<Pubkey>,
) -> Instruction; ) -> Instruction;
/// Gets the IGP configured on-chain.
fn get_interchain_gas_paymaster(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<(Pubkey, InterchainGasPaymasterType)>;
/// Gets an instruction to set the IGP.
fn set_interchain_gas_paymaster_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
igp_config: Option<(Pubkey, InterchainGasPaymasterType)>,
) -> Option<Instruction>;
} }
/// Idempotently deploys routers on multiple Sealevel chains and enrolls all routers (including /// Idempotently deploys routers on multiple Sealevel chains and enrolls all routers (including
@ -426,8 +441,6 @@ fn configure_connection_client(
router_config: &RouterConfig, router_config: &RouterConfig,
chain_config: &ChainMetadata, chain_config: &ChainMetadata,
) { ) {
// Just ISM for now
let client = chain_config.client(); let client = chain_config.client();
let actual_ism = deployer.get_interchain_security_module(&client, program_id); let actual_ism = deployer.get_interchain_security_module(&client, program_id);
@ -451,6 +464,35 @@ fn configure_connection_client(
.with_client(&client) .with_client(&client)
.send_with_payer(); .send_with_payer();
} }
let actual_igp = deployer.get_interchain_gas_paymaster(&client, program_id);
let expected_igp = router_config
.connection_client
.interchain_gas_paymaster_config(&client);
if actual_igp != expected_igp {
let instruction = deployer.set_interchain_gas_paymaster_instruction(
&client,
program_id,
expected_igp.clone(),
);
if let Some(instruction) = instruction {
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Setting IGP for chain: {} ({}) to {:?}",
chain_config.name,
chain_config.domain_id(),
expected_igp
),
)
.with_client(&client)
.send_with_payer();
} else {
println!("WARNING: Invalid configured IGP {:?}, expected {:?} for chain {} ({}), but cannot craft instruction to change it", actual_igp, expected_igp, chain_config.name, chain_config.domain_id());
}
}
} }
// Idempotent. // Idempotent.
@ -541,17 +583,18 @@ fn enroll_all_remote_routers<
.collect::<Vec<RemoteRouterConfig>>(); .collect::<Vec<RemoteRouterConfig>>();
if !router_configs.is_empty() { if !router_configs.is_empty() {
println!(
"Enrolling routers for chain: {}, program_id {}, routers: {:?}",
chain_name, program_id, router_configs,
);
ctx.new_txn() ctx.new_txn()
.add(deployer.enroll_remote_routers_instruction( .add_with_description(
deployer.enroll_remote_routers_instruction(
program_id, program_id,
ctx.payer_pubkey, ctx.payer_pubkey,
router_configs, router_configs.clone(),
)) ),
format!(
"Enrolling routers for chain: {}, program_id {}, routers: {:?}",
chain_name, program_id, router_configs,
),
)
.with_client(&chain_config.client()) .with_client(&chain_config.client())
.send_with_payer(); .send_with_payer();
} else { } else {

@ -24,7 +24,7 @@ pub(crate) mod serde_pubkey {
} }
} }
/// For serializing and deserializing `Option<Pubkey>` /// For serializing and deserializing Option<Pubkey>
pub(crate) mod serde_option_pubkey { pub(crate) mod serde_option_pubkey {
use borsh::BorshDeserialize; use borsh::BorshDeserialize;
use serde::{Deserialize, Deserializer, Serializer}; use serde::{Deserialize, Deserializer, Serializer};

@ -20,7 +20,7 @@ use hyperlane_sealevel_token_lib::{
accounts::{HyperlaneToken, HyperlaneTokenAccount}, accounts::{HyperlaneToken, HyperlaneTokenAccount},
hyperlane_token_pda_seeds, hyperlane_token_pda_seeds,
instruction::{ instruction::{
enroll_remote_routers_instruction, set_destination_gas_configs, enroll_remote_routers_instruction, set_destination_gas_configs, set_igp_instruction,
set_interchain_security_module_instruction, transfer_ownership_instruction, Init, set_interchain_security_module_instruction, transfer_ownership_instruction, Init,
}, },
}; };
@ -125,8 +125,8 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
&deploy.warp_route_name, &deploy.warp_route_name,
deploy.token_config_file, deploy.token_config_file,
deploy.chain_config_file, deploy.chain_config_file,
deploy.environments_dir, deploy.env_args.environments_dir,
&deploy.environment, &deploy.env_args.environment,
deploy.built_so_dir, deploy.built_so_dir,
); );
} }
@ -434,6 +434,27 @@ impl ConnectionClient for WarpRouteDeployer {
set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism) set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism)
.unwrap() .unwrap()
} }
fn get_interchain_gas_paymaster(
&self,
client: &RpcClient,
program_id: &Pubkey,
) -> Option<(Pubkey, InterchainGasPaymasterType)> {
let token_data = get_token_data::<()>(client, program_id);
token_data.interchain_gas_paymaster
}
fn set_interchain_gas_paymaster_instruction(
&self,
client: &RpcClient,
program_id: &Pubkey,
igp_config: Option<(Pubkey, InterchainGasPaymasterType)>,
) -> Option<Instruction> {
let token_data = get_token_data::<()>(client, program_id);
Some(set_igp_instruction(*program_id, token_data.owner.unwrap(), igp_config).unwrap())
}
} }
fn get_token_data<T>(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken<T> fn get_token_data<T>(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken<T>

@ -0,0 +1,465 @@
{
"alfajores": {
"blockExplorers": [
{
"apiUrl": "https://api-alfajores.celoscan.io/api",
"family": "etherscan",
"name": "CeloScan",
"url": "https://alfajores.celoscan.io"
},
{
"apiUrl": "https://explorer.celo.org/alfajores/api",
"family": "blockscout",
"name": "Blockscout",
"url": "https://explorer.celo.org/alfajores"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 5,
"reorgPeriod": 0
},
"chainId": 44787,
"displayName": "Alfajores",
"domainId": 44787,
"isTestnet": true,
"name": "alfajores",
"nativeToken": {
"decimals": 18,
"name": "CELO",
"symbol": "CELO"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://alfajores-forno.celo-testnet.org"
}
]
},
"basegoerli": {
"blockExplorers": [
{
"apiUrl": "https://api-goerli.basescan.org/api",
"family": "etherscan",
"name": "BaseScan",
"url": "https://goerli.basescan.org"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 84531,
"displayName": "Base Goerli",
"domainId": 84531,
"isTestnet": true,
"name": "basegoerli",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://base-goerli.publicnode.com"
},
{
"http": "https://goerli.base.org"
}
]
},
"fuji": {
"blockExplorers": [
{
"apiUrl": "https://api-testnet.snowtrace.io/api",
"family": "etherscan",
"name": "SnowTrace",
"url": "https://testnet.snowtrace.io"
}
],
"blocks": {
"confirmations": 3,
"estimateBlockTime": 2,
"reorgPeriod": 3
},
"chainId": 43113,
"displayName": "Fuji",
"domainId": 43113,
"isTestnet": true,
"name": "fuji",
"nativeToken": {
"decimals": 18,
"name": "Avalanche",
"symbol": "AVAX"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://api.avax-test.network/ext/bc/C/rpc",
"pagination": {
"maxBlockRange": 2048
}
}
]
},
"mumbai": {
"blockExplorers": [
{
"apiUrl": "https://api-testnet.polygonscan.com/api",
"family": "etherscan",
"name": "PolygonScan",
"url": "https://mumbai.polygonscan.com"
}
],
"blocks": {
"confirmations": 3,
"estimateBlockTime": 5,
"reorgPeriod": 32
},
"chainId": 80001,
"displayName": "Mumbai",
"domainId": 80001,
"isTestnet": true,
"name": "mumbai",
"nativeToken": {
"decimals": 18,
"name": "MATIC",
"symbol": "MATIC"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.ankr.com/polygon_mumbai",
"pagination": {
"maxBlockRange": 10000,
"minBlockNumber": 22900000
}
}
],
"transactionOverrides": {
"maxFeePerGas": 150000000000,
"maxPriorityFeePerGas": 40000000000
}
},
"bsctestnet": {
"blockExplorers": [
{
"apiUrl": "https://api-testnet.bscscan.com/api",
"family": "etherscan",
"name": "BscScan",
"url": "https://testnet.bscscan.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 9
},
"chainId": 97,
"displayName": "BSC Testnet",
"domainId": 97,
"isTestnet": true,
"name": "bsctestnet",
"nativeToken": {
"decimals": 18,
"name": "BNB",
"symbol": "BNB"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://bsc-testnet.publicnode.com"
},
{
"http": "https://bsc-testnet.blockpi.network/v1/rpc/public"
}
],
"transactionOverrides": {
"gasPrice": 80000000000
}
},
"goerli": {
"blockExplorers": [
{
"apiUrl": "https://api-goerli.etherscan.io/api",
"family": "etherscan",
"name": "Etherscan",
"url": "https://goerli.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 2
},
"chainId": 5,
"displayName": "Goerli",
"domainId": 5,
"isTestnet": true,
"name": "goerli",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
},
{
"http": "https://rpc.ankr.com/eth_goerli"
}
]
},
"scrollsepolia": {
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.scrollscan.com/api",
"family": "etherscan",
"name": "Scroll Explorer",
"url": "https://sepolia.scrollscan.dev/"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 534351,
"displayName": "Scroll Sepolia",
"domainId": 534351,
"isTestnet": true,
"name": "scrollsepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://sepolia-rpc.scroll.io"
}
]
},
"sepolia": {
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.etherscan.io/api",
"family": "etherscan",
"name": "Etherscan",
"url": "https://sepolia.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 2
},
"chainId": 11155111,
"displayName": "Sepolia",
"domainId": 11155111,
"isTestnet": true,
"name": "sepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public"
},
{
"http": "https://rpc.sepolia.org"
}
]
},
"moonbasealpha": {
"blockExplorers": [
{
"apiUrl": "https://api-moonbase.moonscan.io/api",
"family": "etherscan",
"name": "MoonScan",
"url": "https://moonbase.moonscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 12,
"reorgPeriod": 1
},
"chainId": 1287,
"displayName": "Moonbase Alpha",
"displayNameShort": "Moonbase",
"domainId": 1287,
"isTestnet": true,
"name": "moonbasealpha",
"nativeToken": {
"decimals": 18,
"name": "DEV",
"symbol": "DEV"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.api.moonbase.moonbeam.network"
}
]
},
"optimismgoerli": {
"blockExplorers": [
{
"apiUrl": "https://api-goerli-optimism.etherscan.io/api",
"family": "etherscan",
"name": "Etherscan",
"url": "https://goerli-optimism.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 420,
"displayName": "Optimism Goerli",
"displayNameShort": "Opt. Goerli",
"domainId": 420,
"isTestnet": true,
"name": "optimismgoerli",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://goerli.optimism.io"
}
]
},
"arbitrumgoerli": {
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 421613,
"displayName": "Arbitrum Goerli",
"displayNameShort": "Arb. Goerli",
"domainId": 421613,
"isTestnet": true,
"name": "arbitrumgoerli",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://goerli-rollup.arbitrum.io/rpc"
}
]
},
"polygonzkevmtestnet": {
"blockExplorers": [
{
"apiUrl": "https://api-testnet-zkevm.polygonscan.com/api",
"family": "etherscan",
"name": "PolygonScan",
"url": "https://testnet-zkevm.polygonscan.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 1442,
"displayName": "Polygon zkEVM Testnet",
"displayNameShort": "ZkEvm Testnet",
"domainId": 1442,
"isTestnet": true,
"name": "polygonzkevmtestnet",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.public.zkevm-test.net"
}
]
},
"solanatestnet": {
"blockExplorers": [
{
"apiUrl": "https://explorer.solana.com",
"family": "other",
"name": "Solana Explorer",
"url": "https://explorer.solana.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.4,
"reorgPeriod": 0
},
"chainId": 1399811150,
"displayName": "Solana Testnet",
"displayNameShort": "Sol Testnet",
"domainId": 1399811150,
"isTestnet": true,
"name": "solanatestnet",
"nativeToken": {
"decimals": 9,
"name": "Sol",
"symbol": "SOL"
},
"protocol": "sealevel",
"rpcUrls": [
{
"http": "https://api.testnet.solana.com"
}
]
},
"eclipsetestnet": {
"blockExplorers": [
{
"apiUrl": "https://testnet.dev2.eclipsenetwork.xyz",
"family": "other",
"name": "Eclipse Testnet Explorer",
"url": "https://explorer.dev.eclipsenetwork.xyz/?cluster=testnet"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.4,
"reorgPeriod": 0
},
"chainId": 239092742,
"displayName": "Eclipse Testnet",
"domainId": 239092742,
"isTestnet": true,
"name": "eclipsetestnet",
"nativeToken": {
"decimals": 9,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "sealevel",
"rpcUrls": [
{
"http": "https://testnet.dev2.eclipsenetwork.xyz"
}
]
}
}

@ -0,0 +1,8 @@
{
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k",
"igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2",
"overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB",
"igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy"
}

@ -0,0 +1,29 @@
[
{
"domain": 11155111,
"gasOracle": {
"type": "remoteGasData",
"tokenExchangeRate": "10000000000000000000",
"gasPrice": "15000000000",
"tokenDecimals": 18
}
},
{
"domain": 1399811150,
"gasOracle": {
"type": "remoteGasData",
"tokenExchangeRate": "10000000000000000000",
"gasPrice": "28",
"tokenDecimals": 9
}
},
{
"domain": 239092742,
"gasOracle": {
"type": "remoteGasData",
"tokenExchangeRate": "10000000000000000000",
"gasPrice": "28",
"tokenDecimals": 9
}
}
]

@ -0,0 +1,5 @@
{
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB"
}

@ -0,0 +1,5 @@
{
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB"
}

@ -0,0 +1,117 @@
{
"alfajores": {
"threshold": 2,
"validators": [
"0x2233a5ce12f814bd64c9cdd73410bb8693124d40",
"0xba279f965489d90f90490e3c49e860e0b43c2ae6",
"0x86485dcec5f7bb8478dd251676372d054dea6653"
],
"type": 3
},
"arbitrumgoerli": {
"threshold": 2,
"validators": [
"0x071c8d135845ae5a2cb73f98d681d519014c0a8b",
"0x1bcf03360989f15cbeb174c188288f2c6d2760d7",
"0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d"
],
"type": 3
},
"basegoerli": {
"threshold": 2,
"validators": [
"0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03",
"0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071",
"0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25"
],
"type": 3
},
"bsctestnet": {
"threshold": 2,
"validators": [
"0x242d8a855a8c932dec51f7999ae7d1e48b10c95e",
"0xf620f5e3d25a3ae848fec74bccae5de3edcd8796",
"0x1f030345963c54ff8229720dd3a711c15c554aeb"
],
"type": 3
},
"fuji": {
"threshold": 2,
"validators": [
"0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e",
"0x895ae30bc83ff1493b9cf7781b0b813d23659857",
"0x43e915573d9f1383cbf482049e4a012290759e7f"
],
"type": 3
},
"goerli": {
"threshold": 2,
"validators": [
"0x05a9b5efe9f61f9142453d8e9f61565f333c6768",
"0x43a96c7dfbd8187c95013d6ee8665650cbdb2673",
"0x7940a12c050e24e1839c21ecb12f65afd84e8c5b"
],
"type": 3
},
"moonbasealpha": {
"threshold": 2,
"validators": [
"0x521877064bd7ac7500d300f162c8c47c256a2f9c",
"0xbc1c70f58ae0459d4b8a013245420a893837d568",
"0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c"
],
"type": 3
},
"mumbai": {
"threshold": 2,
"validators": [
"0xebc301013b6cd2548e347c28d2dc43ec20c068f2",
"0x315db9868fc8813b221b1694f8760ece39f45447",
"0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5"
],
"type": 3
},
"optimismgoerli": {
"threshold": 2,
"validators": [
"0x79e58546e2faca865c6732ad5f6c4951051c4d67",
"0x7bbfe1bb7146aad7df309c637987d856179ebbc1",
"0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40"
],
"type": 3
},
"polygonzkevmtestnet": {
"threshold": 2,
"validators": [
"0x3f06b725bc9648917eb11c414e9f8d76fd959550",
"0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636",
"0xd476548222f43206d0abaa30e46e28670aa7859c"
],
"type": 3
},
"scrollsepolia": {
"threshold": 2,
"validators": [
"0xbe18dbd758afb367180260b524e6d4bcd1cb6d05",
"0x9a11ed23ae962974018ab45bc133caabff7b3271",
"0x7867bea3c9761fe64e6d124b171f91fd5dd79644"
],
"type": 3
},
"sepolia": {
"threshold": 2,
"validators": [
"0xb22b65f202558adf86a8bb2847b76ae1036686a5",
"0x469f0940684d147defc44f3647146cb90dd0bc8e",
"0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83"
],
"type": 3
},
"solanatestnet": {
"threshold": 1,
"validators": [
"0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5"
],
"type": 3
}
}

@ -0,0 +1,117 @@
{
"alfajores": {
"threshold": 2,
"validators": [
"0x2233a5ce12f814bd64c9cdd73410bb8693124d40",
"0xba279f965489d90f90490e3c49e860e0b43c2ae6",
"0x86485dcec5f7bb8478dd251676372d054dea6653"
],
"type": 3
},
"arbitrumgoerli": {
"threshold": 2,
"validators": [
"0x071c8d135845ae5a2cb73f98d681d519014c0a8b",
"0x1bcf03360989f15cbeb174c188288f2c6d2760d7",
"0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d"
],
"type": 3
},
"basegoerli": {
"threshold": 2,
"validators": [
"0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03",
"0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071",
"0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25"
],
"type": 3
},
"bsctestnet": {
"threshold": 2,
"validators": [
"0x242d8a855a8c932dec51f7999ae7d1e48b10c95e",
"0xf620f5e3d25a3ae848fec74bccae5de3edcd8796",
"0x1f030345963c54ff8229720dd3a711c15c554aeb"
],
"type": 3
},
"fuji": {
"threshold": 2,
"validators": [
"0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e",
"0x895ae30bc83ff1493b9cf7781b0b813d23659857",
"0x43e915573d9f1383cbf482049e4a012290759e7f"
],
"type": 3
},
"goerli": {
"threshold": 2,
"validators": [
"0x05a9b5efe9f61f9142453d8e9f61565f333c6768",
"0x43a96c7dfbd8187c95013d6ee8665650cbdb2673",
"0x7940a12c050e24e1839c21ecb12f65afd84e8c5b"
],
"type": 3
},
"moonbasealpha": {
"threshold": 2,
"validators": [
"0x521877064bd7ac7500d300f162c8c47c256a2f9c",
"0xbc1c70f58ae0459d4b8a013245420a893837d568",
"0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c"
],
"type": 3
},
"mumbai": {
"threshold": 2,
"validators": [
"0xebc301013b6cd2548e347c28d2dc43ec20c068f2",
"0x315db9868fc8813b221b1694f8760ece39f45447",
"0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5"
],
"type": 3
},
"optimismgoerli": {
"threshold": 2,
"validators": [
"0x79e58546e2faca865c6732ad5f6c4951051c4d67",
"0x7bbfe1bb7146aad7df309c637987d856179ebbc1",
"0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40"
],
"type": 3
},
"polygonzkevmtestnet": {
"threshold": 2,
"validators": [
"0x3f06b725bc9648917eb11c414e9f8d76fd959550",
"0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636",
"0xd476548222f43206d0abaa30e46e28670aa7859c"
],
"type": 3
},
"scrollsepolia": {
"threshold": 2,
"validators": [
"0xbe18dbd758afb367180260b524e6d4bcd1cb6d05",
"0x9a11ed23ae962974018ab45bc133caabff7b3271",
"0x7867bea3c9761fe64e6d124b171f91fd5dd79644"
],
"type": 3
},
"sepolia": {
"threshold": 2,
"validators": [
"0xb22b65f202558adf86a8bb2847b76ae1036686a5",
"0x469f0940684d147defc44f3647146cb90dd0bc8e",
"0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83"
],
"type": 3
},
"eclipsetestnet": {
"threshold": 1,
"validators": [
"0xf344f34abca9a444545b5295066348a0ae22dda3"
],
"type": 3
}
}

@ -0,0 +1,8 @@
{
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k",
"igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2",
"overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB",
"igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy"
}

@ -0,0 +1,10 @@
{
"solanatestnet": {
"hex": "0xb5eb96475b2d58f7b9a9ff2f60f46cf5ac1be244e557574971885337522b59c9",
"base58": "DF99ZiHj8aN4ETbKmpMoMsRTSbm7gtL5j6sKKLC5mkTS"
},
"eclipsetestnet": {
"hex": "0x6dcd836e4ed228f42f3b2d6e2c2723b431b8e8b0bc9dded26bdff16ffaf818bd",
"base58": "8PdCNrJqkDfSMjg9BvXrDr3Hooo7WZ6cN7Qn9mHL1GZr"
}
}

@ -0,0 +1,14 @@
{
"solanatestnet": {
"type": "native",
"decimals": 9,
"interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB"
},
"eclipsetestnet": {
"type": "synthetic",
"decimals": 9,
"name": "Solana (solanatestnet)",
"symbol": "SOL",
"interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB"
}
}

@ -347,3 +347,56 @@ pub fn transfer_igp_account_ownership_instruction(
}; };
Ok(instruction) Ok(instruction)
} }
/// Gets an instruction to claim funds from an IGP to the beneficiary.
pub fn claim_instruction(
program_id: Pubkey,
igp: Pubkey,
beneficiary: Pubkey,
) -> Result<SolanaInstruction, ProgramError> {
let ixn = Instruction::Claim;
// Accounts:
// 0. `[executable]` The system program.
// 1. `[writeable]` The IGP.
// 2. `[writeable]` The IGP beneficiary.
let accounts = vec![
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new(igp, false),
AccountMeta::new(beneficiary, false),
];
let instruction = SolanaInstruction {
program_id,
data: ixn.try_to_vec()?,
accounts,
};
Ok(instruction)
}
/// Gets an instruction to claim funds from an IGP to the beneficiary.
pub fn set_beneficiary_instruction(
program_id: Pubkey,
igp: Pubkey,
igp_owner: Pubkey,
new_beneficiary: Pubkey,
) -> Result<SolanaInstruction, ProgramError> {
let ixn = Instruction::SetIgpBeneficiary(new_beneficiary);
// Accounts:
// 0. `[]` The IGP.
// 1. `[signer]` The owner of the IGP account.
let accounts = vec![
AccountMeta::new(igp, false),
AccountMeta::new(igp_owner, true),
];
let instruction = SolanaInstruction {
program_id,
data: ixn.try_to_vec()?,
accounts,
};
Ok(instruction)
}

@ -50,7 +50,7 @@ const hyperlane: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback, rpcConsensusType: RpcConsensusType.Fallback,
docker: { docker: {
repo, repo,
tag: '8ccfdb7-20240103-084118', tag: '6360cc8-20240122-140607',
}, },
blacklist: [ blacklist: [
...releaseCandidateHelloworldMatchingList, ...releaseCandidateHelloworldMatchingList,
@ -75,7 +75,7 @@ const hyperlane: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback, rpcConsensusType: RpcConsensusType.Fallback,
docker: { docker: {
repo, repo,
tag: '8ccfdb7-20240103-084118', tag: '6360cc8-20240122-140607',
}, },
chains: validatorChainConfig(Contexts.Hyperlane), chains: validatorChainConfig(Contexts.Hyperlane),
}, },
@ -96,7 +96,7 @@ const releaseCandidate: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback, rpcConsensusType: RpcConsensusType.Fallback,
docker: { docker: {
repo, repo,
tag: '8ccfdb7-20240103-084118', tag: '6360cc8-20240122-140607',
}, },
whitelist: [...releaseCandidateHelloworldMatchingList], whitelist: [...releaseCandidateHelloworldMatchingList],
gasPaymentEnforcement, gasPaymentEnforcement,
@ -109,7 +109,7 @@ const releaseCandidate: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback, rpcConsensusType: RpcConsensusType.Fallback,
docker: { docker: {
repo, repo,
tag: '8ccfdb7-20240103-084118', tag: '6360cc8-20240122-140607',
}, },
chains: validatorChainConfig(Contexts.ReleaseCandidate), chains: validatorChainConfig(Contexts.ReleaseCandidate),
}, },

@ -31,7 +31,8 @@ export const ethereumTestnetConfigs: ChainMap<ChainMetadata> = {
// Blessed non-Ethereum chains. // Blessed non-Ethereum chains.
export const nonEthereumTestnetConfigs: ChainMap<ChainMetadata> = { export const nonEthereumTestnetConfigs: ChainMap<ChainMetadata> = {
// solanadevnet: chainMetadata.solanadevnet, solanatestnet: chainMetadata.solanatestnet,
eclipsetestnet: chainMetadata.eclipsetestnet,
}; };
export const testnetConfigs: ChainMap<ChainMetadata> = { export const testnetConfigs: ChainMap<ChainMetadata> = {
@ -54,6 +55,6 @@ export const agentChainNames: AgentChainNames = {
// Run validators for all chains. // Run validators for all chains.
[Role.Validator]: supportedChainNames, [Role.Validator]: supportedChainNames,
// Only run relayers for Ethereum chains at the moment. // Only run relayers for Ethereum chains at the moment.
[Role.Relayer]: ethereumChainNames, [Role.Relayer]: supportedChainNames,
[Role.Scraper]: ethereumChainNames, [Role.Scraper]: ethereumChainNames,
}; };

@ -29,7 +29,8 @@ const gasPrices: ChainMap<BigNumber> = {
lineagoerli: ethers.utils.parseUnits('1', 'gwei'), lineagoerli: ethers.utils.parseUnits('1', 'gwei'),
polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'), polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'),
chiado: ethers.utils.parseUnits('2', 'gwei'), chiado: ethers.utils.parseUnits('2', 'gwei'),
// solanadevnet: ethers.BigNumber.from('28'), solanatestnet: ethers.BigNumber.from('28'),
eclipsetestnet: ethers.BigNumber.from('28'),
}; };
// Used to categorize rarity of testnet tokens & approximate exchange rates. // Used to categorize rarity of testnet tokens & approximate exchange rates.
@ -63,7 +64,8 @@ const chainTokenRarity: ChainMap<Rarity> = {
lineagoerli: Rarity.Rare, lineagoerli: Rarity.Rare,
polygonzkevmtestnet: Rarity.Common, polygonzkevmtestnet: Rarity.Common,
chiado: Rarity.Common, chiado: Rarity.Common,
// solanadevnet: Rarity.Common, solanatestnet: Rarity.Common,
eclipsetestnet: Rarity.Common,
}; };
// Gets the "value" of a testnet chain // Gets the "value" of a testnet chain

@ -306,22 +306,29 @@ export const validatorChainConfig = (
// 'proteustestnet', // 'proteustestnet',
// ), // ),
// }, // },
// solanadevnet: { solanatestnet: {
// interval: 10, interval: 1,
// reorgPeriod: getReorgPeriod(chainMetadata.solanadevnet), reorgPeriod: getReorgPeriod(chainMetadata.solanatestnet),
// validators: validatorsConfig( validators: validatorsConfig(
// { {
// [Contexts.Hyperlane]: [ [Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'],
// '0xec0f73dbc5b1962a20f7dcbe07c98414025b0c43', [Contexts.ReleaseCandidate]: [],
// '0x9c20a149dfa09ea9f77f5a7ca09ed44f9c025133', [Contexts.Neutron]: [],
// '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', },
// ], 'solanatestnet',
// [Contexts.ReleaseCandidate]: [ ),
// '0x21b9eff4d1a6d3122596c7fb80315bf094b6e5c2', },
// ], eclipsetestnet: {
// }, interval: 1,
// 'solanadevnet', reorgPeriod: getReorgPeriod(chainMetadata.eclipsetestnet),
// ), validators: validatorsConfig(
// }, {
[Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'eclipsetestnet',
),
},
}; };
}; };

@ -929,6 +929,33 @@ export const solanadevnet: ChainMetadata = {
rpcUrls: [{ http: 'https://api.devnet.solana.com' }], rpcUrls: [{ http: 'https://api.devnet.solana.com' }],
}; };
export const eclipsetestnet: ChainMetadata = {
blockExplorers: [
{
apiUrl: 'https://testnet.dev2.eclipsenetwork.xyz',
family: ExplorerFamily.Other,
name: 'Eclipse Testnet Explorer',
url: 'https://explorer.dev.eclipsenetwork.xyz/?cluster=testnet',
},
],
blocks: {
confirmations: 1,
estimateBlockTime: 0.4,
reorgPeriod: 0,
},
chainId: 239092742,
displayName: 'Eclipse Testnet',
domainId: 239092742,
isTestnet: true,
name: 'eclipsetestnet',
nativeToken: {
...etherToken,
decimals: 9,
},
protocol: ProtocolType.Sealevel,
rpcUrls: [{ http: 'https://testnet.dev2.eclipsenetwork.xyz' }],
};
export const test1: ChainMetadata = { export const test1: ChainMetadata = {
blockExplorers: [], blockExplorers: [],
blocks: { blocks: {
@ -997,6 +1024,7 @@ export const chainMetadata: ChainMap<ChainMetadata> = {
bsctestnet, bsctestnet,
celo, celo,
chiado, chiado,
eclipsetestnet,
ethereum, ethereum,
fuji, fuji,
gnosis, gnosis,

@ -35,6 +35,8 @@ export enum Chains {
sepolia = 'sepolia', sepolia = 'sepolia',
solana = 'solana', solana = 'solana',
solanadevnet = 'solanadevnet', solanadevnet = 'solanadevnet',
solanatestnet = 'solanatestnet',
eclipsetestnet = 'eclipsetestnet',
test1 = 'test1', test1 = 'test1',
test2 = 'test2', test2 = 'test2',
test3 = 'test3', test3 = 'test3',
@ -87,6 +89,8 @@ export const Testnets: Array<CoreChainName> = [
Chains.scrollsepolia, Chains.scrollsepolia,
Chains.sepolia, Chains.sepolia,
Chains.solanadevnet, Chains.solanadevnet,
Chains.solanatestnet,
Chains.eclipsetestnet,
]; ];
export const TestChains: Array<CoreChainName> = [ export const TestChains: Array<CoreChainName> = [

@ -99,6 +99,11 @@ export const defaultMultisigConfigs: ChainMap<MultisigConfig> = {
], ],
}, },
eclipsetestnet: {
threshold: 1,
validators: ['0xf344f34abca9a444545b5295066348a0ae22dda3'],
},
ethereum: { ethereum: {
threshold: 3, threshold: 3,
validators: [ validators: [
@ -281,4 +286,9 @@ export const defaultMultisigConfigs: ChainMap<MultisigConfig> = {
'0x967c5ecdf2625ae86580bd203b630abaaf85cd62', '0x967c5ecdf2625ae86580bd203b630abaaf85cd62',
], ],
}, },
solanatestnet: {
threshold: 1,
validators: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'],
},
}; };

Loading…
Cancel
Save