diff --git a/.changeset/long-llamas-fly.md b/.changeset/long-llamas-fly.md index c0d6d01e9b..f9da56dd0f 100644 --- a/.changeset/long-llamas-fly.md +++ b/.changeset/long-llamas-fly.md @@ -1,5 +1,5 @@ --- -"@hyperlane-xyz/cli": patch +'@hyperlane-xyz/cli': patch --- Suppress help on CLI failures diff --git a/.changeset/lovely-planes-end.md b/.changeset/lovely-planes-end.md new file mode 100644 index 0000000000..81bd6a8598 --- /dev/null +++ b/.changeset/lovely-planes-end.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Allow self-relaying of all messages if there are multiple in a given dispatch transaction. diff --git a/.changeset/neat-apples-marry.md b/.changeset/neat-apples-marry.md new file mode 100644 index 0000000000..514ac94ce8 --- /dev/null +++ b/.changeset/neat-apples-marry.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/infra': minor +--- + +added ubtc route extension config + usdc from appchain to base diff --git a/.changeset/polite-bulldogs-sit.md b/.changeset/polite-bulldogs-sit.md new file mode 100644 index 0000000000..6c68774325 --- /dev/null +++ b/.changeset/polite-bulldogs-sit.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': patch +--- + +Fix strategy flag propagation diff --git a/.changeset/serious-beers-lay.md b/.changeset/serious-beers-lay.md new file mode 100644 index 0000000000..6f8fbffc34 --- /dev/null +++ b/.changeset/serious-beers-lay.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/widgets': patch +--- + +Allow empty data field in ethers5TxToWagmiTx diff --git a/.changeset/spotty-bees-worry.md b/.changeset/spotty-bees-worry.md new file mode 100644 index 0000000000..7697c5fd9b --- /dev/null +++ b/.changeset/spotty-bees-worry.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Export TOKEN_STANDARD_TO_PROVIDER_TYPE, XERC20_STANDARDS, and MINT_LIMITED_STANDARDS maps diff --git a/.changeset/spotty-guests-dance.md b/.changeset/spotty-guests-dance.md new file mode 100644 index 0000000000..39eb9b0f49 --- /dev/null +++ b/.changeset/spotty-guests-dance.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Added new Sealevel tx submission and priority fee oracle params to agent config types diff --git a/.changeset/stupid-seahorses-yell.md b/.changeset/stupid-seahorses-yell.md new file mode 100644 index 0000000000..61230350a4 --- /dev/null +++ b/.changeset/stupid-seahorses-yell.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/utils': patch +--- + +Require concurrency > 0 for concurrentMap diff --git a/.changeset/two-jeans-sin.md b/.changeset/two-jeans-sin.md new file mode 100644 index 0000000000..2cc997f9bb --- /dev/null +++ b/.changeset/two-jeans-sin.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add chain technical stack selector with Arbitrum Nitro support to `hyperlane registry init` command diff --git a/.changeset/yellow-icons-do.md b/.changeset/yellow-icons-do.md new file mode 100644 index 0000000000..c8e5a583ed --- /dev/null +++ b/.changeset/yellow-icons-do.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Update single chain selection to be searchable instead of a simple select diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 98426a76a6..7f491f8f11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -126,10 +126,16 @@ jobs: fail-fast: false matrix: test: + # Core Commands - core-apply + - core-check - core-deploy + - core-init - core-read + # Other commands - relay + # Warp Commands + - warp-init - warp-read - warp-apply - warp-deploy @@ -301,7 +307,7 @@ jobs: fail-fast: false matrix: environment: [mainnet3] - chain: [ethereum, arbitrum, optimism, inevm, viction] + chain: [ethereum, arbitrum, optimism, inevm] module: [core, igp] include: - environment: testnet4 diff --git a/.registryrc b/.registryrc index 088ae6645a..7c11b8020a 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -d3e1e71965d7d06a8f8761c8255e718699c78f11 +207a4ffe96b0f33701ca6f92d8ca4905a8a0233a diff --git a/rust/README.md b/rust/README.md index 35050b1e79..2e4577deac 100644 --- a/rust/README.md +++ b/rust/README.md @@ -119,11 +119,10 @@ cargo test --release --package run-locally --bin run-locally --features cosmos - ### Building Agent Docker Images There exists a docker build for the agent binaries. These docker images are used for deploying the agents in a -production environment. +production environment. You should run this at the top level of the repo. ```bash -cd rust -./build.sh +./rust/build.sh ``` ### Deploy Procedure diff --git a/rust/build.sh b/rust/build.sh index 65214900b5..16af4721ba 100755 --- a/rust/build.sh +++ b/rust/build.sh @@ -19,4 +19,4 @@ if [[ -z $TAG ]]; then fi fi -DOCKER_BUILDKIT=1 docker build $PLATFORM -t gcr.io/abacus-labs-dev/hyperlane-agent:$TAG . +DOCKER_BUILDKIT=1 docker build -f rust/Dockerfile $PLATFORM -t gcr.io/abacus-labs-dev/hyperlane-agent:$TAG . diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index c14e211f03..47aec03422 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -512,6 +512,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", + "axum-macros", "bitflags 1.3.2", "bytes", "futures-util", @@ -553,6 +554,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +dependencies = [ + "heck 0.4.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.77", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -2901,7 +2914,7 @@ dependencies = [ [[package]] name = "ethers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2915,7 +2928,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "ethers-core", "once_cell", @@ -2926,7 +2939,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2944,7 +2957,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "Inflector", "cfg-if", @@ -2968,7 +2981,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -2982,7 +2995,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "arrayvec", "bytes", @@ -3012,7 +3025,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "ethers-core", "getrandom 0.2.15", @@ -3028,7 +3041,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -3047,6 +3060,7 @@ dependencies = [ "tokio", "tracing", "tracing-futures", + "tracing-test", "url", ] @@ -3076,7 +3090,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "async-trait", "auto_impl 1.2.0", @@ -3112,7 +3126,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-16#c5c2a74660675ab968cf585893325e2faa807d14" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-30#31653ac8c54ae3fe78b91bf5d92a1ab71604d07e" dependencies = [ "async-trait", "coins-bip32 0.7.0", @@ -7014,7 +7028,9 @@ dependencies = [ "tokio-test", "tracing", "tracing-futures", + "tracing-test", "typetag", + "uuid 1.11.0", ] [[package]] @@ -7149,7 +7165,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -7249,6 +7265,7 @@ dependencies = [ "once_cell", "regex", "relayer", + "reqwest", "ripemd", "serde", "serde_json", @@ -7748,7 +7765,7 @@ dependencies = [ "time", "tracing", "url", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -7809,7 +7826,7 @@ dependencies = [ "sea-query-derive", "serde_json", "time", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -7825,7 +7842,7 @@ dependencies = [ "serde_json", "sqlx", "time", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -9125,7 +9142,7 @@ dependencies = [ "time", "tokio-stream", "url", - "uuid 1.10.0", + "uuid 1.11.0", "whoami", ] @@ -10362,10 +10379,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ + "getrandom 0.2.15", "serde", ] diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 55cfc1573a..d9fee1663f 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -153,6 +153,7 @@ typetag = "0.2" uint = "0.9.5" ureq = { version = "2.4", default-features = false } url = "2.3" +uuid = { version = "1.11.0", features = ["v4"] } walkdir = "2" warp = "0.3" which = "4.3" @@ -198,27 +199,27 @@ overflow-checks = true [workspace.dependencies.ethers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-16" +tag = "2024-12-30" [workspace.dependencies.ethers-contract] features = ["legacy"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-16" +tag = "2024-12-30" [workspace.dependencies.ethers-core] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-16" +tag = "2024-12-30" [workspace.dependencies.ethers-providers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-16" +tag = "2024-12-30" [workspace.dependencies.ethers-signers] features = ["aws"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-16" +tag = "2024-12-30" [patch.crates-io.curve25519-dalek] branch = "v3.2.2-relax-zeroize" diff --git a/rust/main/agents/relayer/Cargo.toml b/rust/main/agents/relayer/Cargo.toml index 5a891d912c..fcd89da771 100644 --- a/rust/main/agents/relayer/Cargo.toml +++ b/rust/main/agents/relayer/Cargo.toml @@ -44,6 +44,7 @@ tokio-metrics.workspace = true tracing-futures.workspace = true tracing.workspace = true typetag.workspace = true +uuid.workspace = true hyperlane-core = { path = "../../hyperlane-core", features = [ "agent", @@ -53,12 +54,14 @@ hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } [dev-dependencies] +axum = { workspace = true, features = ["macros"] } once_cell.workspace = true mockall.workspace = true tokio-test.workspace = true hyperlane-test = { path = "../../hyperlane-test" } hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] } hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "async", "test-utils"] } +tracing-test.workspace = true [features] default = ["color-eyre", "oneline-errors"] diff --git a/rust/main/agents/relayer/src/lib.rs b/rust/main/agents/relayer/src/lib.rs index 62b896d628..9a6e1e4147 100644 --- a/rust/main/agents/relayer/src/lib.rs +++ b/rust/main/agents/relayer/src/lib.rs @@ -3,8 +3,9 @@ mod msg; mod processor; mod prover; mod relayer; -mod server; mod settings; +pub mod server; + pub use msg::GAS_EXPENDITURE_LOG_MESSAGE; pub use relayer::*; diff --git a/rust/main/agents/relayer/src/merkle_tree/processor.rs b/rust/main/agents/relayer/src/merkle_tree/processor.rs index 9ddcc2ee0c..e12a7fc343 100644 --- a/rust/main/agents/relayer/src/merkle_tree/processor.rs +++ b/rust/main/agents/relayer/src/merkle_tree/processor.rs @@ -7,7 +7,10 @@ use std::{ use async_trait::async_trait; use derive_new::new; use eyre::Result; -use hyperlane_base::db::{HyperlaneDb, HyperlaneRocksDB}; +use hyperlane_base::{ + db::{HyperlaneDb, HyperlaneRocksDB}, + CoreMetrics, +}; use hyperlane_core::{HyperlaneDomain, MerkleTreeInsertion}; use prometheus::IntGauge; use tokio::sync::RwLock; @@ -71,8 +74,9 @@ impl MerkleTreeProcessor { .retrieve_merkle_tree_insertion_by_leaf_index(&self.leaf_index)? { // Update the metrics + // we assume that leaves are inserted in order so this will be monotonically increasing self.metrics - .max_leaf_index_gauge + .latest_leaf_index_gauge .set(insertion.index() as i64); Some(insertion) } else { @@ -85,17 +89,15 @@ impl MerkleTreeProcessor { #[derive(Debug)] pub struct MerkleTreeProcessorMetrics { - max_leaf_index_gauge: IntGauge, + latest_leaf_index_gauge: IntGauge, } impl MerkleTreeProcessorMetrics { - pub fn new() -> Self { + pub fn new(metrics: &CoreMetrics, origin: &HyperlaneDomain) -> Self { Self { - max_leaf_index_gauge: IntGauge::new( - "max_leaf_index_gauge", - "The max merkle tree leaf index", - ) - .unwrap(), + latest_leaf_index_gauge: metrics + .latest_leaf_index() + .with_label_values(&[origin.name()]), } } } diff --git a/rust/main/agents/relayer/src/msg/op_queue.rs b/rust/main/agents/relayer/src/msg/op_queue.rs index 99c9dde39f..d0640bc2a3 100644 --- a/rust/main/agents/relayer/src/msg/op_queue.rs +++ b/rust/main/agents/relayer/src/msg/op_queue.rs @@ -6,7 +6,7 @@ use prometheus::{IntGauge, IntGaugeVec}; use tokio::sync::{broadcast::Receiver, Mutex}; use tracing::{debug, info, instrument}; -use crate::settings::matching_list::MatchingList; +use crate::server::{MessageRetryRequest, MessageRetryResponse}; pub type OperationPriorityQueue = Arc>>>; @@ -16,7 +16,7 @@ pub type OperationPriorityQueue = Arc>> pub struct OpQueue { metrics: IntGaugeVec, queue_metrics_label: String, - retry_rx: Arc>>, + retry_receiver: Arc>>, #[new(default)] pub queue: OperationPriorityQueue, } @@ -72,27 +72,67 @@ impl OpQueue { // The other consideration is whether to put the channel receiver in the OpQueue or in a dedicated task // that also holds an Arc to the Mutex. For simplicity, we'll put it in the OpQueue for now. let mut message_retry_requests = vec![]; - while let Ok(message_id) = self.retry_rx.lock().await.try_recv() { - message_retry_requests.push(message_id); + + while let Ok(retry_request) = self.retry_receiver.lock().await.try_recv() { + let uuid = retry_request.uuid.clone(); + message_retry_requests.push(( + retry_request, + MessageRetryResponse { + uuid, + evaluated: 0, + matched: 0, + }, + )); } + if message_retry_requests.is_empty() { return; } + let mut queue = self.queue.lock().await; + let queue_length = queue.len(); + let mut reprioritized_queue: BinaryHeap<_> = queue .drain() .map(|Reverse(mut op)| { - if message_retry_requests.iter().any(|r| r.op_matches(&op)) { + let matched_requests: Vec<_> = message_retry_requests + .iter_mut() + .filter_map(|(retry_req, retry_response)| { + // update retry metrics + if retry_req.pattern.op_matches(&op) { + debug!(uuid = retry_req.uuid, "Matched request"); + retry_response.matched += 1; + Some(retry_req.uuid.clone()) + } else { + None + } + }) + .collect(); + + if !matched_requests.is_empty() { info!( operation = %op, queue_label = %self.queue_metrics_label, "Retrying OpQueue operation" ); - op.reset_attempts() + op.reset_attempts(); } Reverse(op) }) .collect(); + + for (retry_req, mut retry_response) in message_retry_requests { + retry_response.evaluated = queue_length; + tracing::debug!( + uuid = retry_response.uuid, + evaluated = retry_response.evaluated, + matched = retry_response.matched, + "Sending relayer retry response back" + ); + if let Err(err) = retry_req.transmitter.send(retry_response).await { + tracing::error!(?err, "Failed to send retry response"); + } + } queue.append(&mut reprioritized_queue); } @@ -115,7 +155,10 @@ impl OpQueue { #[cfg(test)] pub mod test { + use crate::{server::ENDPOINT_MESSAGES_QUEUE_SIZE, settings::matching_list::MatchingList}; + use super::*; + use hyperlane_core::{ HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneDomainTechnicalStack, HyperlaneDomainType, HyperlaneMessage, KnownHyperlaneDomain, PendingOperationResult, @@ -127,7 +170,7 @@ pub mod test { str::FromStr, time::{Duration, Instant}, }; - use tokio::sync; + use tokio::sync::{self, mpsc}; #[derive(Debug, Clone, Serialize)] pub struct MockPendingOperation { @@ -320,6 +363,7 @@ pub mod test { async fn test_multiple_op_queues_message_id() { let (metrics, queue_metrics_label) = dummy_metrics_and_label(); let broadcaster = sync::broadcast::Sender::new(100); + let mut op_queue_1 = OpQueue::new( metrics.clone(), queue_metrics_label.clone(), @@ -364,12 +408,22 @@ pub mod test { .await; } + let (transmitter, _receiver) = mpsc::channel(ENDPOINT_MESSAGES_QUEUE_SIZE); + // Retry by message ids broadcaster - .send(MatchingList::with_message_id(op_ids[1])) + .send(MessageRetryRequest { + uuid: "0e92ace7-ba5d-4a1f-8501-51b6d9d500cf".to_string(), + pattern: MatchingList::with_message_id(op_ids[1]), + transmitter: transmitter.clone(), + }) .unwrap(); broadcaster - .send(MatchingList::with_message_id(op_ids[2])) + .send(MessageRetryRequest { + uuid: "59400966-e7fa-4fb9-9372-9a671d4392c3".to_string(), + pattern: MatchingList::with_message_id(op_ids[2]), + transmitter, + }) .unwrap(); // Pop elements from queue 1 @@ -399,6 +453,7 @@ pub mod test { async fn test_destination_domain() { let (metrics, queue_metrics_label) = dummy_metrics_and_label(); let broadcaster = sync::broadcast::Sender::new(100); + let mut op_queue = OpQueue::new( metrics.clone(), queue_metrics_label.clone(), @@ -425,11 +480,15 @@ pub mod test { .await; } + let (transmitter, _receiver) = mpsc::channel(ENDPOINT_MESSAGES_QUEUE_SIZE); + // Retry by domain broadcaster - .send(MatchingList::with_destination_domain( - destination_domain_2.id(), - )) + .send(MessageRetryRequest { + uuid: "a5b39473-7cc5-48a1-8bed-565454ba1037".to_string(), + pattern: MatchingList::with_destination_domain(destination_domain_2.id()), + transmitter, + }) .unwrap(); // Pop elements from queue diff --git a/rust/main/agents/relayer/src/msg/op_submitter.rs b/rust/main/agents/relayer/src/msg/op_submitter.rs index c1e295a24a..f35a991c45 100644 --- a/rust/main/agents/relayer/src/msg/op_submitter.rs +++ b/rust/main/agents/relayer/src/msg/op_submitter.rs @@ -32,7 +32,7 @@ use hyperlane_core::{ }; use crate::msg::pending_message::CONFIRM_DELAY; -use crate::settings::matching_list::MatchingList; +use crate::server::MessageRetryRequest; use super::op_queue::OpQueue; use super::op_queue::OperationPriorityQueue; @@ -105,7 +105,7 @@ impl SerialSubmitter { pub fn new( domain: HyperlaneDomain, rx: mpsc::UnboundedReceiver, - retry_op_transmitter: Sender, + retry_op_transmitter: Sender, metrics: SerialSubmitterMetrics, max_batch_size: u32, task_monitor: TaskMonitor, diff --git a/rust/main/agents/relayer/src/msg/pending_message.rs b/rust/main/agents/relayer/src/msg/pending_message.rs index d63b7b03f3..3c03094fc5 100644 --- a/rust/main/agents/relayer/src/msg/pending_message.rs +++ b/rust/main/agents/relayer/src/msg/pending_message.rs @@ -338,7 +338,7 @@ impl PendingOperation for PendingMessage { PendingOperationResult::Success } - #[instrument] + #[instrument(skip(self), fields(id=?self.id(), domain=%self.destination_domain()))] async fn submit(&mut self) -> PendingOperationResult { if self.submitted { // this message has already been submitted, possibly not by us diff --git a/rust/main/agents/relayer/src/relayer.rs b/rust/main/agents/relayer/src/relayer.rs index 6bd1a63a8d..4c3d8d33bb 100644 --- a/rust/main/agents/relayer/src/relayer.rs +++ b/rust/main/agents/relayer/src/relayer.rs @@ -318,10 +318,11 @@ impl BaseAgent for Relayer { })); tasks.push(console_server.instrument(info_span!("Tokio console server"))); } - let sender = BroadcastSender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + let sender = BroadcastSender::new(ENDPOINT_MESSAGES_QUEUE_SIZE); // send channels by destination chain let mut send_channels = HashMap::with_capacity(self.destination_chains.len()); let mut prep_queues = HashMap::with_capacity(self.destination_chains.len()); + for (dest_domain, dest_conf) in &self.destination_chains { let (send_channel, receive_channel) = mpsc::unbounded_channel::(); send_channels.insert(dest_domain.id(), send_channel); @@ -385,7 +386,7 @@ impl BaseAgent for Relayer { ); } // run server - let custom_routes = relayer_server::Server::new() + let custom_routes = relayer_server::Server::new(self.origin_chains.len()) .with_op_retry(sender.clone()) .with_message_queue(prep_queues) .routes(); @@ -587,7 +588,7 @@ impl Relayer { origin: &HyperlaneDomain, task_monitor: TaskMonitor, ) -> Instrumented> { - let metrics = MerkleTreeProcessorMetrics::new(); + let metrics = MerkleTreeProcessorMetrics::new(&self.core.metrics, origin); let merkle_tree_processor = MerkleTreeProcessor::new( self.dbs.get(origin).unwrap().clone(), metrics, diff --git a/rust/main/agents/relayer/src/server/list_messages.rs b/rust/main/agents/relayer/src/server/list_messages.rs index e21f39a5df..f6c92ba088 100644 --- a/rust/main/agents/relayer/src/server/list_messages.rs +++ b/rust/main/agents/relayer/src/server/list_messages.rs @@ -97,6 +97,7 @@ mod tests { fn setup_test_server() -> (SocketAddr, OperationPriorityQueue) { let (metrics, queue_metrics_label) = dummy_metrics_and_label(); let broadcaster = sync::broadcast::Sender::new(100); + let op_queue = OpQueue::new( metrics.clone(), queue_metrics_label.clone(), diff --git a/rust/main/agents/relayer/src/server/message_retry.rs b/rust/main/agents/relayer/src/server/message_retry.rs index 6d160355a5..255614b43e 100644 --- a/rust/main/agents/relayer/src/server/message_retry.rs +++ b/rust/main/agents/relayer/src/server/message_retry.rs @@ -1,32 +1,89 @@ use crate::settings::matching_list::MatchingList; + use axum::{extract::State, routing, Json, Router}; + use derive_new::new; -use tokio::sync::broadcast::Sender; +use serde::{Deserialize, Serialize}; +use tokio::sync::{broadcast::Sender, mpsc}; const MESSAGE_RETRY_API_BASE: &str = "/message_retry"; -#[derive(new, Clone)] +#[derive(Clone, Debug, new)] pub struct MessageRetryApi { - tx: Sender, + retry_request_transmitter: Sender, + relayer_chains: usize, +} + +#[derive(Clone, Debug)] +pub struct MessageRetryRequest { + pub uuid: String, + pub pattern: MatchingList, + pub transmitter: mpsc::Sender, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct MessageRetryResponse { + /// ID of the retry request + pub uuid: String, + /// how many pending operations were evaluated + pub evaluated: usize, + /// how many of the pending operations matched the retry request pattern + pub matched: u64, } async fn retry_message( - State(tx): State>, + State(state): State, Json(retry_req_payload): Json, -) -> String { - match tx.send(retry_req_payload) { - Ok(_) => "Moved message(s) to the front of the queue".to_string(), - // Technically it's bad practice to print the error message to the user, but - // this endpoint is for debugging purposes only. - Err(err) => format!("Failed to send retry request to the queue: {}", err), +) -> Result, String> { + let uuid = uuid::Uuid::new_v4(); + let uuid_string = uuid.to_string(); + + tracing::debug!(uuid = uuid_string, "Sending message retry request"); + + // This channel is only created to service this single + // retry request so we're expecting a single response + // from each transmitter end, hence we are using a channel of size 1 + let (transmitter, mut receiver) = mpsc::channel(state.relayer_chains); + state + .retry_request_transmitter + .send(MessageRetryRequest { + uuid: uuid_string.clone(), + pattern: retry_req_payload, + transmitter, + }) + .map_err(|err| { + // Technically it's bad practice to print the error message to the user, but + // this endpoint is for debugging purposes only. + format!("Failed to send retry request to the queue: {}", err) + })?; + + let mut resp = MessageRetryResponse { + uuid: uuid_string, + evaluated: 0, + matched: 0, + }; + + // Wait for responses from relayer + tracing::debug!(uuid = resp.uuid, "Waiting for response from relayer"); + while let Some(relayer_resp) = receiver.recv().await { + tracing::debug!( + uuid = resp.uuid, + evaluated = resp.evaluated, + matched = resp.matched, + "Submitter response to retry request" + ); + resp.evaluated += relayer_resp.evaluated; + resp.matched += relayer_resp.matched; } + + Ok(Json(resp)) } impl MessageRetryApi { pub fn router(&self) -> Router { Router::new() .route("/", routing::post(retry_message)) - .with_state(self.tx.clone()) + .with_state(self.clone()) } pub fn get_route(&self) -> (&'static str, Router) { @@ -41,13 +98,21 @@ mod tests { use super::*; use axum::http::StatusCode; use hyperlane_core::{HyperlaneMessage, QueueOperation}; + use serde::de::DeserializeOwned; use serde_json::json; use std::net::SocketAddr; use tokio::sync::broadcast::{Receiver, Sender}; - fn setup_test_server() -> (SocketAddr, Receiver) { - let broadcast_tx = Sender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); - let message_retry_api = MessageRetryApi::new(broadcast_tx.clone()); + #[derive(Debug)] + struct TestServerSetup { + pub socket_address: SocketAddr, + pub retry_req_rx: Receiver, + } + + fn setup_test_server() -> TestServerSetup { + let broadcast_tx = Sender::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + + let message_retry_api = MessageRetryApi::new(broadcast_tx.clone(), 10); let (path, retry_router) = message_retry_api.get_route(); let app = Router::new().nest(path, retry_router); @@ -58,12 +123,51 @@ mod tests { let addr = server.local_addr(); tokio::spawn(server); - (addr, broadcast_tx.subscribe()) + let retry_req_rx = broadcast_tx.subscribe(); + + TestServerSetup { + socket_address: addr, + retry_req_rx, + } + } + + async fn send_retry_responses_future( + mut retry_request_receiver: Receiver, + pending_operations: Vec, + metrics: Vec<(usize, u64)>, + ) { + if let Ok(req) = retry_request_receiver.recv().await { + for (op, (evaluated, matched)) in pending_operations.iter().zip(metrics) { + // Check that the list received by the server matches the pending operation + assert!(req.pattern.op_matches(&op)); + let resp = MessageRetryResponse { + uuid: req.uuid.clone(), + evaluated, + matched, + }; + req.transmitter.send(resp).await.unwrap(); + } + } } + async fn parse_response_to_json(response: reqwest::Response) -> T { + let resp_body = response + .text() + .await + .expect("Failed to parse response body"); + let resp_json: T = + serde_json::from_str(&resp_body).expect("Failed to deserialize response body"); + resp_json + } + + #[tracing_test::traced_test] #[tokio::test] async fn test_message_id_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); // Create a random message with a random message ID @@ -75,25 +179,37 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + + let (_t1, response_res) = tokio::join!(respond_task, response); + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } #[tokio::test] async fn test_destination_domain_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); let message = HyperlaneMessage { @@ -108,25 +224,37 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + + let (_t1, response_res) = tokio::join!(respond_task, response); + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } #[tokio::test] async fn test_origin_domain_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); let message = HyperlaneMessage { @@ -141,25 +269,37 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + let (_t1, response_res) = tokio::join!(respond_task, response); + + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } #[tokio::test] async fn test_sender_address_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); let message = HyperlaneMessage::default(); @@ -170,25 +310,37 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + + let (_t1, response_res) = tokio::join!(respond_task, response); + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } #[tokio::test] async fn test_recipient_address_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); let message = HyperlaneMessage::default(); @@ -199,25 +351,37 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + let (_t1, response_res) = tokio::join!(respond_task, response); + + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } #[tokio::test] async fn test_multiple_retry() { - let (addr, mut rx) = setup_test_server(); + let TestServerSetup { + socket_address: addr, + retry_req_rx, + .. + } = setup_test_server(); let client = reqwest::Client::new(); let message = HyperlaneMessage { @@ -238,19 +402,27 @@ mod tests { } ]); + // spawn a task to respond to message retry request + let respond_task = send_retry_responses_future( + retry_req_rx, + vec![Box::new(pending_operation.clone()) as QueueOperation], + vec![(1, 1)], + ); + // Send a POST request to the server let response = client .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) .json(&matching_list_body) // Set the request body - .send() - .await - .unwrap(); + .send(); + + let (_t1, response_res) = tokio::join!(respond_task, response); + let response = response_res.unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - let list = rx.try_recv().unwrap(); - // Check that the list received by the server matches the pending operation - assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + let resp_json: MessageRetryResponse = parse_response_to_json(response).await; + assert_eq!(resp_json.evaluated, 1); + assert_eq!(resp_json.matched, 1); } } diff --git a/rust/main/agents/relayer/src/server/mod.rs b/rust/main/agents/relayer/src/server/mod.rs index 083f8d94d2..8cc49a3f25 100644 --- a/rust/main/agents/relayer/src/server/mod.rs +++ b/rust/main/agents/relayer/src/server/mod.rs @@ -3,7 +3,7 @@ use derive_new::new; use std::collections::HashMap; use tokio::sync::broadcast::Sender; -use crate::{msg::op_queue::OperationPriorityQueue, settings::matching_list::MatchingList}; +use crate::msg::op_queue::OperationPriorityQueue; pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 100; @@ -15,14 +15,15 @@ mod message_retry; #[derive(new)] pub struct Server { + relayer_chains: usize, #[new(default)] - retry_transmitter: Option>, + retry_transmitter: Option>, #[new(default)] op_queues: Option>, } impl Server { - pub fn with_op_retry(mut self, transmitter: Sender) -> Self { + pub fn with_op_retry(mut self, transmitter: Sender) -> Self { self.retry_transmitter = Some(transmitter); self } @@ -36,8 +37,8 @@ impl Server { /// Can be extended with additional routes and feature flags to enable/disable individually. pub fn routes(self) -> Vec<(&'static str, Router)> { let mut routes = vec![]; - if let Some(retry_transmitter) = self.retry_transmitter { - routes.push(MessageRetryApi::new(retry_transmitter).get_route()); + if let Some(tx) = self.retry_transmitter { + routes.push(MessageRetryApi::new(tx, self.relayer_chains).get_route()); } if let Some(op_queues) = self.op_queues { routes.push(ListOperationsApi::new(op_queues).get_route()); diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs index 1e9bfaa041..85a709223b 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs @@ -496,7 +496,7 @@ where .into()) } - #[instrument(skip(self), fields(metadata=%bytes_to_hex(metadata)))] + #[instrument(skip(self, message, metadata), fields(metadata=%bytes_to_hex(metadata)))] async fn process( &self, message: &HyperlaneMessage, diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs index 7c57e61702..59baeec762 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs @@ -10,7 +10,7 @@ use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use thiserror::Error; use tokio::time::sleep; -use tracing::{debug, error, instrument, trace, warn_span}; +use tracing::{error, instrument, trace, warn, warn_span}; /// An HTTP Provider with a simple naive exponential backoff built-in #[derive(Debug, Clone)] @@ -89,13 +89,16 @@ where { let params = serde_json::to_value(params).expect("valid"); - let mut last_err; + let mut last_err = None; let mut i = 1; loop { let mut rate_limited = false; let backoff_ms = self.base_retry_ms * 2u64.pow(i - 1); - trace!(params = %serde_json::to_string(¶ms).unwrap_or_default(), "Dispatching request with params"); - debug!(attempt = i, "Dispatching request"); + if let Some(ref last_err) = last_err { + // `last_err` is always expected to be `Some` if `i > 1` + warn!(attempt = i, ?last_err, "Dispatching request"); + } + trace!(attempt = i, params = %serde_json::to_string(¶ms).unwrap_or_default(), "Dispatching request"); let fut = match params { Value::Null => self.inner.request(method, ()), @@ -110,10 +113,10 @@ where return Err(RetryingProviderError::JsonRpcClientError(e)); } HandleMethod::Retry(e) => { - last_err = e; + last_err = Some(e); } HandleMethod::RateLimitedRetry(e) => { - last_err = e; + last_err = Some(e); rate_limited = true; } } @@ -128,7 +131,7 @@ where trace!(backoff_ms, rate_limited, "Retrying provider going to sleep"); sleep(Duration::from_millis(backoff_ms)).await; } else { - trace!( + warn!( requests_made = self.max_requests, "Retrying provider reached max requests" ); @@ -150,7 +153,7 @@ where JsonRpcClientError(P::Error), /// Hit max requests #[error("Hit max requests")] - MaxRequests(P::Error), + MaxRequests(Option), } impl

From> for ProviderError diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index 90a2e01b66..6121bbe27f 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/chains/hyperlane-sealevel/src/lib.rs @@ -23,8 +23,10 @@ mod log_meta_composer; mod mailbox; mod merkle_tree_hook; mod multisig_ism; +mod priority_fee; mod provider; mod rpc; mod trait_builder; +mod tx_submitter; mod utils; mod validator_announce; diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs index f530b04e0f..5e3da84942 100644 --- a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs @@ -220,6 +220,11 @@ fn filter_by_validity( tx: UiTransaction, meta: UiTransactionStatusMeta, ) -> Option<(H512, Vec, Vec)> { + // If the transaction has an error, we skip it + if meta.err.is_some() { + return None; + } + let Some(transaction_hash) = tx .signatures .first() @@ -238,9 +243,29 @@ fn filter_by_validity( return None; }; + // Orders the account keys in line with the behavior of compiled instructions. + let account_keys = match &meta.loaded_addresses { + OptionSerializer::Some(addresses) => { + // If there are loaded addresses, we have a versioned transaction + // that may include dynamically loaded addresses (e.g. from a lookup table). + // The order of these is [static, dynamic writeable, dynamic readonly] and + // follows the iter ordering of https://docs.rs/solana-sdk/latest/solana_sdk/message/struct.AccountKeys.html. + [ + message.account_keys, + addresses.writable.clone(), + addresses.readonly.clone(), + ] + .concat() + } + OptionSerializer::None | OptionSerializer::Skip => { + // There are only static addresses in the transaction. + message.account_keys + } + }; + let instructions = instructions(message.instructions, meta); - Some((transaction_hash, message.account_keys, instructions)) + Some((transaction_hash, account_keys, instructions)) } fn filter_by_encoding( diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_block_multiple_txs_one_successful.json b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_block_multiple_txs_one_successful.json new file mode 100644 index 0000000000..d90fb4eabc --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_block_multiple_txs_one_successful.json @@ -0,0 +1,3388 @@ +{ + "blockHeight": 35744889, + "blockTime": 1734974925, + "blockhash": "DvtcDha1ZnwXbggRkP7pw3EDNortvgCWWA7Lyv5wVTcZ", + "parentSlot": 35781851, + "previousBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM", + "rewards": [ + { + "commission": null, + "lamports": 1900, + "postBalance": 8937590330, + "pubkey": "AJwEBmY7PcLNtZKhpVvmF13VgosvMDva2kH79dHaasb9", + "rewardType": "Fee" + } + ], + "transactions": [ + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8937588480, + 272411, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8937588530, + 272411, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "AJwEBmY7PcLNtZKhpVvmF13VgosvMDva2kH79dHaasb9", + "C6PeLNxv75pYFFMnWctc75tAPHvbnQk3p5SgcGPemhqg", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvnh9eEmYV25eHYJGiBBVdJ7QaJuHpWU5p1xAELYC92HNBkLkf7vwiANvYagfemtwLFqN5YQdrT1eCHaWb78mx36Efr6yN4rEdV6WTC1K3cCpTpR1XXfJUvvWS4xZ4Ueuep2jg1z4FPi8rj2cNxPoSeCwZ", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "82X78RuKVZMjZ4wwMJpjiaYVGW3q9PatQzWSsokJLCa3" + }, + "signatures": [ + "3dLqABPPT8JkeyZfub57E99rPtU4dRXvtqcdNU6iNa3iuaLHExmYpAa8isksMo5RthPkAkypaTa8Hp8CMmxG9Vec" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8994813620, + 264520, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8994813670, + 264520, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "7jtuen1N95bBNyAV5dbcx6enYT24qKqHobyHQZnyRoTD", + "Fuwdpk6BQq6hPsEWPtmvyujchKYAb6gMA7Z9rKezuoxS", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvnh9eEmYV25eHYJGiBBVdJ7QaJuHpWU5p1xAELYC92HNBkLkf7vwiANvYagfemtwLFqN5YQdrT1eCHaWb78mx36Efr6yN4rEdV6WTC1K3cCpTpR1XXfJUvvWS4xZ4Ueuep2jg1z4FPi8rj2cNxPoSeCwZ", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "82X78RuKVZMjZ4wwMJpjiaYVGW3q9PatQzWSsokJLCa3" + }, + "signatures": [ + "2o1YTyd4AWK5j23DBsYDzw6c6qqQ8JAEm1TQYEhK8BHXZaxyZsQDUfbpndoQPgvXqu21GktDqvnKSkcpW45YSvqY" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8900415217, + 272411, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8900415267, + 272411, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "2gb1u6kcg3V5MfhoycoEL6m6JWgQAmdkpJSXoUa4ogVf", + "Fo7TvsvnVzsVsntZX4bnfH9h3MffzxTGtzQyvm8sTLtP", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvnh9eEmYV25eHYJGiBBVdJ7QaJuHpWU5p1xAELYC92HNBkLkf7vwiANvYagfemtwLFqN5YQdrT1eCHaWb78mx36Efr6yN4rEdV6WTC1K3cCpTpR1XXfJUvvWS4xZ4Ueuep2jg1z4FPi8rj2cNxPoSeCwZ", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "82X78RuKVZMjZ4wwMJpjiaYVGW3q9PatQzWSsokJLCa3" + }, + "signatures": [ + "4ztQLtKoowNM3QBvSi8H7BJ6MAuJKRKCfhsV85ptcC8JM1cXqYr3qq5p4JjT1W1jcRTk6gFyPYXS5ZRQyuQbNwfD" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 24927, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr invoke [1]", + "Program log: Memo (len 66): \"0xe377a5c244b12079b9bd5baad4d3ae7e36165721b774557980fb2f2c4bb859f8\"", + "Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr consumed 24927 of 29913 compute units", + "Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr success" + ], + "postBalances": [ + 1495953, + 6722589421, + 1, + 1, + 5095104 + ], + "postTokenBalances": [], + "preBalances": [ + 51496003, + 6672589421, + 1, + 1, + 5095104 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "ByMrVM5LUgHKwPeQSWKEPnyKQVHqfxFTkQuuj1Vi9cVD", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "ComputeBudget111111111111111111111111111111", + "11111111111111111111111111111111", + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + ], + "addressTableLookups": [], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 3, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [], + "data": "KeSwJP", + "programIdIndex": 2, + "stackHeight": null + }, + { + "accounts": [ + 0, + 1 + ], + "data": "3Bxs4NRZ15a54oAf", + "programIdIndex": 3, + "stackHeight": null + }, + { + "accounts": [], + "data": "KszPnaqCF2i8y7gn5x5f9sgXMgoyBncGcNtYb8WoiD9nqQVRZWawYwMBK234hSpsQ9zxR6sb8DyByvm377GFFo5w75", + "programIdIndex": 4, + "stackHeight": null + } + ], + "recentBlockhash": "FgiA2obJyS9f5QjsbHf3YGjTusWN9Mu9aqB9zSJKEsLh" + }, + "signatures": [ + "4vhWbER158mpYWNNfKgN3EGytrt1mE8PmcHvr2eCAWJiCqZDZxBZy3vCa76FrZefxdyiVSiovDZQD9qKyxkvdZzJ" + ] + }, + "version": 0 + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 431050, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 431100, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXxd", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "Gva3FJcjwjirKpGdQdxreMWnQxd32jDMEwh2pg7NsnuN" + }, + "signatures": [ + "5Nmrbx7J6hhUu3YNYfxdHgh4BioCWAYDWMmXKjR2Ps7xKwfKcJXQVPpTqNgWJirjePb65fwEVZe7G5Pz4GTzPdGR" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25114, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 6yWRb7MA4PTputZS2GgQ1BKQHC5pu1124HqpKroLs2LG", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25114 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 519850, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 519900, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "6yWRb7MA4PTputZS2GgQ1BKQHC5pu1124HqpKroLs2LG", + "Hu94iEVDhfAhUVUYTe2ePXBVUNtPsTTAtkZKPNf2pq8Z", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "B9TNgu8ndXGYB73nF992K6wYKv6Ty7LxLBoxnE5tYYmR", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXzE", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "3veDZBERK9bD5PzCuygjYquZ85aUnU24kRuUwWRzTtkc" + }, + "signatures": [ + "4dm9BrvJsFmM614MuNFyS4EkeEZenJ79g8HMj4bDPErh4DrdN26pHFZr7txJWrbVLEoT3Wbp82Jgmdk7SeGxasde" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker DJiDXfe1jSRmgYuKjzDCbnYciZkXCRbdRR8ezDbM15iF", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 4260450, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 4260500, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "DJiDXfe1jSRmgYuKjzDCbnYciZkXCRbdRR8ezDbM15iF", + "6qQAxeLkfS1Qfsg7WbyMriGtsuPY1T6NwSiXKrqb3gqs", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "HU4JccsEKv5HKdNDe1wiiKBTacB78QEdSV4xJdrCnFZN", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyq", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "AXAiaXycACYJT9WyET1nM9zJkNWWv9h1SAz6VBX9gwdg" + }, + "signatures": [ + "4x13vs877UsCCugnPPVH9jJ3reby5RfKqQm6fgXkieVMgoLBzognyN4PJFcV6hpeksFN78Sz3eyw5bkaLK9Piv8u" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 23575, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 23575 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 4669050, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 4669100, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "3WUFeYoWxzMKwngWsB2GsKmByZfZvhp2F2Y3QUswxUfh", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "9JhY9KffbdVxED3GY4jNUrDNv2dv7JQc61ioszdkNrXS", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyV", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "HrGwSEeS1L3DKDjHDCeVycwTKrxN5n861eZY6m3G8Fkm" + }, + "signatures": [ + "4PyVsQnjZnnLC5pmgtAsSD5ZYZyp3NuL1At8J5VR5si18eLcCQFmiXKuALQ2Z5VbesKcRVs2zD9PrtzAJciGr8aS" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 29692, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker CfXww3YzNBQUEtS4ZJeEMZtAEbsym7BAKYUdPsQ1qqrJ", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 29692 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 2947200, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 2947250, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "CfXww3YzNBQUEtS4ZJeEMZtAEbsym7BAKYUdPsQ1qqrJ", + "12p6G2ShLgRbxBpCqDue2zed3UGqnLmrxHNmCrhTR1QS", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "AogE6CYJgaLGXug6D6oTuzZsKJAptaCRD2sLTAMqg1Dm", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXx9", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "745aVzBnpkgPpDCpG4F5fnpFy9WhhRHDJeezHxKHHzDr" + }, + "signatures": [ + "4feKSCpn9AFR3UfkoqHuC6HRoqGjkiWqAH5PESV2U3CXjv8obBwooMKCP8xfB9VM8WdjGDHb8BKgBZCbScr5kCjR" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25153, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25153 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 992750, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 992800, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "5YDa9bxTHEBsWfERWRxNVAe7NgjGtpRmjtGuMSXjGhdu", + "4SCCH7gkNRBXtY2j1a6rwCfsWdgDTZDap7msk72Rkefo", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXz6", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "GX81sTiYtmXrs7p99NPP74vY3Q6TLHueueQ1xXPzt4bk" + }, + "signatures": [ + "a7sKgx3MotvqiK7KsnAUfWaEHqW1oJ2mLScaRQWvEiHH146yTokGfuKUGDzG7tgZta3LVd1TMjk2yWa4r7hqndE" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 23705, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker FZDe7R2oeEqPXUZg7GdbP98yfScU5j3WM9BwDdzA3ZLx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 23705 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 2058300, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 2058350, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "FZDe7R2oeEqPXUZg7GdbP98yfScU5j3WM9BwDdzA3ZLx", + "Cy3u4YK7jv3mfqhoFTSMvxWE5wyarYi9fXWR7bEe9NAk", + "35b813JrG1f4gJ3GpBRqPkuFRiEgE5himtQHXVj8YvjY", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXvm", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "745aVzBnpkgPpDCpG4F5fnpFy9WhhRHDJeezHxKHHzDr" + }, + "signatures": [ + "3qExduzZcYxeKTSTKEY9gs5UTFeg5azfYytswpgFFVeu1BW3XVJJtyfHwHJh9ZLUyn2pHvDfGFaEyMvrtvLgpenu" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 23705, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker FZDe7R2oeEqPXUZg7GdbP98yfScU5j3WM9BwDdzA3ZLx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 23705 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 2058250, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 2058300, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "FZDe7R2oeEqPXUZg7GdbP98yfScU5j3WM9BwDdzA3ZLx", + "Cy3u4YK7jv3mfqhoFTSMvxWE5wyarYi9fXWR7bEe9NAk", + "35b813JrG1f4gJ3GpBRqPkuFRiEgE5himtQHXVj8YvjY", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXyG", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "745aVzBnpkgPpDCpG4F5fnpFy9WhhRHDJeezHxKHHzDr" + }, + "signatures": [ + "CG5epQJHrdo6WME2VRwHhdasACE7CV5kwCuEbvVk5QitMJjhhpJ5V6u1hFk1cMG7oozXFoFJWeNExnoYwPeSVmm" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25114, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 6yWRb7MA4PTputZS2GgQ1BKQHC5pu1124HqpKroLs2LG", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25114 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 519800, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 519850, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "6yWRb7MA4PTputZS2GgQ1BKQHC5pu1124HqpKroLs2LG", + "Hu94iEVDhfAhUVUYTe2ePXBVUNtPsTTAtkZKPNf2pq8Z", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "B9TNgu8ndXGYB73nF992K6wYKv6Ty7LxLBoxnE5tYYmR", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXxS", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "3veDZBERK9bD5PzCuygjYquZ85aUnU24kRuUwWRzTtkc" + }, + "signatures": [ + "3jueLij1hqvENMjToMiEUM119WPZehXCrSCBYLniRZzC1sbwzwGxYGRRB6wKtHkc82ngqbzJJba9GZ7XztCHMjbr" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 431000, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 431050, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXzT", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "Gva3FJcjwjirKpGdQdxreMWnQxd32jDMEwh2pg7NsnuN" + }, + "signatures": [ + "4JES3o1EfQFxUsEuNxYwbyvZzvhoiY6kHvoizya4J5aeuqYBXbmMiY8s76TNoEyT98KY72fe3dp2QHtL3L5zicDs" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 23575, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 23575 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 4669000, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 4669050, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "3WUFeYoWxzMKwngWsB2GsKmByZfZvhp2F2Y3QUswxUfh", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "9JhY9KffbdVxED3GY4jNUrDNv2dv7JQc61ioszdkNrXS", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXvb", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "4bXsMeojJxqqLbDsN9DnWRwoSyrorP7CQ8HAZBPxhyND" + }, + "signatures": [ + "2N3PrKS7Yd6sDTUhN471dvsHeTP8cs5RC5VKZGvRN3TnmRYVqaZR1y8Hr69o811YMvKpnWVtqSAknbeYKSXbZrcN" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25153, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25153 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 992700, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 992750, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "5YDa9bxTHEBsWfERWRxNVAe7NgjGtpRmjtGuMSXjGhdu", + "4SCCH7gkNRBXtY2j1a6rwCfsWdgDTZDap7msk72Rkefo", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXzJ", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "4bXsMeojJxqqLbDsN9DnWRwoSyrorP7CQ8HAZBPxhyND" + }, + "signatures": [ + "8f5FFcU8KgnDgwRdx6YV1FZEbz8fcjDUhp6M8TkXJbDeY434QSpM3fHa1LgTYLfvViXz9e63ePfXmmT5KVXg4Rt" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 26066, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e invoke [1]", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e consumed 13033 of 27000 compute units", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e success", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e invoke [1]", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e consumed 13033 of 13967 compute units", + "Program FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e success" + ], + "postBalances": [ + 55400350, + 233920, + 233920, + 1, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 55400400, + 233920, + 233920, + 1, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "4bEk3CLwmnRHQxUEqDtNcXabz8RSdQyMpsAKJTaM8NUc", + "4bbK5m6YSPyALDa5uE9WYMLwnqEATHDzWdYqFZJ6Ezrf", + "E4wLiGrpMi2RtH7c1WrTzCdVVEi58za1XzPygtr7Kmz1", + "ComputeBudget111111111111111111111111111111", + "FUCHhfHbuxXBWiRBfVdhmiog84sUJw11aAq3ibAUGL6e" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 2, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [], + "data": "HAaSfh", + "programIdIndex": 3, + "stackHeight": null + }, + { + "accounts": [ + 2, + 0 + ], + "data": "vaJro6ZLfqS8Tk6mcBehNesxGGBBDUfeYjo9XpzxmW6pTfUBhDh7eNT", + "programIdIndex": 4, + "stackHeight": null + }, + { + "accounts": [ + 1, + 0 + ], + "data": "svao1p5ZoTbEBpMq8HCJ7B2jKsqFvGGBTjackJ79Q8sb67xwyCid5wd", + "programIdIndex": 4, + "stackHeight": null + } + ], + "recentBlockhash": "HrGwSEeS1L3DKDjHDCeVycwTKrxN5n861eZY6m3G8Fkm" + }, + "signatures": [ + "32xkNevmZipE6UdriH8UdB7bT7LajuAXZF6Y1J7ZJL5q4DEYvZa4SRHL9c6njmmPHjiEDKsEM7CvNPkx9KXb8NaZ" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 262874, + "err": null, + "fee": 150, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [ + 0, + 4 + ], + "data": "1111JX81xkJCwniZ1Erg4Sybstk6tqT9u8gU37B5SnCv3ruqnd5SbCwNAcbMvfg2xgbbn", + "programIdIndex": 12, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 6 + ], + "data": "1111JX81xkJCwniZ1Erg4Sybstk6tqT9u8gU37B5SnCv3ruqnd5SbCwNAcbMvfg2xgbbn", + "programIdIndex": 12, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 8 + ], + "data": "3Bxs4Tm5txe73V9q", + "programIdIndex": 12, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 7 + ], + "data": "3Bxs4Tm5txe73V9q", + "programIdIndex": 12, + "stackHeight": 2 + }, + { + "accounts": [ + 3, + 0, + 10, + 0, + 2, + 1, + 9, + 5, + 11, + 18, + 13, + 16, + 12 + ], + "data": "9ZxXEgRKtxM", + "programIdIndex": 15, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 2 + ], + "data": "11114hL6PWxs4U7NCYRNXyZk7j7iV5YvbM2Y4EYYnUoc78BhWkZV7EbHKZpZnbJM3FMyFq", + "programIdIndex": 12, + "stackHeight": 3 + }, + { + "accounts": [ + 2 + ], + "data": "GC422sqZqSiuDzjorrJVQ1wDyBHerctVdibtxN47F9PXCNokjD5NBjqdUEZ7q7gULrAbayLZ33AbQKyBCSPFNnZM1u", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2 + ], + "data": "GyEZt3T6vJ45ToJP8DQByidGXsy4KJEGWVWgP1hEpCP6MRsdGJabNdMf1B9yvA6BX5x37UchRjzKErQP9GT1YjQp1G", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2 + ], + "data": "bPFz9iKJZS3rDbU6zowCjSwEjkgEPMo9i3Vmbodo9YYN1dGaEta9eWisUYepxtz2rgAZBo5ZFNFCJ6MvKF9e8V7ceoa", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2, + 3, + 2, + 3 + ], + "data": "YzZQyW1KJYUcGMonWYpqEXb51jrqpC6SG3vMmUSJpjmTHzgi5rH5QercoJFoQY2wRHDwBmhF3iD7TFuBnY1ETUnuBcVVooT7LgnFcL3XatbSFEdD9SP9HdmRrR1AbAzTR1qauF9DxCfUoHMv7g5GfXSF8H3PcTKcy4HeZAqD6GP7ApLu5bS", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 1 + ], + "data": "11113Md75Dq7V8gjbzJMw6r5QL5XX645yU1bhj7iFnRmnhAT1ske6hnTQ4LYv8avqJdZ2Z", + "programIdIndex": 12, + "stackHeight": 3 + }, + { + "accounts": [ + 1, + 2, + 3, + 9, + 3 + ], + "data": "SSqVPNo92jX", + "programIdIndex": 16, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 11, + 0, + 2, + 12, + 18 + ], + "data": "1", + "programIdIndex": 13, + "stackHeight": 3 + }, + { + "accounts": [ + 2 + ], + "data": "84eT", + "programIdIndex": 18, + "stackHeight": 4 + }, + { + "accounts": [ + 0, + 11 + ], + "data": "11112Um5QtBM9RVwhzeb2i4U2mPJ7krjBF48k17qACUVhhTNRzdmH1adRRGPABHBdaEx67", + "programIdIndex": 12, + "stackHeight": 4 + }, + { + "accounts": [ + 11 + ], + "data": "P", + "programIdIndex": 18, + "stackHeight": 4 + }, + { + "accounts": [ + 11, + 2 + ], + "data": "6U1ZUf261fPY3ULHBWy3hurmHY2X4qaWfcvvCwszYF11e", + "programIdIndex": 18, + "stackHeight": 4 + }, + { + "accounts": [ + 2, + 11, + 3 + ], + "data": "6AuM4xMCPFhR", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2, + 3 + ], + "data": "31tb", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2, + 3 + ], + "data": "C2X7q5sVBvi2E5BSotT3ogtLHb3igj3sP7xdQF1f2suStR2MUMN7QBq", + "programIdIndex": 18, + "stackHeight": 3 + }, + { + "accounts": [ + 2, + 3 + ], + "data": "5SL6KejFcJAFCech12rmhDMH11RyzCaprR64rHCLCuRW48Wy42PgxL4nfYsZSejosCQQsJve6HruQaoknGeML2GF", + "programIdIndex": 18, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program RariUNM3vz1rwxPg8UJyRAN7rSKXxgd2ncS2ddCa4ZE invoke [1]", + "Program log: Instruction: MintWithControls", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program Rari9ftBd6vFdtpn8TDLwN2ze24KKkM5MLEETNiBMNn invoke [2]", + "Program log: Instruction: Mint", + "Program log: Invoke create account 7sroipZDNYGDCrBfRorNRtKu38wFRqExv1kMTJJiwrag,BQ2TPCCzmxNMvmk5snbHGfVDL5JDzvXJBvhGc7adsY2o", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Invoke initialise metadata pointer extension", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: MetadataPointerInstruction::Initialize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 2442 of 766976 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: GroupMemberPointerInstruction::Initialize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 2505 of 762558 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: Invoke initialise mint", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: Instruction: InitializeMint2", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 4078 of 758195 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: Initialise metadata if needed", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: TokenMetadataInstruction: Initialize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 9992 of 751137 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: Invoke create account 7sroipZDNYGDCrBfRorNRtKu38wFRqExv1kMTJJiwrag,BQ2TPCCzmxNMvmk5snbHGfVDL5JDzvXJBvhGc7adsY2o", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program RariGDYwEF1jQA4kisHxBxiv1TDuBPVHNNoXFNYriFb invoke [3]", + "Program log: Instruction: InitializeMember", + "Program RariGDYwEF1jQA4kisHxBxiv1TDuBPVHNNoXFNYriFb consumed 3062 of 712812 compute units", + "Program RariGDYwEF1jQA4kisHxBxiv1TDuBPVHNNoXFNYriFb success", + "Program log: Finished", + "Program log: MINT NON-FUNGIBLE TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "Program log: 7sroipZDNYGDCrBfRorNRtKu38wFRqExv1kMTJJiwrag", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [3]", + "Program log: Create", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: GetAccountDataSize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 3038 of 670644 compute units", + "Program return: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb qgAAAAAAAAA=", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program 11111111111111111111111111111111 invoke [4]", + "Program 11111111111111111111111111111111 success", + "Program log: Initialize the associated token account", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: InitializeImmutableOwner", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1924 of 662747 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: InitializeAccount3", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5392 of 658406 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 24807 of 677448 compute units", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", + "Program log: Minting TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: Instruction: MintTo", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5568 of 638961 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: Removing mint authority", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: Instruction: SetAuthority", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 2740 of 631171 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: done", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: TokenMetadataInstruction: UpdateField", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 12096 of 621425 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: TokenMetadataInstruction: UpdateField", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 13658 of 606546 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program Rari9ftBd6vFdtpn8TDLwN2ze24KKkM5MLEETNiBMNn consumed 214421 of 805330 compute units", + "Program Rari9ftBd6vFdtpn8TDLwN2ze24KKkM5MLEETNiBMNn success", + "Program RariUNM3vz1rwxPg8UJyRAN7rSKXxgd2ncS2ddCa4ZE consumed 262874 of 850000 compute units", + "Program RariUNM3vz1rwxPg8UJyRAN7rSKXxgd2ncS2ddCa4ZE success" + ], + "postBalances": [ + 1504115, + 14144, + 48484, + 142256, + 15368, + 53924, + 15368, + 216518752, + 139608770978, + 14416, + 95200, + 20264, + 1, + 7277632, + 1, + 11152, + 11152, + 11152, + 11152 + ], + "postTokenBalances": [ + { + "accountIndex": 11, + "mint": "BQ2TPCCzmxNMvmk5snbHGfVDL5JDzvXJBvhGc7adsY2o", + "owner": "7sroipZDNYGDCrBfRorNRtKu38wFRqExv1kMTJJiwrag", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "1", + "decimals": 0, + "uiAmount": 1, + "uiAmountString": "1" + } + } + ], + "preBalances": [ + 2777893, + 0, + 0, + 142256, + 0, + 53924, + 0, + 215938752, + 139608190978, + 14416, + 95200, + 0, + 1, + 7277632, + 1, + 11152, + 11152, + 11152, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "7sroipZDNYGDCrBfRorNRtKu38wFRqExv1kMTJJiwrag", + "9toVdfGuM4qwW9uyfadz3JA9SED6kzJzJaqRfKnvZif2", + "BQ2TPCCzmxNMvmk5snbHGfVDL5JDzvXJBvhGc7adsY2o", + "23buSpWBskpcVtkQsWtcugmbfqsYqHpsDLe4tj5mmAqN", + "5oaouyNATLwQjixXpYT5sEqKaKafafQi5oEM8gcFS1p9", + "71HcxJnmvJrfyYA1gkfK5SAXecGzuWN3B3w9T6dHNa8H", + "8SUKSg5QaqkXBczx27hKisrLnoFpdTekYVyvjcBcwgcw", + "8y6yGuhdyPYEaiujsK62V6ctHcrtYu7f2LxLDXCHnb4d", + "AsSKqK7CkxFUf3KaoQzzr8ZLPm5fFguUtVE5QwGALQQn", + "DLDzmqUwGhBnRkdoUPjAYcHcx17FYjfAAk6q8qj7MPJJ", + "Dq6A6TNFWHjFKuLqWPYjFcTjhSuanpKAgWXxGzJncwtT", + "EoHBPKH6A18QECuWth1pUYafF95W5mfmwS7nAZp9LZLH", + "11111111111111111111111111111111", + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + "ComputeBudget111111111111111111111111111111", + "Rari9ftBd6vFdtpn8TDLwN2ze24KKkM5MLEETNiBMNn", + "RariGDYwEF1jQA4kisHxBxiv1TDuBPVHNNoXFNYriFb", + "RariUNM3vz1rwxPg8UJyRAN7rSKXxgd2ncS2ddCa4ZE", + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 7, + "numRequiredSignatures": 3 + }, + "instructions": [ + { + "accounts": [], + "data": "GA6zB1", + "programIdIndex": 14, + "stackHeight": null + }, + { + "accounts": [ + 3, + 10, + 0, + 4, + 6, + 2, + 1, + 9, + 5, + 8, + 11, + 7, + 18, + 13, + 16, + 12, + 15 + ], + "data": "5gMQbgxkTMLLTytDRsM8B", + "programIdIndex": 17, + "stackHeight": null + } + ], + "recentBlockhash": "GRKHwZHCpQCkdCfLB5uDfNZ1DnbhFaegxpv351WGZESk" + }, + "signatures": [ + "4YYiYWvcT7wqWQ18Jcfwf3D8mPXNVWWWQ1Qapuubqu7FFriFentR8pv6wcDKYAaEe2xNNZXWrDwB6cFXu9g6AUU2", + "4mKjD6UP9fHRaW5BAFp2fzyDFbYZBUF5n2eYfipTYTpXYdbj9o7bysHgJWbNf4HKaXpWtUw5iG6FNhcBbc87rkNS", + "637DgH3g2Giu3m4gD7ALUXGjDAiietgubWDo6yTjkvbqUtNttYTyxQjRBf8j2W8iqsX6AacGGW6j54NXE3pkGTE3" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8937588430, + 272411, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8937588480, + 272411, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "AJwEBmY7PcLNtZKhpVvmF13VgosvMDva2kH79dHaasb9", + "C6PeLNxv75pYFFMnWctc75tAPHvbnQk3p5SgcGPemhqg", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvyak29TbGXpE2Lwz5sSCzhNdG5DLVnfiBZBkMgazajyeLBZzKECbBUxETBJhpzkWTodGw6pMLoLygdjREwer3urL9cNoKzndAFuwucV6nMKUePk649zMvZXuP4WCUgk6pjuXigR8Wux32vCszFfJBfqGj", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "4QjwbwDvkGXJ3PiKpEcmbvoiVJKUnyfN3RRJY3xfRYjwkVJ4PQ6kDBiLFWnuirDnqVpSRaPP7NK5JZgxuwC7Y9hy" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430950, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 431000, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXy8", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "Gva3FJcjwjirKpGdQdxreMWnQxd32jDMEwh2pg7NsnuN" + }, + "signatures": [ + "pTYUghgJjDE9jj9TD3izbmKbsoZwyTvQnVXEa9Xumd1enH1TUq3C7tTT7UzXNjCV75V5XNSzvF1yWTZ4Tq4c2Ay" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 23575, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 23575 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 4668950, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 4669000, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "55x98feYeH7taBmGj5MnvVyALkj51ctsvrkJgUooiT2d", + "3WUFeYoWxzMKwngWsB2GsKmByZfZvhp2F2Y3QUswxUfh", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "9JhY9KffbdVxED3GY4jNUrDNv2dv7JQc61ioszdkNrXS", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXxV", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "745aVzBnpkgPpDCpG4F5fnpFy9WhhRHDJeezHxKHHzDr" + }, + "signatures": [ + "2DRJDZLuP2bxw5rLvZ4AvX6RhgEnfJ9fRaWPTbGDNEPj43kNof3g2Vp6ZqifU5zPA9FTmZQnTjHKEM8CBhkedHED" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25153, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25153 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 992650, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 992700, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "5YDa9bxTHEBsWfERWRxNVAe7NgjGtpRmjtGuMSXjGhdu", + "4SCCH7gkNRBXtY2j1a6rwCfsWdgDTZDap7msk72Rkefo", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXwN", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "745aVzBnpkgPpDCpG4F5fnpFy9WhhRHDJeezHxKHHzDr" + }, + "signatures": [ + "2tB9PPDwziUWj8m6HtPn9A3gn74rwVDSekD2Z5XEtV8DLPY8Fm9S7hZEuFWq8NGcncbb8agyDwNXLB5uoeLbtsff" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25153, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25153 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 992600, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 992650, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "5YDa9bxTHEBsWfERWRxNVAe7NgjGtpRmjtGuMSXjGhdu", + "4SCCH7gkNRBXtY2j1a6rwCfsWdgDTZDap7msk72Rkefo", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXxv", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "2d8BhKG9E8nMARkU9SkMN9N5UmS4VkPyYYZNoUcekqCX" + }, + "signatures": [ + "VHivewViQLW3Y7W37y6aLB9ZR7k4oBTDizUcL6s69Fsegz3obiz1TBATzDGQsYRn73JLKWA7so64iDe4HavbEoA" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430900, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430950, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyV", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "14tvFMnmt5uugsBUyWWvdBC6YiD6bcWbNUQc2Tr1aLc5" + }, + "signatures": [ + "4hx1wQotXicZS5LcCzoTn8b9rXoAkRNcTTh1hp7yhb22iR3RpyFDab19PhHVTWpHzqrFjYZbRxpgK6nSUT4JGAeS" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430850, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430900, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXzW", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "9PWtA56bgsBEkMS45eBbkUUzzNUYTUeML3VcGHJTK3CR" + }, + "signatures": [ + "3LWqKcfdwLgte6N7xozj4ESm79n2icUdAx1CowBMbaaHEwDFumFnYeQE1HAfqxpWCx1ALFJpaF22pUWZCDPMjUYh" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430800, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430850, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXwu", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "Gva3FJcjwjirKpGdQdxreMWnQxd32jDMEwh2pg7NsnuN" + }, + "signatures": [ + "5DG3XrVfnfTKimSYuz8mWgnqF5mBLuqAcqDZcv1Tq7TGUdmbwwoygEjHjTkbpeuJ79s3Sp6BGhBe8JzYmT6VXbd1" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430750, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430800, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXzN", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "2ASwJhrEruoZdH9BXDEvL7z25w2PX8ztHy9qDgFqhd2X" + }, + "signatures": [ + "5CevNdHzh7yAgNsEAQQqpC2UmckgtpSkRdHqHo6uW1ow5ZoMCcWCj9dFWkBV28PeDA3xicVeZ2urmpyspoQoNUta" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 9235663620, + 272411, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 9235663670, + 272411, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "8t2XBEjM7db1Fv24FhNaayPx32daDZ95L5xTETKCk2pK", + "GtvJ5ydPqRrq1oSDdFr4mKynoFFqkDDDYfkPPJnjsH53", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvyak29TbGXpE2Lwz5sSCzhNdG5DLVnfiBZBkMgazajyeLBZzKECbBUxETBJhpzkWTodGw6pMLoLygdjREwer3urL9cNoKzndAFuwucV6nMKUePk649zMvZXuP4WCUgk6pjuXigR8Wux32vCszFfJBfqGj", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "232wBqGGxFPQi22horR7YvofKdtrpc4xnX84D36VFR9QZa8FmTEMteUURgav2eogG33srJ5wKrwmQXw5EhKxNNWB" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430700, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430750, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyM", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "14tvFMnmt5uugsBUyWWvdBC6YiD6bcWbNUQc2Tr1aLc5" + }, + "signatures": [ + "cNqG9iaTpv89mcmfkMXKrY6iUDGHFbJRBMEoWuFPfPV5aXmvf9AUy6xVeot9BxMXpCoFFScQnQT4yPdrpBxBYmq" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25192, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25192 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 430650, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 430700, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "Cp6knuLPhSihkEX7RvhxVAqsjfaxZ8mBdaDSp6Fh3Pgx", + "FLKwjYChrU5BSjWCwjzvk1tdiR1bntTsQd4F4FJhZrDJ", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "FPZ9A7WnsY8q5sarCFAHJqCLuHkrAo5Jqh1nbCiwFQ5e", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyw", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "14tvFMnmt5uugsBUyWWvdBC6YiD6bcWbNUQc2Tr1aLc5" + }, + "signatures": [ + "x1x9c9YJwvMe9fKJ1etuSEQvjstXHsHH4AGFLBFAGv564ezffyeDmfPi5oHLLhHZ15pBENLVyyBGZRVY3GXu7NK" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8900415167, + 272411, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8900415217, + 272411, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "2gb1u6kcg3V5MfhoycoEL6m6JWgQAmdkpJSXoUa4ogVf", + "Fo7TvsvnVzsVsntZX4bnfH9h3MffzxTGtzQyvm8sTLtP", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvyak29TbGXpE2Lwz5sSCzhNdG5DLVnfiBZBkMgazajyeLBZzKECbBUxETBJhpzkWTodGw6pMLoLygdjREwer3urL9cNoKzndAFuwucV6nMKUePk649zMvZXuP4WCUgk6pjuXigR8Wux32vCszFfJBfqGj", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "4GvrQHF1BuoDyPvT7Vs3TLSfzcSMJscB1fz8Ro36dMncFGYRgg5nJnSpy423MBLqewdn2UNz4Jz7Ze1xfKagz9eH" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 0, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program Vote111111111111111111111111111111111111111 invoke [1]", + "Program Vote111111111111111111111111111111111111111 success" + ], + "postBalances": [ + 8994813570, + 264520, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 8994813620, + 264520, + 1 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "7jtuen1N95bBNyAV5dbcx6enYT24qKqHobyHQZnyRoTD", + "Fuwdpk6BQq6hPsEWPtmvyujchKYAb6gMA7Z9rKezuoxS", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 0 + ], + "data": "Fk63Pvyak29TbGXpE2Lwz5sSCzhNdG5DLVnfiBZBkMgazajyeLBZzKECbBUxETBJhpzkWTodGw6pMLoLygdjREwer3urL9cNoKzndAFuwucV6nMKUePk649zMvZXuP4WCUgk6pjuXigR8Wux32vCszFfJBfqGj", + "programIdIndex": 2, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "2hWDjjzuEPtuWXyxh7UJscffuDDV7wjgwDrB4LKezEMpJe3PvwZsCHH2tQaYapRaYgoCuxxMxusHsr8gUcsWaDyh" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 29692, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker CfXww3YzNBQUEtS4ZJeEMZtAEbsym7BAKYUdPsQ1qqrJ", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 29692 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 2947150, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 2947200, + 11492, + 14212, + 11492, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "CfXww3YzNBQUEtS4ZJeEMZtAEbsym7BAKYUdPsQ1qqrJ", + "12p6G2ShLgRbxBpCqDue2zed3UGqnLmrxHNmCrhTR1QS", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "AogE6CYJgaLGXug6D6oTuzZsKJAptaCRD2sLTAMqg1Dm", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 3, + 1, + 2, + 0, + 4 + ], + "data": "9Yid3B3uRXyz", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "5ieYGqrkBLQBp5QgEw1qk9TSM7bh1agZF16RT5nJQsjj" + }, + "signatures": [ + "5hwa9WgTik1y13XNZxxXViCbjRqhzgAGtALYAmuvQi934FNFNrjPAiREQiW5HnJGUDiyp9ANMeB5Sahk3NfNDRR6" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 328824, + "err": null, + "fee": 50, + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "accounts": [ + 17 + ], + "data": "8YGwT5LUTP4", + "programIdIndex": 15, + "stackHeight": 2 + }, + { + "accounts": [ + 16 + ], + "data": "AsRPEhA6Qs4gQCF7F6cofTsuBDbrBbFbaw5CQ6ceeNF4myVnsfFJtriYBe6adGrQatTrdAY7a1TzMsGRFLBUtZVoToquyyDt3x3G5D2X5g7pUa8DuEZ2AHFK4jJthUV3ytsRDUgDVnPjq2dKcWBc5ZDQKw4EGxaS5U7bofeCxX2vZfWNvCswBx1RABdTaPJJPyhxyDqiT6RfYkL2m6EGAW2dmjLpR4pHmQhC85MnCGKJRd7JR93L5h1EujCBE2gto6wdUUnBFDHGL5SWEKdcEkudNrtUGjCj4R3EnnTBMdmVKFDw8unwhdvGbXnZxxnYDJo31p2e7vVntzaH9MoJ1AGiCAj3VmDYujbJvknMz92Ehk9dihov3uoBrwiJo7X2H7kS7HkfdYS3mGKdom4ZRFAv4vKLjg2Uk9NtE54wVQcqAUCT92e696RnU8gxAc8oeSKf8gFJGHFg1Qrekk9LdaR95Bm7w36iDTB7xovZHkLQay16ZbCv3SC8YRyeqmBfMguY8GJDKXobXg6njWvFVZtVYViLMy9t7o8dZmy9swmuQFYPeRs6fddgQKYQxw", + "programIdIndex": 13, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 1 + ], + "data": "11112L11uQJhYT72kZ1iSnLC4yTJBhW25Zvv2dhge4xi6PgvdPK711MPj6dqaW9Q1Act58", + "programIdIndex": 6, + "stackHeight": 2 + }, + { + "accounts": [ + 12, + 6, + 17, + 10, + 8, + 11, + 3, + 2, + 4 + ], + "data": "7MHiQP8ahsZ7exLBXWsfg2PXgutcWwygoSu2YxVxohLeXDRFUKhVLRz9XRvPvP7wvMK68dufmtWRJYfJgGQkKVJh4UoibKpaSHDjWZd1d1GEnkLMdBPhmZcVGV7Df8LXknCM8gAfqE1gtF8Mey8WfZbuq", + "programIdIndex": 15, + "stackHeight": 2 + }, + { + "accounts": [ + 4, + 2, + 10, + 3, + 6, + 8 + ], + "data": "2", + "programIdIndex": 11, + "stackHeight": 3 + }, + { + "accounts": [ + 3 + ], + "data": "84eT", + "programIdIndex": 8, + "stackHeight": 4 + }, + { + "accounts": [ + 4, + 2 + ], + "data": "11112Um5QtBM9RVwhzeb2i4U2mPJ7krjBF48k17qACUVhhTNRzdmH1adRRGPABHBdaEx67", + "programIdIndex": 6, + "stackHeight": 4 + }, + { + "accounts": [ + 2 + ], + "data": "P", + "programIdIndex": 8, + "stackHeight": 4 + }, + { + "accounts": [ + 2, + 3 + ], + "data": "6SsgWxU3VsJsq1Mht5HyhzQLbcTLc5nrKuFyytmLcmRv9", + "programIdIndex": 8, + "stackHeight": 4 + }, + { + "accounts": [ + 3, + 2, + 3 + ], + "data": "ndTgmR9QMvT7W", + "programIdIndex": 8, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y invoke [1]", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y invoke [2]", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y consumed 4505 of 323327 compute units", + "Program return: FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y AA==", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y success", + "Program BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V invoke [2]", + "Program BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V consumed 184250 of 312286 compute units", + "Program BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y invoke [2]", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [3]", + "Program log: CreateIdempotent", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: GetAccountDataSize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 2941 of 91911 compute units", + "Program return: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb qgAAAAAAAAA=", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program 11111111111111111111111111111111 invoke [4]", + "Program 11111111111111111111111111111111 success", + "Program log: Initialize the associated token account", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: InitializeImmutableOwner", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1924 of 84111 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [4]", + "Program log: Instruction: InitializeAccount3", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5175 of 79770 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 24597 of 98798 compute units", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [3]", + "Program log: Instruction: MintToChecked", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5383 of 70773 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program log: Warp route transfer completed from origin: 1399811149, recipient: 6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB, remote_amount: 80000000", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y consumed 73912 of 119663 compute units", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y success", + "Program log: Hyperlane inbox processed message 0x95a06dba7265e86a56ad2c2f0fa9e2888cb304f0cd0d2bddbdb4915f5df16f5a", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y consumed 328824 of 361706 compute units", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y success" + ], + "postBalances": [ + 29970800, + 12580, + 20264, + 41072, + 92638960, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "postTokenBalances": [ + { + "accountIndex": 2, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "80000000", + "decimals": 9, + "uiAmount": 0.08, + "uiAmountString": "0.08" + } + } + ], + "preBalances": [ + 29983430, + 0, + 0, + 41072, + 92659224, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "G6fWKEBSrDtR8Uzx4kdVRRhrsHZ6zmGX1G2TYMmMgK6u", + "2BZdvWiiTfeeUZKAK9R8UPrV8s8HLNGbdEUtjED52ayf", + "5Py6bEKmK84Wp4Bm9fG9JnjoDazyDrEM4KRrLZLbLexR", + "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "CijxTbPs9JZxTUfo8Hmz2imxzHtKnDFD3kZP3RPy34uJ", + "GKkC5NTFvHkjkPprY4hWbtpsCAt4bhVG9zPx7N8rbSuA", + "11111111111111111111111111111111", + "ComputeBudget111111111111111111111111111111", + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + "AUiZ4KyQ25f9uRFPJ7GRaYLwFfd8TqL76xYfymWQ7H41", + "BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V", + "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", + "FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y", + "HRmKtAk47FtpiJuNvwZh66CmWkCFuXN18DN4z4b4cRzH", + "HXLdLtBLj3yn384tpZCdN3SMDdN9hDzBsvNxCND3gQCt" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 12, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [], + "data": "L5k7BD", + "programIdIndex": 7, + "stackHeight": null + }, + { + "accounts": [], + "data": "3DTZbgwsozUF", + "programIdIndex": 7, + "stackHeight": null + }, + { + "accounts": [ + 0, + 6, + 5, + 12, + 1, + 17, + 9, + 13, + 16, + 15, + 6, + 17, + 10, + 8, + 11, + 3, + 2, + 4 + ], + "data": "FTTpGG1B19Dvee6GQUXvdRAGVDD2KsGVzEYf41TUxTUZR4AtU9NZiyDkKioWpAgvXfTcn6B3JKbwvQy8n1bWorY2VvWnzwaJivpNKea2HxaSWqZNCJQMUM4CcmeWVnUfjCp8R8V5Pii6RY4MAd66MJLioPNnL9UQYyZ6eT64ncWhuoeLqC8yuESqcsxGRSRmR7cX6fASBoDZVEMxoQ9X4ct9Q2a9PWKivVkTReLZyEHHT4NTTWek13DawFDehydmDcWpLSUJKSmuFwKUisJSTfuzTsgqfSxKtp6KE2hMzVQ46PZpw5XSFqNUp4rGuTYYPcH2uF51wLKNQ8ABY6iXLAe6Lwri7C491RqKvKKM1xcUdSQNNKykNjVcq1pLZ4jKuf2idakmgfACytryMQxAeyJxxZ2d1pDmHxy2kwZTF8LW3quTnxHvaLroQJfr1vjFddfBhnnM4MRuwQnEwhmLcohPRncqcRroByhEwGugYw1XJMvyH8D8sP1MWm9SRKRxSz88QgGfGhtg8eGPVE5fZPnQY46JFUi7JyznxjBoRTjd472aCpP", + "programIdIndex": 14, + "stackHeight": null + } + ], + "recentBlockhash": "5ieYGqrkBLQBp5QgEw1qk9TSM7bh1agZF16RT5nJQsjj" + }, + "signatures": [ + "4atym7S78qpT4k9mUFWc2tu7KAHcqUxDn8fxP7RL8utooTi6frJtq9xFbM6MSSqDffTGpRSmEAMtCYisRe5m8KXb" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 25153, + "err": null, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN invoke [1]", + "Program log: Instruction: Click", + "Program log: Clicker 9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN consumed 25153 of 200000 compute units", + "Program turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN success" + ], + "postBalances": [ + 992550, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "postTokenBalances": [], + "preBalances": [ + 992600, + 11492, + 11492, + 14212, + 0, + 11152 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "9T5ntKpqFJKx32hM2sZrGtSKUzRj4iyq74LrYGsdM8Dp", + "5YDa9bxTHEBsWfERWRxNVAe7NgjGtpRmjtGuMSXjGhdu", + "4SCCH7gkNRBXtY2j1a6rwCfsWdgDTZDap7msk72Rkefo", + "9FXCusMeR26k1LkDixLF2dw1AnBX8SsB2cspSSi3BcKE", + "Sysvar1nstructions1111111111111111111111111", + "turboe9kMc3mSR8BosPkVzoHUfn5RVNzZhkrT2hdGxN" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 4, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 2, + 1, + 3, + 0, + 4 + ], + "data": "9Yid3B3uRXy9", + "programIdIndex": 5, + "stackHeight": null + } + ], + "recentBlockhash": "3veDZBERK9bD5PzCuygjYquZ85aUnU24kRuUwWRzTtkc" + }, + "signatures": [ + "3sTCx7dEzUALDq3D2zkLyftawaxQZBpHaiqtkvSxkd6QUuuuyjgLUvnrhmkMt5j2rVooqqEscJUAEaQB2GHgXgAf" + ] + }, + "version": "legacy" + }, + { + "meta": { + "computeUnitsConsumed": 38067, + "err": { + "InstructionError": [ + 1, + { + "Custom": 5 + } + ] + }, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y invoke [1]", + "Program log: Custom program error: 0x5", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y consumed 38067 of 1400000 compute units", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y failed: custom program error: 0x5" + ], + "postBalances": [ + 3105342080, + 12580, + 20264, + 41072, + 92638960, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "postTokenBalances": [ + { + "accountIndex": 2, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "80000000", + "decimals": 9, + "uiAmount": 0.08, + "uiAmountString": "0.08" + } + } + ], + "preBalances": [ + 3105342130, + 12580, + 20264, + 41072, + 92638960, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "preTokenBalances": [ + { + "accountIndex": 2, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "80000000", + "decimals": 9, + "uiAmount": 0.08, + "uiAmountString": "0.08" + } + } + ], + "rewards": [], + "status": { + "Err": { + "InstructionError": [ + 1, + { + "Custom": 5 + } + ] + } + } + }, + "transaction": { + "message": { + "accountKeys": [ + "G5FM3UKwcBJ47PwLWLLY1RQpqNtTMgnqnd6nZGcJqaBp", + "2BZdvWiiTfeeUZKAK9R8UPrV8s8HLNGbdEUtjED52ayf", + "5Py6bEKmK84Wp4Bm9fG9JnjoDazyDrEM4KRrLZLbLexR", + "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "CijxTbPs9JZxTUfo8Hmz2imxzHtKnDFD3kZP3RPy34uJ", + "GKkC5NTFvHkjkPprY4hWbtpsCAt4bhVG9zPx7N8rbSuA", + "11111111111111111111111111111111", + "ComputeBudget111111111111111111111111111111", + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + "AUiZ4KyQ25f9uRFPJ7GRaYLwFfd8TqL76xYfymWQ7H41", + "BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V", + "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", + "FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y", + "HRmKtAk47FtpiJuNvwZh66CmWkCFuXN18DN4z4b4cRzH", + "HXLdLtBLj3yn384tpZCdN3SMDdN9hDzBsvNxCND3gQCt" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 12, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [], + "data": "K1FDJ7", + "programIdIndex": 7, + "stackHeight": null + }, + { + "accounts": [ + 0, + 6, + 5, + 12, + 1, + 17, + 9, + 13, + 16, + 15, + 6, + 17, + 10, + 8, + 11, + 3, + 2, + 4 + ], + "data": "FTTpGG1B19Dvee6GQUXvdRAGVDD2KsGVzEYf41TUxTUZR4AtU9NZiyDkKioWpAgvXfTcn6B3JKbwvQy8n1bWorY2VvWnzwaJivpNKea2HxaSWqZNCJQMUM4CcmeWVnUfjCp8R8V5Pii6RY4MAd66MJLioPNnL9UQYyZ6eT64ncWhuoeLqC8yuESqcsxGRSRmR7cX6fASBoDZVEMxoQ9X4ct9Q2a9PWKivVkTReLZyEHHT4NTTWek13DawFDehydmDcWpLSUJKSmuFwKUisJSTfuzTsgqfSxKtp6KE2hMzVQ46PZpw5XSFqNUp4rGuTYYPcH2uF51wLKNQ8ABY6iXLAe6Lwri7C491RqKvKKM1xcUdSQNNKykNjVcq1pLZ4jKuf2idakmgfACytryMQxAeyJxxZ2d1pDmHxy2kwZTF8LW3quTnxHvaLroQJfr1vjFddfBhnnM4MRuwQnEwhmLcohPRncqcRroByhEwGugYw1XJMvyH8D8sP1MWm9SRKRxSz88QgGfGhtg8eGPVE5fZPnQY46JFUi7JyznxjBoRTjd472aCpP", + "programIdIndex": 14, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "5F5PUWAZcBmuGaejN4qr9BjbXCMNSY2ujQ2eUTmiftYaKEE3VoU5uowttiY73ksqpGM3R4CBB7TFKBP1j3R3EUM9" + ] + }, + "version": "legacy" + } + ] +} \ No newline at end of file diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_reverted_txn.json b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_reverted_txn.json new file mode 100644 index 0000000000..0001db5074 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/delivery_message_reverted_txn.json @@ -0,0 +1,175 @@ +{ + "blockTime": 1734974925, + "meta": { + "computeUnitsConsumed": 38067, + "err": { + "InstructionError": [ + 1, + { + "Custom": 5 + } + ] + }, + "fee": 50, + "innerInstructions": [], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y invoke [1]", + "Program log: Custom program error: 0x5", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y consumed 38067 of 1400000 compute units", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y failed: custom program error: 0x5" + ], + "postBalances": [ + 3105342080, + 12580, + 20264, + 41072, + 92638960, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "postTokenBalances": [ + { + "accountIndex": 2, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "80000000", + "decimals": 9, + "uiAmount": 0.08, + "uiAmountString": "0.08" + } + } + ], + "preBalances": [ + 3105342130, + 12580, + 20264, + 41072, + 92638960, + 11832, + 1, + 1, + 11152, + 0, + 101150754, + 7277632, + 0, + 11152, + 11152, + 11152, + 78336, + 28492 + ], + "preTokenBalances": [ + { + "accountIndex": 2, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "80000000", + "decimals": 9, + "uiAmount": 0.08, + "uiAmountString": "0.08" + } + } + ], + "rewards": [], + "status": { + "Err": { + "InstructionError": [ + 1, + { + "Custom": 5 + } + ] + } + } + }, + "slot": 35781852, + "transaction": { + "message": { + "accountKeys": [ + "G5FM3UKwcBJ47PwLWLLY1RQpqNtTMgnqnd6nZGcJqaBp", + "2BZdvWiiTfeeUZKAK9R8UPrV8s8HLNGbdEUtjED52ayf", + "5Py6bEKmK84Wp4Bm9fG9JnjoDazyDrEM4KRrLZLbLexR", + "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "CijxTbPs9JZxTUfo8Hmz2imxzHtKnDFD3kZP3RPy34uJ", + "GKkC5NTFvHkjkPprY4hWbtpsCAt4bhVG9zPx7N8rbSuA", + "11111111111111111111111111111111", + "ComputeBudget111111111111111111111111111111", + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "6jyr2GWhaTbzjscMz8nNWRuD7Zkng3adCLp8QBeoUHVB", + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + "AUiZ4KyQ25f9uRFPJ7GRaYLwFfd8TqL76xYfymWQ7H41", + "BgG35GxoaMgmiam3EJzcwivwQ2DTYGPTLfUCg7bhiH6V", + "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", + "FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y", + "HRmKtAk47FtpiJuNvwZh66CmWkCFuXN18DN4z4b4cRzH", + "HXLdLtBLj3yn384tpZCdN3SMDdN9hDzBsvNxCND3gQCt" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 12, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [], + "data": "K1FDJ7", + "programIdIndex": 7, + "stackHeight": null + }, + { + "accounts": [ + 0, + 6, + 5, + 12, + 1, + 17, + 9, + 13, + 16, + 15, + 6, + 17, + 10, + 8, + 11, + 3, + 2, + 4 + ], + "data": "FTTpGG1B19Dvee6GQUXvdRAGVDD2KsGVzEYf41TUxTUZR4AtU9NZiyDkKioWpAgvXfTcn6B3JKbwvQy8n1bWorY2VvWnzwaJivpNKea2HxaSWqZNCJQMUM4CcmeWVnUfjCp8R8V5Pii6RY4MAd66MJLioPNnL9UQYyZ6eT64ncWhuoeLqC8yuESqcsxGRSRmR7cX6fASBoDZVEMxoQ9X4ct9Q2a9PWKivVkTReLZyEHHT4NTTWek13DawFDehydmDcWpLSUJKSmuFwKUisJSTfuzTsgqfSxKtp6KE2hMzVQ46PZpw5XSFqNUp4rGuTYYPcH2uF51wLKNQ8ABY6iXLAe6Lwri7C491RqKvKKM1xcUdSQNNKykNjVcq1pLZ4jKuf2idakmgfACytryMQxAeyJxxZ2d1pDmHxy2kwZTF8LW3quTnxHvaLroQJfr1vjFddfBhnnM4MRuwQnEwhmLcohPRncqcRroByhEwGugYw1XJMvyH8D8sP1MWm9SRKRxSz88QgGfGhtg8eGPVE5fZPnQY46JFUi7JyznxjBoRTjd472aCpP", + "programIdIndex": 14, + "stackHeight": null + } + ], + "recentBlockhash": "9RgfP18ApUKiWXyVv74ZBCoWX1X8pWb463FrVQCj4oM" + }, + "signatures": [ + "5F5PUWAZcBmuGaejN4qr9BjbXCMNSY2ujQ2eUTmiftYaKEE3VoU5uowttiY73ksqpGM3R4CBB7TFKBP1j3R3EUM9" + ] + }, + "version": "legacy" +} \ No newline at end of file diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_versioned_txn.json b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_versioned_txn.json new file mode 100644 index 0000000000..0fcd409407 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_versioned_txn.json @@ -0,0 +1,565 @@ +{ + "blockTime": 1734978752, + "meta": { + "computeUnitsConsumed": 285017, + "err": null, + "fee": 50150, + "innerInstructions": [ + { + "index": 4, + "instructions": [ + { + "accounts": [ + 14 + ], + "data": "84eT", + "programIdIndex": 24, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 3 + ], + "data": "11112Um5QtBM9RVwhzeb2i4U2mPJ7krjBF48k17qACUVhhTNRzdmH1adRRGPABHBdaEx67", + "programIdIndex": 9, + "stackHeight": 2 + }, + { + "accounts": [ + 3 + ], + "data": "P", + "programIdIndex": 24, + "stackHeight": 2 + }, + { + "accounts": [ + 3, + 14 + ], + "data": "6U6TaA8c9FiMd3yMvYM3RZr5L251Z6XBhE7CZMpM6UqrA", + "programIdIndex": 24, + "stackHeight": 2 + } + ] + }, + { + "index": 5, + "instructions": [ + { + "accounts": [ + 1, + 22, + 16, + 0 + ], + "data": "iSvRA2gPzQThi", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [ + 17, + 14, + 3, + 15 + ], + "data": "iQVR3BvfMWGrC", + "programIdIndex": 24, + "stackHeight": 2 + } + ] + }, + { + "index": 7, + "instructions": [ + { + "accounts": [ + 3, + 14, + 0, + 0 + ], + "data": "tBUevJEscuw5z", + "programIdIndex": 24, + "stackHeight": 2 + }, + { + "accounts": [ + 19, + 29, + 9, + 26, + 0, + 2, + 5 + ], + "data": "2cRRtjzqbhv2aDpjzg6LXy6C7SLpDEsUatfoywKz3m1ifhrSUVpNtpkHK1dj5rchz1z3j8CtHuW5T2ezWvBJVLET7tCge9vDB2AF5EM8LPUdk1EGpKZam3tQNV1W7xMmt4iDto9DAhMQrbR6uG1Vb66vdH6kjEbHWTMbhUim4BP661Gpk1yyPTHjQUB", + "programIdIndex": 28, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 19 + ], + "data": "3Bxs3zrfFUZbEPqZ", + "programIdIndex": 9, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 5 + ], + "data": "1111614MkWaU9KqTDJLQwrzvidXGxXXwfY2mfoKeoqQBJrMaZskjDkgQ6pE2yERkoudxWv", + "programIdIndex": 9, + "stackHeight": 3 + }, + { + "accounts": [ + 9, + 0, + 20, + 2, + 6, + 21, + 31 + ], + "data": "6ZKyf8sS97C2Z34TUpXiGQBLPqCXXtBbZ2gHBxwLacXBZ6P8XPoaBUwFzxs1H", + "programIdIndex": 30, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 21 + ], + "data": "3Bxs46hkTBuBteUP", + "programIdIndex": 9, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 6 + ], + "data": "11115GPcU8wJysz1fCGMm1QuqnxK4pGnpF8XeC1S6D1r5qH2HggEcqE6oAfk4xFS7paC4J", + "programIdIndex": 9, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { + "readonly": [ + "So11111111111111111111111111111111111111112", + "SysvarRent111111111111111111111111111111111", + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "HXLdLtBLj3yn384tpZCdN3SMDdN9hDzBsvNxCND3gQCt", + "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", + "HP8WWjbxdeGhW3NRdBpWpARKgphEAQP5htG4AfbXqvRE", + "Hs7KVBU67nBnWhDPZkEFwWqrFMUfJbmY2DQ4gmCZfaZp", + "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj" + ], + "writable": [ + "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "E6AFbRkMwidQyBQ872e9kbVT2ZqybmM6dJ2Zaa6sVxJq", + "Du5B9P5uPhZ1nWubRTLrhoh6Va1kpuVXM1FpM7wJSPSu", + "FQjMvPVo9ReLZwbojjBkdjda4kSjdqoRjrARfN9qX9FJ", + "GgHsBMbgswDHEDoMvoYGhqsepjdsxCAjk2W22yEvz1Em", + "FKKDGYumoKjQjVEejff6MD1FpKuBs6SdgAobVdJdE21B", + "FvGvXJf6bd2wx8FxzsYNzd2uHaPy7JTkmuKiVvSTt7jm", + "ABb3i11z7wKoGCfeRQNQbVYWjAm7jG7HzZnDLV4RKRbK" + ] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]", + "Program log: Instruction: InitializeAccount", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 3442 of 500000 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]", + "Program log: CreateIdempotent", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]", + "Program log: Instruction: GetAccountDataSize", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 2941 of 488247 compute units", + "Program return: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb qgAAAAAAAAA=", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program log: Initialize the associated token account", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]", + "Program log: Instruction: InitializeImmutableOwner", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1924 of 480447 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]", + "Program log: Instruction: InitializeAccount3", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5175 of 476106 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 26000 of 496558 compute units", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", + "Program whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc invoke [1]", + "Program log: Instruction: SwapV2", + "Program log: fee_growth: 6962398081", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6239 of 425251 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]", + "Program log: Instruction: TransferChecked", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 8356 of 410679 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc consumed 71018 of 470558 compute units", + "Program whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]", + "Program log: Instruction: CloseAccount", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2914 of 399540 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y invoke [1]", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]", + "Program log: Instruction: BurnChecked", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 5921 of 376214 compute units", + "Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Protocol fee of 0 paid from 7xkuDw5Lxs5nnVGQTBr65sdwXBRjgmuzXC2hsEfHBhRC to FKKDGYumoKjQjVEejff6MD1FpKuBs6SdgAobVdJdE21B", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Dispatched message to 1399811149, ID 0xdf5b603df4e3e3e318f222cf6c2ea3aea44893dab86ab34d3dd88f42c87b27d0", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y consumed 81465 of 365453 compute units", + "Program return: EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y 31tgPfTj4+MY8iLPbC6jrqRIk9q4arNNPdiPQsh7J9A=", + "Program EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y success", + "Program Hs7KVBU67nBnWhDPZkEFwWqrFMUfJbmY2DQ4gmCZfaZp invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Paid IGP ABb3i11z7wKoGCfeRQNQbVYWjAm7jG7HzZnDLV4RKRbK for 244000 gas for message 0xdf5b…27d0 to 1399811149", + "Program Hs7KVBU67nBnWhDPZkEFwWqrFMUfJbmY2DQ4gmCZfaZp consumed 56083 of 280947 compute units", + "Program Hs7KVBU67nBnWhDPZkEFwWqrFMUfJbmY2DQ4gmCZfaZp success", + "Program log: Warp route transfer completed to destination: 1399811149, recipient: 0x9a83…6889, remote_amount: 17884084", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y consumed 181643 of 396626 compute units", + "Program FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y success", + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success" + ], + "postBalances": [ + 14487952, + 0, + 0, + 20264, + 687888, + 21896, + 18292, + 4187500, + 1, + 1, + 9076640, + 7277632, + 11152, + 11152, + 41072, + 53108, + 41900320071, + 19924, + 0, + 84796, + 9928, + 2660002254, + 1000000000, + 9860, + 11152, + 5095104, + 0, + 28492, + 11152, + 8704, + 11152, + 18700 + ], + "postTokenBalances": [ + { + "accountIndex": 3, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "7xkuDw5Lxs5nnVGQTBr65sdwXBRjgmuzXC2hsEfHBhRC", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "0", + "decimals": 9, + "uiAmount": null, + "uiAmountString": "0" + } + }, + { + "accountIndex": 16, + "mint": "So11111111111111111111111111111111111111112", + "owner": "E6AFbRkMwidQyBQ872e9kbVT2ZqybmM6dJ2Zaa6sVxJq", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "41900300147", + "decimals": 9, + "uiAmount": 41.900300147, + "uiAmountString": "41.900300147" + } + }, + { + "accountIndex": 17, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "E6AFbRkMwidQyBQ872e9kbVT2ZqybmM6dJ2Zaa6sVxJq", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "1100478938255", + "decimals": 9, + "uiAmount": 1100.478938255, + "uiAmountString": "1100.478938255" + } + } + ], + "preBalances": [ + 15923196, + 0, + 0, + 0, + 687888, + 0, + 0, + 4182500, + 1, + 1, + 9076640, + 7277632, + 11152, + 11152, + 41072, + 53108, + 41899325071, + 19924, + 0, + 84796, + 9928, + 2659677612, + 1000000000, + 9860, + 11152, + 5095104, + 0, + 28492, + 11152, + 8704, + 11152, + 18700 + ], + "preTokenBalances": [ + { + "accountIndex": 16, + "mint": "So11111111111111111111111111111111111111112", + "owner": "E6AFbRkMwidQyBQ872e9kbVT2ZqybmM6dJ2Zaa6sVxJq", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "41899305147", + "decimals": 9, + "uiAmount": 41.899305147, + "uiAmountString": "41.899305147" + } + }, + { + "accountIndex": 17, + "mint": "BeRUj3h7BqkbdfFU7FBNYbodgf8GCHodzKvF9aVjNNfL", + "owner": "E6AFbRkMwidQyBQ872e9kbVT2ZqybmM6dJ2Zaa6sVxJq", + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "uiTokenAmount": { + "amount": "1100496822339", + "decimals": 9, + "uiAmount": 1100.496822339, + "uiAmountString": "1100.496822339" + } + } + ], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 35792433, + "transaction": { + "message": { + "accountKeys": [ + "7xkuDw5Lxs5nnVGQTBr65sdwXBRjgmuzXC2hsEfHBhRC", + "AHE9Hs4nZF9NM1VJZHg61jkFBx5cBVaabAYAJFzPAiCk", + "2xieFW4TnPYwRyCf1aT9m6Xry4rBeCRKyPsScByyiAAG", + "GZKxYbwFzZgPti86aMjFTVtFL3pP4wUq38TfQjpGHqg5", + "AkEJURpBDvPEaQcnDum3BywUB5vLHHLoBLm3z7jmMKtG", + "9g87Di4xiYVvBE5F8Atk8xorbbVD8yKqbdHRkFu5HEgw", + "gJRnC4X1pnctZEzdo67Pwx4wfRw5MLFdTa2ostjGv9C", + "BWbHzqYBnWmU6kRg8Upi61hq3gf5u8F3hB886MBhg7wh", + "ComputeBudget111111111111111111111111111111", + "11111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", + "FJu4E1BDYKVg7aTWdwATZRUvytJZ8ZZ2gQuvPfMWAz9y" + ], + "addressTableLookups": [ + { + "accountKey": "F4omQWQnyM6Fny5CA9KY4xyyNemDrp87ZHXZDwME5M8q", + "readonlyIndexes": [ + 7, + 1, + 3, + 4, + 14, + 15, + 16, + 18, + 21, + 24 + ], + "writableIndexes": [ + 8, + 6, + 9, + 11, + 13, + 17, + 22, + 25 + ] + } + ], + "header": { + "numReadonlySignedAccounts": 1, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 3 + }, + "instructions": [ + { + "accounts": [], + "data": "EvSMNP", + "programIdIndex": 8, + "stackHeight": null + }, + { + "accounts": [], + "data": "3gJqkocMWaMm", + "programIdIndex": 8, + "stackHeight": null + }, + { + "accounts": [ + 0, + 1 + ], + "data": "111169tnWyB6yf7otj9gwMZ223az8WhRhw17kSToNJKZLqyX9pDi2Cd13sTA9NPWFfoQpx", + "programIdIndex": 9, + "stackHeight": null + }, + { + "accounts": [ + 1, + 22, + 0, + 23 + ], + "data": "2", + "programIdIndex": 10, + "stackHeight": null + }, + { + "accounts": [ + 0, + 3, + 0, + 14, + 9, + 24 + ], + "data": "2", + "programIdIndex": 11, + "stackHeight": null + }, + { + "accounts": [ + 10, + 24, + 25, + 0, + 15, + 22, + 14, + 1, + 16, + 3, + 17, + 4, + 4, + 4, + 18 + ], + "data": "4AoQRYXBdnCR4Ye4kfVqUSk92cVTu2wqdAePuVxcoJsMUVket7q7Sp2DCT1", + "programIdIndex": 12, + "stackHeight": null + }, + { + "accounts": [ + 1, + 0, + 0 + ], + "data": "A", + "programIdIndex": 10, + "stackHeight": null + }, + { + "accounts": [ + 9, + 26, + 27, + 28, + 19, + 29, + 0, + 2, + 5, + 30, + 20, + 6, + 31, + 21, + 24, + 14, + 3 + ], + "data": "RpjV6TtUSvsqoqZDmaVYK3Bu3EKPjEdZSC4i9viiVTSQ7fcSFrXZVvXuHzpQxzu8MT913h4XokFBcMrqJJrqDufh1zrzR885gUDpnk23", + "programIdIndex": 13, + "stackHeight": null + }, + { + "accounts": [ + 0, + 7 + ], + "data": "3Bxs4PckVVt51W8w", + "programIdIndex": 9, + "stackHeight": null + } + ], + "recentBlockhash": "9BcgXdeCcvL42fcsM9Gszs5Rjg4QvjyCTQZb2AAyrLaC" + }, + "signatures": [ + "Ku7gtQzEztksncHMHYkXq8PNst5rZsznrXMDmVqoQjc7SR4PsXz33qjNMckNcHriCxuhZJGZequo8moNsRP9GZK", + "3koYYZbZiHFBuNiUjFUqHGJw6hw8A8qxudK7v18TVvW9pwzER3XEcWWcMovYok83ihGxMhGxUdW6WbFcdajdWZUU", + "5e5xsKD3s4GWCHpiji2XmioRZG6SWKAYyQb5V7KoTjFueTYxKqbD8QYvWV7iLGJm2fVEZpDx9pY5cRV7Az8Vmk1w" + ] + }, + "version": 0 +} \ No newline at end of file diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs index d1097b1b08..3d666d3461 100644 --- a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs @@ -1,13 +1,16 @@ use std::fs; use std::path::PathBuf; -use solana_transaction_status::EncodedTransactionWithStatusMeta; +use hyperlane_core::{LogMeta, U256}; +use solana_transaction_status::{EncodedTransactionWithStatusMeta, UiConfirmedBlock}; use crate::log_meta_composer::{ is_interchain_payment_instruction, is_message_delivery_instruction, is_message_dispatch_instruction, search_transactions, }; -use crate::utils::decode_pubkey; +use crate::utils::{decode_h256, decode_h512, decode_pubkey}; + +use super::LogMetaComposer; #[test] pub fn test_search_dispatched_message_transaction() { @@ -29,6 +32,26 @@ pub fn test_search_dispatched_message_transaction() { assert!(!transaction_hashes.is_empty()); } +#[test] +pub fn test_search_dispatched_message_versioned_transaction() { + // given + let mailbox_program_id = decode_pubkey("EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y").unwrap(); + let dispatched_message_pda_account = + decode_pubkey("9g87Di4xiYVvBE5F8Atk8xorbbVD8yKqbdHRkFu5HEgw").unwrap(); + let transactions = transactions(&read_json("dispatch_message_versioned_txn.json")); + + // when + let transaction_hashes = search_transactions( + transactions, + &mailbox_program_id, + &dispatched_message_pda_account, + is_message_dispatch_instruction, + ); + + // then + assert!(!transaction_hashes.is_empty()); +} + #[test] pub fn test_search_delivered_message_transaction() { // given @@ -49,6 +72,27 @@ pub fn test_search_delivered_message_transaction() { assert!(!transaction_hashes.is_empty()); } +#[test] +pub fn test_search_delivered_message_reverted_transaction() { + // given + let mailbox_program_id = decode_pubkey("EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y").unwrap(); + // From the successful version of the delivery in https://eclipsescan.xyz/tx/4atym7S78qpT4k9mUFWc2tu7KAHcqUxDn8fxP7RL8utooTi6frJtq9xFbM6MSSqDffTGpRSmEAMtCYisRe5m8KXb + let delivered_message_pda_account = + decode_pubkey("2BZdvWiiTfeeUZKAK9R8UPrV8s8HLNGbdEUtjED52ayf").unwrap(); + let transactions = transactions(&read_json("delivery_message_reverted_txn.json")); + + // when + let transaction_hashes = search_transactions( + transactions, + &mailbox_program_id, + &delivered_message_pda_account, + is_message_delivery_instruction, + ); + + // then + assert!(transaction_hashes.is_empty()); +} + #[test] pub fn test_search_interchain_payment_transaction() { // given @@ -70,6 +114,48 @@ pub fn test_search_interchain_payment_transaction() { assert!(!transaction_hashes.is_empty()); } +#[test] +fn test_log_meta_block_with_multiple_txs_only_one_successful() { + // This test case uses an example of a block where delivery was attempted + // in 2 transactions, but only one was successful. + // Successful: https://eclipsescan.xyz/tx/4atym7S78qpT4k9mUFWc2tu7KAHcqUxDn8fxP7RL8utooTi6frJtq9xFbM6MSSqDffTGpRSmEAMtCYisRe5m8KXb + // Reverted: https://eclipsescan.xyz/tx/5F5PUWAZcBmuGaejN4qr9BjbXCMNSY2ujQ2eUTmiftYaKEE3VoU5uowttiY73ksqpGM3R4CBB7TFKBP1j3R3EUM9 + + // given + let mailbox_program_id = decode_pubkey("EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y").unwrap(); + let composer = LogMetaComposer::new( + mailbox_program_id, + "message delivery".to_owned(), + is_message_delivery_instruction, + ); + // From the successful version of the delivery in https://eclipsescan.xyz/tx/4atym7S78qpT4k9mUFWc2tu7KAHcqUxDn8fxP7RL8utooTi6frJtq9xFbM6MSSqDffTGpRSmEAMtCYisRe5m8KXb + let delivered_message_pda_account = + decode_pubkey("2BZdvWiiTfeeUZKAK9R8UPrV8s8HLNGbdEUtjED52ayf").unwrap(); + let block = serde_json::from_str::(&read_json( + "delivery_message_block_multiple_txs_one_successful.json", + )) + .unwrap(); + let log_index = U256::zero(); + let pda_slot = block.block_height.unwrap(); + let blockhash = decode_h256(&block.blockhash).unwrap(); + + // when + let log_meta = composer + .log_meta(block, log_index, &delivered_message_pda_account, &pda_slot) + .unwrap(); + + // then + assert_eq!(log_meta, LogMeta { + address: mailbox_program_id.to_bytes().into(), + block_number: pda_slot, + block_hash: blockhash, + // The successful transaction and its index in the block + transaction_id: decode_h512("4atym7S78qpT4k9mUFWc2tu7KAHcqUxDn8fxP7RL8utooTi6frJtq9xFbM6MSSqDffTGpRSmEAMtCYisRe5m8KXb").unwrap(), + transaction_index: 33, + log_index, + }); +} + fn read_json(path: &str) -> String { let relative = PathBuf::new().join("src/log_meta_composer/").join(path); let absolute = fs::canonicalize(relative).expect("cannot find path"); diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 66e58131aa..034f24c027 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -22,61 +22,37 @@ use hyperlane_sealevel_message_recipient_interface::{ }; use lazy_static::lazy_static; use serializable_account_meta::SimulationReturnData; -use solana_client::{rpc_client::SerializableTransaction, rpc_response::Response}; use solana_program::pubkey; use solana_sdk::{ account::Account, - bs58, clock::Slot, commitment_config::CommitmentConfig, - compute_budget::ComputeBudgetInstruction, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, - signature::Signature, signer::{keypair::Keypair, Signer as _}, - transaction::Transaction, }; -use solana_transaction_status::TransactionStatus; use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneChain, HyperlaneContract, HyperlaneDomain, - HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain, LogMeta, Mailbox, - MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, + HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, LogMeta, Mailbox, MerkleTreeHook, + ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, }; -use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; use crate::log_meta_composer::{ is_message_delivery_instruction, is_message_dispatch_instruction, LogMetaComposer, }; +use crate::tx_submitter::TransactionSubmitter; +use crate::{ + account::{search_accounts_by_discriminator, search_and_validate_account}, + priority_fee::PriorityFeeOracle, +}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; const SPL_NOOP: &str = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"; -// The max amount of compute units for a transaction. -// TODO: consider a more sane value and/or use IGP gas payments instead. -const PROCESS_COMPUTE_UNITS: u32 = 1_400_000; - -/// 0.0005 SOL, in lamports. -/// A typical tx fee without a prioritization fee is 0.000005 SOL, or -/// 5000 lamports. (Example: https://explorer.solana.com/tx/fNd3xVeBzFHeuzr8dXQxLGiHMzTeYpykSV25xWzNRaHtzzjvY9A3MzXh1ZsK2JncRHkwtuWrGEwGXVhFaUCYhtx) -/// See average priority fees here https://solanacompass.com/statistics/fees -/// to inform what to spend here. -const PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX: u64 = 500000; - -/// In micro-lamports. Multiply this by the compute units to figure out -/// the additional cost of processing a message, in addition to the mandatory -/// "base" cost of signature verification. -/// Unused at the moment, but kept for future reference. -#[allow(dead_code)] -const PROCESS_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS: u64 = - // Convert to micro-lamports - (PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX * 1_000_000) - // Divide by the max compute units - / PROCESS_COMPUTE_UNITS as u64; - // Earlier versions of collateral warp routes were deployed off a version where the mint // was requested as a writeable account for handle instruction. This is not necessary, // and generally requires a higher priority fee to be paid. @@ -96,6 +72,7 @@ lazy_static! { (pubkey!("CuQmsT4eSF4dYiiGUGYYQxJ7c58pUAD5ADE3BbFGzQKx"), pubkey!("EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm")), ]); } + /// A reference to a Mailbox contract on some Sealevel chain pub struct SealevelMailbox { pub(crate) program_id: Pubkey, @@ -103,6 +80,8 @@ pub struct SealevelMailbox { pub(crate) outbox: (Pubkey, u8), pub(crate) provider: SealevelProvider, payer: Option, + priority_fee_oracle: Box, + tx_submitter: Box, } impl SealevelMailbox { @@ -127,8 +106,12 @@ impl SealevelMailbox { program_id, inbox, outbox, - provider, payer, + priority_fee_oracle: conf.priority_fee_oracle.create_oracle(), + tx_submitter: conf + .transaction_submitter + .create_submitter(provider.rpc().url()), + provider, }) } @@ -301,122 +284,88 @@ impl SealevelMailbox { self.get_account_metas(instruction).await } - fn use_jito(&self) -> bool { - matches!( - self.domain(), - HyperlaneDomain::Known(KnownHyperlaneDomain::SolanaMainnet) - ) - } - - async fn send_and_confirm_transaction( + async fn get_process_instruction( &self, - transaction: &Transaction, - ) -> ChainResult { - if self.use_jito() { - self.send_and_confirm_transaction_with_jito(transaction) - .await - } else { - self.provider - .rpc() - .send_and_confirm_transaction(transaction) - .await - } - } + message: &HyperlaneMessage, + metadata: &[u8], + ) -> ChainResult { + let recipient: Pubkey = message.recipient.0.into(); + let mut encoded_message = vec![]; + message.write_to(&mut encoded_message).unwrap(); - /// Send a transaction to Jito and wait for it to be confirmed. - /// Logic stolen from Solana's non-blocking client. - pub async fn send_and_confirm_transaction_with_jito( - &self, - transaction: &impl SerializableTransaction, - ) -> ChainResult { - let signature = transaction.get_signature(); + let payer = self.get_payer()?; - let base58_txn = bs58::encode( - bincode::serialize(&transaction).map_err(ChainCommunicationError::from_other)?, + let (process_authority_key, _process_authority_bump) = Pubkey::try_find_program_address( + mailbox_process_authority_pda_seeds!(&recipient), + &self.program_id, ) - .into_string(); - - const SEND_RETRIES: usize = 1; - const GET_STATUS_RETRIES: usize = usize::MAX; - - 'sending: for _ in 0..SEND_RETRIES { - let jito_request_body = serde_json::json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "sendBundle", - "params": [ - [base58_txn] - ], + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for process authority", + ) + })?; + let (processed_message_account_key, _processed_message_account_bump) = + Pubkey::try_find_program_address( + mailbox_processed_message_pda_seeds!(message.id()), + &self.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for processed message account", + ) + })?; + + // Get the account metas required for the recipient.InterchainSecurityModule instruction. + let ism_getter_account_metas = self.get_ism_getter_account_metas(recipient).await?; + + // Get the recipient ISM. + let ism = self + .get_recipient_ism(recipient, ism_getter_account_metas.clone()) + .await?; + + let ixn = + hyperlane_sealevel_mailbox::instruction::Instruction::InboxProcess(InboxProcess { + metadata: metadata.to_vec(), + message: encoded_message.clone(), }); + let ixn_data = ixn + .into_instruction_data() + .map_err(ChainCommunicationError::from_other)?; - tracing::info!( - ?jito_request_body, - ?signature, - "Sending sealevel transaction to Jito as bundle" - ); + // Craft the accounts for the transaction. + let mut accounts: Vec = vec![ + AccountMeta::new_readonly(payer.pubkey(), true), + AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM).unwrap(), false), + AccountMeta::new(self.inbox.0, false), + AccountMeta::new_readonly(process_authority_key, false), + AccountMeta::new(processed_message_account_key, false), + ]; + accounts.extend(ism_getter_account_metas); + accounts.extend([ + AccountMeta::new_readonly(Pubkey::from_str(SPL_NOOP).unwrap(), false), + AccountMeta::new_readonly(ism, false), + ]); - let jito_response = reqwest::Client::new() - .post("https://mainnet.block-engine.jito.wtf:443/api/v1/bundles") - .json(&jito_request_body) - .send() - .await - .map_err(ChainCommunicationError::from_other)?; - let jito_response_text = jito_response.text().await; - - tracing::info!( - ?signature, - ?jito_response_text, - "Got Jito response for sealevel transaction bundle" - ); + // Get the account metas required for the ISM.Verify instruction. + let ism_verify_account_metas = self + .get_ism_verify_account_metas(ism, metadata.into(), encoded_message) + .await?; + accounts.extend(ism_verify_account_metas); - let recent_blockhash = if transaction.uses_durable_nonce() { - self.provider - .rpc() - .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) - .await? - } else { - *transaction.get_recent_blockhash() - }; - - for status_retry in 0..GET_STATUS_RETRIES { - let signature_statuses: Response>> = self - .provider - .rpc() - .get_signature_statuses(&[*signature]) - .await?; - let signature_status = signature_statuses.value.first().cloned().flatten(); - match signature_status { - Some(_) => return Ok(*signature), - None => { - if !self - .provider - .rpc() - .is_blockhash_valid(&recent_blockhash) - .await? - { - // Block hash is not found by some reason - break 'sending; - } else if cfg!(not(test)) - // Ignore sleep at last step. - && status_retry < GET_STATUS_RETRIES - { - // Retry twice a second - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - continue; - } - } - } - } - } + // The recipient. + accounts.extend([AccountMeta::new_readonly(recipient, false)]); - Err(ChainCommunicationError::from_other( - solana_client::rpc_request::RpcError::ForUser( - "unable to confirm transaction. \ - This can happen in situations such as transaction expiration \ - and insufficient fee-payer funds" - .to_string(), - ), - )) + // Get account metas required for the Handle instruction + let handle_account_metas = self.get_handle_account_metas(message).await?; + accounts.extend(handle_account_metas); + + let process_instruction = Instruction { + program_id: self.program_id, + data: ixn_data, + accounts, + }; + + Ok(process_instruction) } async fn get_inbox(&self) -> ChainResult> { @@ -429,6 +378,12 @@ impl SealevelMailbox { .into_inner(); Ok(inbox) } + + fn get_payer(&self) -> ChainResult<&Keypair> { + self.payer + .as_ref() + .ok_or_else(|| ChainCommunicationError::SignerUnavailable) + } } impl HyperlaneContract for SealevelMailbox { @@ -508,133 +463,42 @@ impl Mailbox for SealevelMailbox { metadata: &[u8], _tx_gas_limit: Option, ) -> ChainResult { - let recipient: Pubkey = message.recipient.0.into(); - let mut encoded_message = vec![]; - message.write_to(&mut encoded_message).unwrap(); - - let payer = self - .payer - .as_ref() - .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?; - - let mut instructions = Vec::with_capacity(3); - // Set the compute unit limit. - instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( - PROCESS_COMPUTE_UNITS, - )); - - // If we're using Jito, we need to send a tip to the Jito fee account. - // Otherwise, we need to set the compute unit price. - if self.use_jito() { - let tip: u64 = std::env::var("JITO_TIP_LAMPORTS") - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or(PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX); - - // The tip is a standalone transfer to a Jito fee account. - // See https://github.com/jito-labs/mev-protos/blob/master/json_rpc/http.md#sendbundle. - instructions.push(solana_sdk::system_instruction::transfer( - &payer.pubkey(), - // A random Jito fee account, taken from the getFeeAccount RPC response: - // https://github.com/jito-labs/mev-protos/blob/master/json_rpc/http.md#gettipaccounts - &solana_sdk::pubkey!("DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh"), - tip, - )); - } // "processed" level commitment does not guarantee finality. // roughly 5% of blocks end up on a dropped fork. // However we don't want this function to be a bottleneck and there already // is retry logic in the agents. let commitment = CommitmentConfig::processed(); - let (process_authority_key, _process_authority_bump) = Pubkey::try_find_program_address( - mailbox_process_authority_pda_seeds!(&recipient), - &self.program_id, - ) - .ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find program address for process authority", - ) - })?; - let (processed_message_account_key, _processed_message_account_bump) = - Pubkey::try_find_program_address( - mailbox_processed_message_pda_seeds!(message.id()), - &self.program_id, - ) - .ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find program address for processed message account", - ) - })?; + let process_instruction = self.get_process_instruction(message, metadata).await?; - // Get the account metas required for the recipient.InterchainSecurityModule instruction. - let ism_getter_account_metas = self.get_ism_getter_account_metas(recipient).await?; - - // Get the recipient ISM. - let ism = self - .get_recipient_ism(recipient, ism_getter_account_metas.clone()) - .await?; - - let ixn = - hyperlane_sealevel_mailbox::instruction::Instruction::InboxProcess(InboxProcess { - metadata: metadata.to_vec(), - message: encoded_message.clone(), - }); - let ixn_data = ixn - .into_instruction_data() - .map_err(ChainCommunicationError::from_other)?; - - // Craft the accounts for the transaction. - let mut accounts: Vec = vec![ - AccountMeta::new_readonly(payer.pubkey(), true), - AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM).unwrap(), false), - AccountMeta::new(self.inbox.0, false), - AccountMeta::new_readonly(process_authority_key, false), - AccountMeta::new(processed_message_account_key, false), - ]; - accounts.extend(ism_getter_account_metas); - accounts.extend([ - AccountMeta::new_readonly(Pubkey::from_str(SPL_NOOP).unwrap(), false), - AccountMeta::new_readonly(ism, false), - ]); - - // Get the account metas required for the ISM.Verify instruction. - let ism_verify_account_metas = self - .get_ism_verify_account_metas(ism, metadata.into(), encoded_message) + let tx = self + .provider + .rpc() + .build_estimated_tx_for_instruction( + process_instruction, + self.get_payer()?, + &*self.tx_submitter, + &*self.priority_fee_oracle, + ) .await?; - accounts.extend(ism_verify_account_metas); - // The recipient. - accounts.extend([AccountMeta::new_readonly(recipient, false)]); + tracing::info!(?tx, "Created sealevel transaction to process message"); - // Get account metas required for the Handle instruction - let handle_account_metas = self.get_handle_account_metas(message).await?; - accounts.extend(handle_account_metas); + let signature = self.tx_submitter.send_transaction(&tx, true).await?; - let inbox_instruction = Instruction { - program_id: self.program_id, - data: ixn_data, - accounts, - }; - instructions.push(inbox_instruction); - let recent_blockhash = self - .rpc() - .get_latest_blockhash_with_commitment(commitment) - .await?; + tracing::info!(?tx, ?signature, "Sealevel transaction sent"); - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &[payer], - recent_blockhash, - ); + let send_instant = std::time::Instant::now(); - tracing::info!(?txn, "Created sealevel transaction to process message"); + // Wait for the transaction to be confirmed. + self.rpc().wait_for_transaction_confirmation(&tx).await?; - let signature = self.send_and_confirm_transaction(&txn).await?; - - tracing::info!(?txn, ?signature, "Sealevel transaction sent"); + // We expect time_to_confirm to fluctuate depending on the commitment level when submitting the + // tx, but still use it as a proxy for tx latency to help debug. + tracing::info!(?tx, ?signature, time_to_confirm=?send_instant.elapsed(), "Sealevel transaction confirmed"); + // TODO: not sure if this actually checks if the transaction was executed / reverted? + // Confirm the transaction. let executed = self .rpc() .confirm_transaction_with_commitment(&signature, commitment) @@ -655,10 +519,30 @@ impl Mailbox for SealevelMailbox { #[instrument(err, ret, skip(self))] async fn process_estimate_costs( &self, - _message: &HyperlaneMessage, - _metadata: &[u8], + message: &HyperlaneMessage, + metadata: &[u8], ) -> ChainResult { - // TODO use correct data upon integrating IGP support + // Getting a process instruction in Sealevel is a pretty expensive operation + // that involves some view calls. Consider reusing the instruction with subsequent + // calls to `process` to avoid this cost. + let process_instruction = self.get_process_instruction(message, metadata).await?; + + // The returned costs are unused at the moment - we simply want to perform a simulation to + // determine if the message will revert or not. + let _ = self + .rpc() + .get_estimated_costs_for_instruction( + process_instruction, + self.get_payer()?, + &*self.tx_submitter, + &*self.priority_fee_oracle, + ) + .await?; + + // TODO use correct data upon integrating IGP support. + // NOTE: providing a real gas limit here will result in accurately enforcing + // gas payments. Be careful rolling this out to not impact existing contracts + // that may not be paying for super accurate gas amounts. Ok(TxCostEstimate { gas_limit: U256::zero(), gas_price: FixedPointNumber::zero(), diff --git a/rust/main/chains/hyperlane-sealevel/src/priority_fee.rs b/rust/main/chains/hyperlane-sealevel/src/priority_fee.rs new file mode 100644 index 0000000000..2df395cce6 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/priority_fee.rs @@ -0,0 +1,218 @@ +use async_trait::async_trait; +use derive_new::new; +use hyperlane_core::{ChainCommunicationError, ChainResult}; +use reqwest::Client; +use serde::Deserialize; +use solana_sdk::{bs58, transaction::Transaction}; + +use crate::{HeliusPriorityFeeLevel, HeliusPriorityFeeOracleConfig}; + +/// A trait for fetching the priority fee for a transaction. +#[async_trait] +pub trait PriorityFeeOracle: Send + Sync { + /// Fetch the priority fee in microlamports for a transaction. + async fn get_priority_fee(&self, transaction: &Transaction) -> ChainResult; +} + +/// A priority fee oracle that returns a constant fee. +#[derive(Debug, Clone, new)] +pub struct ConstantPriorityFeeOracle { + fee: u64, +} + +#[async_trait] +impl PriorityFeeOracle for ConstantPriorityFeeOracle { + async fn get_priority_fee(&self, _transaction: &Transaction) -> ChainResult { + Ok(self.fee) + } +} + +/// A priority fee oracle that fetches the fee from the Helius API. +/// https://docs.helius.dev/solana-apis/priority-fee-api +#[derive(Debug, Clone)] +pub struct HeliusPriorityFeeOracle { + client: Client, + config: HeliusPriorityFeeOracleConfig, +} + +impl HeliusPriorityFeeOracle { + pub fn new(config: HeliusPriorityFeeOracleConfig) -> Self { + Self { + client: reqwest::Client::new(), + config, + } + } + + fn get_priority_fee_estimate_options(&self) -> serde_json::Value { + // It's an odd interface, but if using the Recommended fee level, the API requires `recommended: true`, + // otherwise it requires `priorityLevel: ""`. + + let (key, value) = match &self.config.fee_level { + HeliusPriorityFeeLevel::Recommended => ("recommended", serde_json::json!(true)), + level => ("priorityLevel", serde_json::json!(level)), + }; + + serde_json::json!({ + key: value, + "transactionEncoding": "base58", + }) + } +} + +#[async_trait] +impl PriorityFeeOracle for HeliusPriorityFeeOracle { + async fn get_priority_fee(&self, transaction: &Transaction) -> ChainResult { + let base58_tx = bs58::encode( + bincode::serialize(transaction).map_err(ChainCommunicationError::from_other)?, + ) + .into_string(); + + let request_body = serde_json::json!({ + "jsonrpc": "2.0", + "id": "1", + "method": "getPriorityFeeEstimate", + "params": [ + { + "transaction": base58_tx, + "options": self.get_priority_fee_estimate_options(), + } + ], + }); + + let response = self + .client + .post(self.config.url.clone()) + .json(&request_body) + .send() + .await + .map_err(ChainCommunicationError::from_other)?; + + let response: JsonRpcResult = response + .json() + .await + .map_err(ChainCommunicationError::from_other)?; + + tracing::debug!(?response, "Fetched priority fee from Helius API"); + + let fee = response.result.priority_fee_estimate.round() as u64; + + Ok(fee) + } +} + +/// The result of a JSON-RPC request to the Helius API. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct JsonRpcResult { + #[allow(dead_code)] + jsonrpc: String, + #[allow(dead_code)] + id: String, + result: T, +} + +/// The result of a `getPriorityFeeEstimate` request to the Helius API. +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +struct GetPriorityFeeEstimateResult { + priority_fee_estimate: f64, +} + +#[cfg(test)] +mod test { + use solana_sdk::{bs58, transaction::Transaction}; + + use crate::{ + priority_fee::{HeliusPriorityFeeOracle, PriorityFeeOracle}, + HeliusPriorityFeeLevel, HeliusPriorityFeeOracleConfig, + }; + + use super::{GetPriorityFeeEstimateResult, JsonRpcResult}; + + #[tokio::test] + async fn test_helius_get_priority_fee() { + let helius_url = if let Ok(url) = std::env::var("HELIUS_URL") { + url + } else { + // Skip test if HELIUS_URL is not set + return; + }; + + let oracle = super::HeliusPriorityFeeOracle::new(super::HeliusPriorityFeeOracleConfig { + url: url::Url::parse(&helius_url).unwrap(), + fee_level: super::HeliusPriorityFeeLevel::Medium, + }); + + // Example process transaction + // https://solscan.io/tx/W9fXtRD8mPkkUmuoLi9QxSCgFuy32rCVa8kfxtPjWXWRH2D1AWzuDEGuvexWGyWhQDXnEmaADZMeYu5RVjWZyAB + let process_tx_base58 = "BPBE2dE4sPJX3nm4svEZ181qBfX9yvUp5H67uTt3aqRGtC6a77hW5vrQk9zJ3KkNuK63KoJCeqp1kkFwsbF5KL1UHf5Hrj8GXpiRxmKD8NybEZUWhjdVW9azMxJdnxxiFqH7wFQtZGkQxhx6oJz1qi5Xc64LEbPJEwSTAp5US1VCnnhWGRqJ297kvS8hWaVLuUxr4jEqYNG2LSusXZmzABBqEvRv753PBxcKiBE2moo9VKZ8n3ai6rmQGnSzsoAfwnjCx6iUdNSWqpYFHcq2xhMXJx8US5kv837KsT5tKQBbujsWUoRGGJ8vkmm7RJSYyR3DYEMa5ira9fiDwnK5qP3EgP2hrG73YYBxZ9naRrYzHG2GiEGWEUgNPHaUtK3JsbjTLiNjyZU8ERTdMxi4rBLppREJfHDWYUNgN9hTL81LYv4YoJY3UUTQphzT268f6oZoiyavngb8t3Lq8pbyc3gPiw7AcWXmn2ERDAcHvS59AaoxxcwZyn8UWUdynwCzvNbWhb97qVHSzBY1S79sxHFuqyBhbbD5YhkMhFGLjPUEDvncxE2hLt9iQCQaEQzCNRMmnZw7yJ1YxoKDKfmUTXJ6rmT4p2pz7f8x4jJwQ2pC2YxobcfHrNvD7929vXSvpomyZmaEXYAN2bqGBUe2KazpnobVCwafjKMVN4AaTJRMTXi92VKuShuKJEuZo9ZM7TScEqRZC5hLFU8SbCdASEUoQjpDzivUf1m9gQtT2ob5FPwJzcuZpqTWgixd59BRHTB1L5c4fDvtYr1QJFpJRN4DsXGryK4eTMu2oAs3imGpg1rHRLpuBTbcrchEivz7bD17bBj8VeHogfkPcehD9yaHzmYPRF47aWZ52GSFSSpc5kJRRQyghUKNPFBnycLGAbfkRYDdVzUgdrr3CNYksJCu45TChg54tMWWwrqSD3k5RPv7A6bXbAH4PzW83vzE2vGJFYpwUgNEnjuA1rVnYJHXsFdWBrqrsz3UvdTs5kUxyoxjNNKvoXSaTeXMXEt1HUdmQ3sw1dW9wRkYdHwWzksM6n7P7MLnVY6qv3BVUpJiX4K355BXhMhyozzcBQX2vvyC7J8UxPBofMrBRVtbMsXmfp3sphos1pog6wpN2MiEaJqm6KK5yQguANnQzN8mK7MREkjYXtCnczf84CrcHqpp2onQUaR4TPn8zCPVAxY4HVkCoDWTwKj8Am9M4L3a7wmF37epgKnQuypTH7dqbJPRTALe7tndrtvJCuoTFP8wPXQXxvwnBPXeLmhK9E2mpskTA33KfqvVBu4R5SFYNtGoKbvuHaDf83Lf2xx1YPUogXuEWZMx5zcaHWMmvutpfdnPe3Rb7GL4hPVKj4t9MNgiAg3QbjaR9nqYBUPT4kUpxVCJWEadDVh5pgLwnkg4DJ5ArNfgH5"; + let process_tx_bytes = bs58::decode(process_tx_base58).into_vec().unwrap(); + let transaction: Transaction = bincode::deserialize(&process_tx_bytes).unwrap(); + + oracle.get_priority_fee(&transaction).await.unwrap(); + } + + #[test] + fn test_helius_get_priority_fee_estimate_options_ser() { + let get_oracle = |fee_level| { + HeliusPriorityFeeOracle::new(HeliusPriorityFeeOracleConfig { + url: url::Url::parse("http://localhost:8080").unwrap(), + fee_level, + }) + }; + + // When the fee level is Recommended, ensure `recommended` is set to true + let oracle = get_oracle(HeliusPriorityFeeLevel::Recommended); + + let options = oracle.get_priority_fee_estimate_options(); + let expected = serde_json::json!({ + "recommended": true, + "transactionEncoding": "base58", + }); + assert_eq!(options, expected); + + // When the fee level is not Recommended, ensure `priorityLevel` is set + let oracle = get_oracle(HeliusPriorityFeeLevel::Medium); + + let options = oracle.get_priority_fee_estimate_options(); + let expected = serde_json::json!({ + "priorityLevel": "Medium", + "transactionEncoding": "base58", + }); + assert_eq!(options, expected); + + // Ensure the serialization of HeliusPriorityFeeLevel is PascalCase, + // as required by the API https://docs.helius.dev/solana-apis/priority-fee-api#helius-priority-fee-api + let serialized = serde_json::json!([ + HeliusPriorityFeeLevel::Recommended, + HeliusPriorityFeeLevel::Min, + HeliusPriorityFeeLevel::Low, + HeliusPriorityFeeLevel::Medium, + HeliusPriorityFeeLevel::High, + HeliusPriorityFeeLevel::VeryHigh, + HeliusPriorityFeeLevel::UnsafeMax, + ]); + let expected = serde_json::json!([ + "Recommended", + "Min", + "Low", + "Medium", + "High", + "VeryHigh", + "UnsafeMax" + ]); + assert_eq!(serialized, expected); + } + + #[test] + fn test_helius_get_priority_fee_estimate_deser() { + let text = r#"{"jsonrpc":"2.0","result":{"priorityFeeEstimate":1000.0},"id":"1"}"#; + let response: JsonRpcResult = + serde_json::from_str(text).unwrap(); + + let expected = GetPriorityFeeEstimateResult { + priority_fee_estimate: 1000.0, + }; + assert_eq!(response.result, expected); + } +} diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs index 59adb671f3..22b44497c8 100644 --- a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs +++ b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs @@ -2,12 +2,18 @@ use base64::Engine; use borsh::{BorshDeserialize, BorshSerialize}; use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; use solana_client::{ - nonblocking::rpc_client::RpcClient, rpc_config::RpcBlockConfig, - rpc_config::RpcProgramAccountsConfig, rpc_config::RpcTransactionConfig, rpc_response::Response, + nonblocking::rpc_client::RpcClient, + rpc_client::SerializableTransaction, + rpc_config::{ + RpcBlockConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, + RpcSimulateTransactionConfig, RpcTransactionConfig, + }, + rpc_response::{Response, RpcSimulateTransactionResult}, }; use solana_sdk::{ account::Account, commitment_config::CommitmentConfig, + compute_budget::ComputeBudgetInstruction, hash::Hash, instruction::{AccountMeta, Instruction}, message::Message, @@ -17,16 +23,27 @@ use solana_sdk::{ }; use solana_transaction_status::{ EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock, - UiReturnDataEncoding, UiTransactionEncoding, UiTransactionReturnData, + UiReturnDataEncoding, UiTransactionEncoding, }; use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; -use crate::error::HyperlaneSealevelError; +use crate::{ + error::HyperlaneSealevelError, priority_fee::PriorityFeeOracle, + tx_submitter::TransactionSubmitter, +}; + +pub struct SealevelTxCostEstimate { + compute_units: u32, + compute_unit_price_micro_lamports: u64, +} pub struct SealevelRpcClient(RpcClient); impl SealevelRpcClient { + /// The max amount of compute units for a transaction. + const MAX_COMPUTE_UNITS: u32 = 1_400_000; + pub fn new(rpc_endpoint: String) -> Self { Self(RpcClient::new_with_commitment( rpc_endpoint, @@ -199,16 +216,74 @@ impl SealevelRpcClient { .map_err(ChainCommunicationError::from_other) } - pub async fn send_and_confirm_transaction( + pub async fn send_transaction( &self, transaction: &Transaction, + skip_preflight: bool, ) -> ChainResult { self.0 - .send_and_confirm_transaction(transaction) + .send_transaction_with_config( + transaction, + RpcSendTransactionConfig { + skip_preflight, + ..Default::default() + }, + ) .await .map_err(ChainCommunicationError::from_other) } + /// Polls the RPC until the transaction is confirmed or the blockhash + /// expires. + /// Standalone logic stolen from Solana's non-blocking client, + /// decoupled from the sending of a transaction. + pub async fn wait_for_transaction_confirmation( + &self, + transaction: &impl SerializableTransaction, + ) -> ChainResult<()> { + let signature = transaction.get_signature(); + + const GET_STATUS_RETRIES: usize = usize::MAX; + + let recent_blockhash = if transaction.uses_durable_nonce() { + self.get_latest_blockhash_with_commitment(CommitmentConfig::processed()) + .await? + } else { + *transaction.get_recent_blockhash() + }; + + for status_retry in 0..GET_STATUS_RETRIES { + let signature_statuses: Response>> = + self.get_signature_statuses(&[*signature]).await?; + let signature_status = signature_statuses.value.first().cloned().flatten(); + match signature_status { + Some(_) => return Ok(()), + None => { + if !self.is_blockhash_valid(&recent_blockhash).await? { + // Block hash is not found by some reason + break; + } else if cfg!(not(test)) + // Ignore sleep at last step. + && status_retry < GET_STATUS_RETRIES + { + // Retry twice a second + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + continue; + } + } + } + } + + Err(ChainCommunicationError::from_other( + solana_client::rpc_request::RpcError::ForUser( + "unable to confirm transaction. \ + This can happen in situations such as transaction expiration \ + and insufficient fee-payer funds" + .to_string(), + ), + )) + } + /// Simulates an instruction, and attempts to deserialize it into a T. /// If no return data at all was returned, returns Ok(None). /// If some return data was returned but deserialization was unsuccessful, @@ -227,9 +302,9 @@ impl SealevelRpcClient { Some(&payer.pubkey()), &recent_blockhash, )); - let return_data = self.simulate_transaction(&transaction).await?; + let simulation = self.simulate_transaction(&transaction).await?; - if let Some(return_data) = return_data { + if let Some(return_data) = simulation.return_data { let bytes = match return_data.data.1 { UiReturnDataEncoding::Base64 => base64::engine::general_purpose::STANDARD .decode(return_data.data.0) @@ -245,19 +320,176 @@ impl SealevelRpcClient { Ok(None) } - async fn simulate_transaction( + pub async fn simulate_transaction( &self, transaction: &Transaction, - ) -> ChainResult> { - let return_data = self + ) -> ChainResult { + let result = self .0 - .simulate_transaction(transaction) + .simulate_transaction_with_config( + transaction, + RpcSimulateTransactionConfig { + sig_verify: false, + replace_recent_blockhash: true, + ..Default::default() + }, + ) .await .map_err(ChainCommunicationError::from_other)? - .value - .return_data; + .value; + + Ok(result) + } + + /// Gets the estimated costs for a given instruction. + pub async fn get_estimated_costs_for_instruction( + &self, + instruction: Instruction, + payer: &Keypair, + tx_submitter: &dyn TransactionSubmitter, + priority_fee_oracle: &dyn PriorityFeeOracle, + ) -> ChainResult { + // Build a transaction that sets the max compute units and a dummy compute unit price. + // This is used for simulation to get the actual compute unit limit. We set dummy values + // for the compute unit limit and price because we want to include the instructions that + // set these in the cost estimate. + let simulation_tx = self + .create_transaction_for_instruction( + Self::MAX_COMPUTE_UNITS, + 0, + instruction.clone(), + payer, + tx_submitter, + false, + ) + .await?; + + let simulation_result = self.simulate_transaction(&simulation_tx).await?; + + // If there was an error in the simulation result, return an error. + if simulation_result.err.is_some() { + tracing::error!(?simulation_result, "Got simulation result for transaction"); + return Err(ChainCommunicationError::from_other_str( + format!("Error in simulation result: {:?}", simulation_result.err).as_str(), + )); + } else { + tracing::debug!(?simulation_result, "Got simulation result for transaction"); + } + + // Get the compute units used in the simulation result, requiring + // that it is greater than 0. + let simulation_compute_units: u32 = simulation_result + .units_consumed + .unwrap_or_default() + .try_into() + .map_err(ChainCommunicationError::from_other)?; + if simulation_compute_units == 0 { + return Err(ChainCommunicationError::from_other_str( + "Empty or zero compute units returned in simulation result", + )); + } + + // Bump the compute units by 10% to ensure we have enough, but cap it at the max. + let simulation_compute_units = + Self::MAX_COMPUTE_UNITS.min((simulation_compute_units * 11) / 10); + + let priority_fee = priority_fee_oracle.get_priority_fee(&simulation_tx).await?; + + Ok(SealevelTxCostEstimate { + compute_units: simulation_compute_units, + compute_unit_price_micro_lamports: priority_fee, + }) + } + + /// Builds a transaction with estimated costs for a given instruction. + pub async fn build_estimated_tx_for_instruction( + &self, + instruction: Instruction, + payer: &Keypair, + tx_submitter: &dyn TransactionSubmitter, + priority_fee_oracle: &dyn PriorityFeeOracle, + ) -> ChainResult { + // Get the estimated costs for the instruction. + let SealevelTxCostEstimate { + compute_units, + compute_unit_price_micro_lamports, + } = self + .get_estimated_costs_for_instruction( + instruction.clone(), + payer, + tx_submitter, + priority_fee_oracle, + ) + .await?; + + tracing::info!( + ?compute_units, + ?compute_unit_price_micro_lamports, + "Got compute units and compute unit price / priority fee for transaction" + ); + + // Build the final transaction with the correct compute unit limit and price. + let tx = self + .create_transaction_for_instruction( + compute_units, + compute_unit_price_micro_lamports, + instruction, + payer, + tx_submitter, + true, + ) + .await?; + + Ok(tx) + } + + /// Creates a transaction for a given instruction, compute unit limit, and compute unit price. + /// If `sign` is true, the transaction will be signed. + pub async fn create_transaction_for_instruction( + &self, + compute_unit_limit: u32, + compute_unit_price_micro_lamports: u64, + instruction: Instruction, + payer: &Keypair, + tx_submitter: &dyn TransactionSubmitter, + sign: bool, + ) -> ChainResult { + let instructions = vec![ + // Set the compute unit limit. + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + // Set the priority fee / tip + tx_submitter.get_priority_fee_instruction( + compute_unit_price_micro_lamports, + compute_unit_limit.into(), + &payer.pubkey(), + ), + instruction, + ]; + + let tx = if sign { + // Getting the finalized blockhash eliminates the chance the blockhash + // gets reorged out, causing the tx to be invalid. The tradeoff is this + // will cause the tx to expire in about 47 seconds (instead of the typical 60). + let recent_blockhash = self + .get_latest_blockhash_with_commitment(CommitmentConfig::finalized()) + .await + .map_err(ChainCommunicationError::from_other)?; + + Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ) + } else { + Transaction::new_unsigned(Message::new(&instructions, Some(&payer.pubkey()))) + }; + + Ok(tx) + } - Ok(return_data) + pub fn url(&self) -> String { + self.0.url() } } diff --git a/rust/main/chains/hyperlane-sealevel/src/trait_builder.rs b/rust/main/chains/hyperlane-sealevel/src/trait_builder.rs index e0b7c5cb37..13cc0b90a4 100644 --- a/rust/main/chains/hyperlane-sealevel/src/trait_builder.rs +++ b/rust/main/chains/hyperlane-sealevel/src/trait_builder.rs @@ -1,6 +1,12 @@ use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, NativeToken}; +use serde::Serialize; use url::Url; +use crate::{ + priority_fee::{ConstantPriorityFeeOracle, HeliusPriorityFeeOracle, PriorityFeeOracle}, + tx_submitter::{JitoTransactionSubmitter, RpcTransactionSubmitter, TransactionSubmitter}, +}; + /// Sealevel connection configuration #[derive(Debug, Clone)] pub struct ConnectionConf { @@ -10,6 +16,10 @@ pub struct ConnectionConf { pub operation_batch: OperationBatchConfig, /// Native token and its denomination pub native_token: NativeToken, + /// Priority fee oracle configuration + pub priority_fee_oracle: PriorityFeeOracleConfig, + /// Transaction submitter configuration + pub transaction_submitter: TransactionSubmitterConfig, } /// An error type when parsing a connection configuration. @@ -23,6 +33,106 @@ pub enum ConnectionConfError { InvalidConnectionUrl(String, url::ParseError), } +/// Configuration to of how the priority fee should be determined +#[derive(Debug, Clone)] +pub enum PriorityFeeOracleConfig { + /// A constant value, in micro lamports + Constant(u64), + /// A Helius priority fee oracle + Helius(HeliusPriorityFeeOracleConfig), +} + +impl Default for PriorityFeeOracleConfig { + fn default() -> Self { + PriorityFeeOracleConfig::Constant(0) + } +} + +impl PriorityFeeOracleConfig { + /// Create a new priority fee oracle from the configuration + pub fn create_oracle(&self) -> Box { + match self { + PriorityFeeOracleConfig::Constant(fee) => { + Box::new(ConstantPriorityFeeOracle::new(*fee)) + } + PriorityFeeOracleConfig::Helius(config) => { + Box::new(HeliusPriorityFeeOracle::new(config.clone())) + } + } + } +} + +/// Configuration for the Helius priority fee oracle +#[derive(Debug, Clone)] +pub struct HeliusPriorityFeeOracleConfig { + /// The Helius URL to use + pub url: Url, + /// The fee level to use + pub fee_level: HeliusPriorityFeeLevel, +} + +/// The priority fee level to use +#[derive(Debug, Clone, Serialize, Default)] +pub enum HeliusPriorityFeeLevel { + /// 50th percentile, but a floor of 10k microlamports. + /// The floor results in a staked Helius connection being used. (https://docs.helius.dev/guides/sending-transactions-on-solana#staked-connections) + #[default] + Recommended, + /// 0th percentile + Min, + /// 10th percentile + Low, + /// 50th percentile + Medium, + /// 75th percentile + High, + /// 90th percentile + VeryHigh, + /// 100th percentile + UnsafeMax, +} + +/// Configuration for the transaction submitter +#[derive(Debug, Clone)] +pub enum TransactionSubmitterConfig { + /// Use the RPC transaction submitter + Rpc { + /// The URL to use. If not provided, a default RPC URL will be used + url: Option, + }, + /// Use the Jito transaction submitter + Jito { + /// The URL to use. If not provided, a default Jito URL will be used + url: Option, + }, +} + +impl Default for TransactionSubmitterConfig { + fn default() -> Self { + TransactionSubmitterConfig::Rpc { url: None } + } +} + +impl TransactionSubmitterConfig { + /// Create a new transaction submitter from the configuration + pub fn create_submitter(&self, default_rpc_url: String) -> Box { + match self { + TransactionSubmitterConfig::Rpc { url } => Box::new(RpcTransactionSubmitter::new( + url.clone().unwrap_or(default_rpc_url), + )), + TransactionSubmitterConfig::Jito { url } => { + // Default to a bundle-only URL (i.e. revert protected) + Box::new(JitoTransactionSubmitter::new(url.clone().unwrap_or_else( + || { + "https://mainnet.block-engine.jito.wtf/api/v1/transactions?bundleOnly=true" + .to_string() + }, + ))) + } + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] struct SealevelNewConnectionError(#[from] anyhow::Error); diff --git a/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs b/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs new file mode 100644 index 0000000000..c7468d0f24 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs @@ -0,0 +1,115 @@ +use async_trait::async_trait; +use hyperlane_core::ChainResult; +use solana_sdk::{ + compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, + signature::Signature, transaction::Transaction, +}; + +use crate::SealevelRpcClient; + +/// A trait for submitting transactions to the chain. +#[async_trait] +pub trait TransactionSubmitter: Send + Sync { + /// Get the instruction to set the compute unit price. + fn get_priority_fee_instruction( + &self, + compute_unit_price_micro_lamports: u64, + compute_units: u64, + payer: &Pubkey, + ) -> Instruction; + + /// Send a transaction to the chain. + async fn send_transaction( + &self, + transaction: &Transaction, + skip_preflight: bool, + ) -> ChainResult; +} + +/// A transaction submitter that uses the vanilla RPC to submit transactions. +#[derive(Debug)] +pub struct RpcTransactionSubmitter { + rpc_client: SealevelRpcClient, +} + +impl RpcTransactionSubmitter { + pub fn new(url: String) -> Self { + Self { + rpc_client: SealevelRpcClient::new(url), + } + } +} + +#[async_trait] +impl TransactionSubmitter for RpcTransactionSubmitter { + fn get_priority_fee_instruction( + &self, + compute_unit_price_micro_lamports: u64, + _compute_units: u64, + _payer: &Pubkey, + ) -> Instruction { + ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price_micro_lamports) + } + + async fn send_transaction( + &self, + transaction: &Transaction, + skip_preflight: bool, + ) -> ChainResult { + self.rpc_client + .send_transaction(transaction, skip_preflight) + .await + } +} + +/// A transaction submitter that uses the Jito API to submit transactions. +#[derive(Debug)] +pub struct JitoTransactionSubmitter { + rpc_client: SealevelRpcClient, +} + +impl JitoTransactionSubmitter { + /// The minimum tip to include in a transaction. + /// From https://docs.jito.wtf/lowlatencytxnsend/#sendtransaction + const MINIMUM_TIP_LAMPORTS: u64 = 1000; + + pub fn new(url: String) -> Self { + Self { + rpc_client: SealevelRpcClient::new(url), + } + } +} + +#[async_trait] +impl TransactionSubmitter for JitoTransactionSubmitter { + fn get_priority_fee_instruction( + &self, + compute_unit_price_micro_lamports: u64, + compute_units: u64, + payer: &Pubkey, + ) -> Instruction { + // Divide by 1_000_000 to convert from microlamports to lamports. + let tip_lamports = (compute_units * compute_unit_price_micro_lamports) / 1_000_000; + let tip_lamports = tip_lamports.max(Self::MINIMUM_TIP_LAMPORTS); + + // The tip is a standalone transfer to a Jito fee account. + // See https://github.com/jito-labs/mev-protos/blob/master/json_rpc/http.md#sendbundle. + solana_sdk::system_instruction::transfer( + payer, + // A random Jito fee account, taken from the getFeeAccount RPC response: + // https://github.com/jito-labs/mev-protos/blob/master/json_rpc/http.md#gettipaccounts + &solana_sdk::pubkey!("DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh"), + tip_lamports, + ) + } + + async fn send_transaction( + &self, + transaction: &Transaction, + skip_preflight: bool, + ) -> ChainResult { + self.rpc_client + .send_transaction(transaction, skip_preflight) + .await + } +} diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index 05599c97e3..8f81beeec4 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -34,7 +34,7 @@ "interchainAccountIsm": "0xd766e7C7517f2d0D92754b2fe4aE7AdEf7bDEC3e", "interchainAccountRouter": "0x25C87e735021F72d8728438C2130b02E3141f2cb", "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0x88BD2166Ed70a1Cae544037e16582e38bd74C6e6", + "interchainSecurityModule": "0xd3fA56EDc496f986df5D2464E4C2c6027F8b4cEf", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", @@ -100,7 +100,7 @@ "interchainAccountIsm": "0x2A7574358Ec53522CE2452887661AB4c86F7d400", "interchainAccountRouter": "0x91874Dbed74925dFe6059B90385EEb90DdE0B2E6", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0xd53Ea5FD4C735cabc468CC676E76d2A97ef03eF3", + "interchainSecurityModule": "0xe99C998dd9823B9b713A100fB885dd192C0d86DA", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "name": "arbitrum", @@ -172,7 +172,7 @@ "interchainAccountIsm": "0x27a3233c05C1Df7c163123301D14bE9349E3Cb48", "interchainAccountRouter": "0xa82a0227e6d6db53AF4B264A852bfF91C6504a51", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0x5958A0062297AE89276E6b28D31899310B4CB873", + "interchainSecurityModule": "0xFf2139af114d1D43C7348CA7471e08c7B31213A3", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "name": "avalanche", @@ -245,7 +245,7 @@ "interchainAccountIsm": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8", "interchainAccountRouter": "0x4767D22117bBeeb295413000B620B93FD8522d53", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0x67a2F6B3AAf57A36f78e230ef92B3C3781bEb42C", + "interchainSecurityModule": "0x22c3D036C518A3953c57Ec3101b404a78158b7e4", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", "name": "base", @@ -316,7 +316,7 @@ "interchainAccountIsm": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", "interchainAccountRouter": "0x2f4Eb04189e11Af642237Da62d163Ab714614498", "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0x3FB5E5eCC49fbe363573622C8bE677984d25AE21", + "interchainSecurityModule": "0xff226D43F30B95Fac99D225E6aD36f308ff03d7A", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "name": "blast", @@ -384,7 +384,7 @@ "interchainAccountIsm": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4", "interchainAccountRouter": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "interchainGasPaymaster": "0x62B7592C1B6D1E43f4630B8e37f4377097840C05", - "interchainSecurityModule": "0xB50d23647410d02Ff0C3F1C763E501EDAbb75783", + "interchainSecurityModule": "0xD94FA19BaC0A4f8Aa14D4ccB4B7A7Efb777F0A1e", "mailbox": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "merkleTreeHook": "0x781bE492F1232E66990d83a9D3AC3Ec26f56DAfB", "name": "bob", @@ -450,7 +450,7 @@ "interchainAccountIsm": "0x9e22945bE593946618383B108CC5bce09eBA4C26", "interchainAccountRouter": "0x32A07c1B7a7fe8D4A0e44B0181873aB9d64C16c1", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0x41B0dDBa97657551325f4579E93D756701d46fD6", + "interchainSecurityModule": "0x8ca30D8d1406B820b411dD1799fAa2A5E15a9D93", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "name": "bsc", @@ -531,7 +531,7 @@ "interchainAccountIsm": "0xB732c83aeE29596E3163Da2260710eAB67Bc0B29", "interchainAccountRouter": "0x27a6cAe33378bB6A6663b382070427A01fc9cB37", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0x89ee5969142a040653912D500d561A9F6b03Ad72", + "interchainSecurityModule": "0x0df8056AB4fd95D7c8f848BcF95d63b76F6bFA04", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "name": "celo", @@ -596,7 +596,7 @@ "interchainAccountIsm": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "interchainAccountRouter": "0xEF9A332Ec1fD233Bf9344A58be56ff9E104B4f60", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0x3dE3032C39Df41CC9b986584A69f7839781Dd16a", + "interchainSecurityModule": "0xB8508EecB14dd8d3762782eefC1ead0E92D89Ea5", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "cheesechain", @@ -659,7 +659,7 @@ "from": 4842212 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xD9267b8C7D7f23D257717BC45E401b07Cd635DA2", + "interchainSecurityModule": "0x18250E686438602607205154288f160065d5F600", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "cyber", @@ -726,7 +726,7 @@ "from": 23783929 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x1811eabA3aaF0CCc197BE558D700C17C9F57756f", + "interchainSecurityModule": "0x7fc28AE86f109B1F2f2aeCb9aDFaFF10A5440E02", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "degenchain", @@ -785,7 +785,7 @@ "index": { "from": 1, "mode": "sequence", - "chunk": 100 + "chunk": 20 }, "interchainGasPaymaster": "ABb3i11z7wKoGCfeRQNQbVYWjAm7jG7HzZnDLV4RKRbK", "mailbox": "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", @@ -839,7 +839,7 @@ "interchainAccountIsm": "0xCeafc098e5c3c7768b9229Be2FEC275862A81Abd", "interchainAccountRouter": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5", "interchainGasPaymaster": "0xB30EAB08aa87138D57168D0e236850A530f49921", - "interchainSecurityModule": "0x6c4C65E1559EEB81B1041810795405c182b021F2", + "interchainSecurityModule": "0x99d4146DC4910e3881b63C3471198F3Cb6aBAc52", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xC831271c1fB212012811a91Dd43e5926C1020563", "name": "endurance", @@ -910,7 +910,7 @@ "interchainAccountIsm": "0x292C614ED53DaaDBf971521bc2C652d1ca51cB47", "interchainAccountRouter": "0x5E532F7B610618eE73C2B462978e94CB1F7995Ce", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0xdcB6cB7477d19fB13C7168061FE034FE4BA255E3", + "interchainSecurityModule": "0x8C3c9e95091A504b28902dF319F4cE8Bd77D2f59", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "name": "ethereum", @@ -982,7 +982,7 @@ "interchainAccountIsm": "0x7C012DCA02C42cfA3Fd7Da3B0ED7234B52AE68eF", "interchainAccountRouter": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", "interchainGasPaymaster": "0x2Fca7f6eC3d4A0408900f2BB30004d4616eE985E", - "interchainSecurityModule": "0xC15c21430Dfa1e0184FE57BdD6bf31d9607D6705", + "interchainSecurityModule": "0xd0C1FC10c922434452DAc6DA518Fe91582CF2407", "mailbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", "merkleTreeHook": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "name": "fraxtal", @@ -1050,7 +1050,7 @@ "interchainAccountIsm": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", "interchainAccountRouter": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", "interchainGasPaymaster": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainSecurityModule": "0xd097D848a044F6F044ae63f6B8d35B70A7334997", + "interchainSecurityModule": "0xE62f55C6760CE12688Ae4C4FDBf2Cb7f08C468A6", "mailbox": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696", "merkleTreeHook": "0xfBc08389224d23b79cb21cDc16c5d42F0ad0F57f", "name": "fusemainnet", @@ -1124,7 +1124,7 @@ "interchainAccountIsm": "0x07E2062A1bC66a2C1d05cb5C3870a4AF86e0056E", "interchainAccountRouter": "0xBE70Ab882D1F7E37e04a70CDd9Ec23b37a234064", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0x4e3D805431E5658Ba753D04BBbD50D9930CaE38F", + "interchainSecurityModule": "0x93BC454D1E8049a286EC84E75e628AB23e5E61f8", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "name": "gnosis", @@ -1195,7 +1195,7 @@ "interchainAccountIsm": "0x708E002637792FDC031E6B62f23DD60014AC976a", "interchainAccountRouter": "0xfB8cea1c7F45608Da30655b50bbF355D123A4358", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x65b590874726F780e11Aee7E68932c196ac5Cfef", + "interchainSecurityModule": "0xe6a1cA2c655FD86E5bD0307b1577f11e031E4691", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", "name": "inevm", @@ -1323,7 +1323,7 @@ "from": 14616307 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xF2270f4b8a3274924c3e048F25c92fB7A5B091D4", + "interchainSecurityModule": "0xc11B492a516F839F3f05D0CE8f79E51Bf65a66E9", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "kroma", @@ -1396,7 +1396,7 @@ "interchainAccountIsm": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893", "interchainAccountRouter": "0xD59dA396F162Ed93a41252Cebb8d5DD4F093238C", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0x1a669B1f14C6C62B2d56f1eBb5C016904E17F831", + "interchainSecurityModule": "0x73171e4CAAe12ae90EFA6f5D33818021b2C8F80F", "mailbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", "merkleTreeHook": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7", "name": "linea", @@ -1467,7 +1467,7 @@ "from": 4195553 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x0c3b196dbA07dfaCcb5Ea4D2809712A2114EC37b", + "interchainSecurityModule": "0x629c7Bf8586256624f5D925360F4382defa04eBf", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "lisk", @@ -1531,7 +1531,7 @@ "from": 3088760 }, "interchainGasPaymaster": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", - "interchainSecurityModule": "0x1Df3B3fc76F1E76Ce2D87a921F4436D888E79038", + "interchainSecurityModule": "0x6f3e2043611307bE33016a9aaDf1943cCd10d7db", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "name": "lukso", @@ -1605,7 +1605,7 @@ "interchainAccountIsm": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", "interchainAccountRouter": "0x693A4cE39d99e46B04cb562329e3F0141cA17331", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0x22BdaC15c02353D331C5787858117F1aC1183831", + "interchainSecurityModule": "0x809253E828b9F94781Ee58089bA696883D05dDca", "isTestnet": false, "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", @@ -1675,7 +1675,7 @@ "interchainAccountIsm": "0xe039DA3A0071BEd087A12660D7b03cf669c7776E", "interchainAccountRouter": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0x3bb99404072f895B0F10bB27855Ede36E15c43E2", + "interchainSecurityModule": "0xA9dfB22e46133f23bB635818aeB04Ae31645D2a8", "mailbox": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", "merkleTreeHook": "0x5332D1AC0A626D265298c14ff681c0A8D28dB86d", "name": "mantle", @@ -1737,7 +1737,7 @@ "from": 13523607 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x1f2c988EE1b80434d89B52A16585595A558fAa48", + "interchainSecurityModule": "0x8Dc8e217071F55C772Ffa21CCCCf3B70FCec40c7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "merlin", @@ -1804,7 +1804,7 @@ "from": 17966274 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x90b5E844395e3539CE3Ab008C50E1D1c9648F632", + "interchainSecurityModule": "0x67F16B702A2b8AC3d8fc1ffAEfBAB7Fe6fe762AD", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "metis", @@ -1869,7 +1869,7 @@ "from": 3752032 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x26918172f13F5E481564fb3Ed14b2cA4Dd4b8AC7", + "interchainSecurityModule": "0xFE82E2d4278124122e9771D7A525846e506BbAD3", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "mint", @@ -1936,7 +1936,7 @@ "interchainAccountIsm": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", "interchainAccountRouter": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x2151dBB20aaa63E644276bb59A024a81f4b9BC9A", + "interchainSecurityModule": "0xA54Cca261C76B09663Ae4bCe042d644e6F822e2E", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "mode", @@ -2004,7 +2004,7 @@ "interchainAccountIsm": "0x79b3730CE3685f65802aF1771319992bA960EB9D", "interchainAccountRouter": "0xc4482f66191754a8629D35289043C4EB0285F10E", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "interchainSecurityModule": "0x194A56Bf67bF4821290A0F3C56573eCed842d8D2", + "interchainSecurityModule": "0xe12cd1ddf6AA5f71FBee45CE9A819a0B3B2fb67F", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "name": "moonbeam", @@ -2146,7 +2146,7 @@ "interchainAccountIsm": "0x2c46BF14641d00549ECa4779BF5CBf91602C1DEd", "interchainAccountRouter": "0x03D6cC17d45E9EA27ED757A8214d1F07F7D901aD", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "interchainSecurityModule": "0x8A04d9237BE52200F7363cB82BF099990d4390b1", + "interchainSecurityModule": "0x10b4d96f8D4F095f0B708195B502FDeC165b739b", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "name": "optimism", @@ -2281,7 +2281,7 @@ "interchainAccountIsm": "0xBAC4529cdfE7CCe9E858BF706e41F8Ed096C1BAd", "interchainAccountRouter": "0xF163949AD9F88977ebF649D0461398Ca752E64B9", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0x7dFd4006e9AB433d3f999830F611B2188631B33f", + "interchainSecurityModule": "0x47E840c5537547E0Fff9aB9cCd05eDe70A6D9139", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "name": "polygon", @@ -2318,11 +2318,7 @@ "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", "staticMerkleRootWeightedMultisigIsmFactory": "0x07CE1B0cFfa436AE2fb7Fbd7318648774FdA53f9", "staticMessageIdWeightedMultisigIsmFactory": "0x9e22945bE593946618383B108CC5bce09eBA4C26", - "technicalStack": "other", - "transactionOverrides": { - "maxFeePerGas": 800000000000, - "maxPriorityFeePerGas": 50000000000 - } + "technicalStack": "other" }, "polygonzkevm": { "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", @@ -2358,7 +2354,7 @@ "interchainAccountIsm": "0xc1198e241DAe48BF5AEDE5DCE49Fe4A6064cF7a7", "interchainAccountRouter": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0x3e1D7F5c1208A0eb48bc30717F9Ca2614257bFF5", + "interchainSecurityModule": "0x008cF37Cf63dE3179A9a93F558537daB1A73a46C", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "polygonzkevm", @@ -2426,7 +2422,7 @@ "from": 32018468 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xC453a7f24540B90aB43681A5Bc07c9E03acC1a48", + "interchainSecurityModule": "0x56c8e2Ca77EA631D049e9AdbCC81902c4A420AF8", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "proofofplay", @@ -2490,7 +2486,7 @@ "from": 363159 }, "interchainGasPaymaster": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696", - "interchainSecurityModule": "0xc43577973b012058Fb96dbF0A4b3246019b54016", + "interchainSecurityModule": "0x16a780d2f0d39B0e0e7B3d05a7E9d0BA772D2e2D", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x55E4F0bc6b7Bb493D50839A8592e7ad8d5e93cf7", "name": "real", @@ -2557,7 +2553,7 @@ "interchainAccountIsm": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", "interchainAccountRouter": "0x7a4d31a686A36285d68e14EDD53631417eB19603", "interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", - "interchainSecurityModule": "0x32A4A7aB028eb349b20D6370e8B56ba82CbB45c7", + "interchainSecurityModule": "0xF11b43b26e1Df7A7fc9E2fB7982630Ca9E78Db17", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "name": "redstone", @@ -2619,7 +2615,7 @@ "from": 937117 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x72ee68825c1Ad788E4F2414972f11CE92d141Fc1", + "interchainSecurityModule": "0xa8dBD60A10bC4f2B807Abc040c620453b1Ad9fb0", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "sanko", @@ -2687,7 +2683,7 @@ "interchainAccountIsm": "0x32af5Df81fEd5E26119F6640FBB13f3d63a94CDe", "interchainAccountRouter": "0x0B48a744698ba8dFa514742dFEB6728f52fD66f7", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "interchainSecurityModule": "0x2811146829429c551285889134C0014c35b38c24", + "interchainSecurityModule": "0x86e427d91d26eCD2542846D4C9b57186e9339a37", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "name": "scroll", @@ -2703,7 +2699,19 @@ "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "rpcUrls": [ { - "http": "https://scroll.blockpi.network/v1/rpc/public" + "http": "https://rpc.scroll.io" + }, + { + "http": "https://rpc.ankr.com/scroll" + }, + { + "http": "https://scroll-mainnet.chainstacklabs.com" + }, + { + "http": "https://scroll.drpc.org" + }, + { + "http": "https://1rpc.io/scroll" } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", @@ -2750,12 +2758,13 @@ "gasCurrencyCoinGeckoId": "sei-network", "gnosisSafeTransactionServiceUrl": "https://transaction.sei-safe.protofire.io", "index": { - "from": 80809403 + "from": 80809403, + "chunk": 1000 }, "interchainAccountIsm": "0xf35dc7B9eE4Ebf0cd3546Bd6EE3b403dE2b9F5D6", "interchainAccountRouter": "0xBcaedE97a98573A88242B3b0CB0A255F3f90d4d5", "interchainGasPaymaster": "0xFC62DeF1f08793aBf0E67f69257c6be258194F72", - "interchainSecurityModule": "0xBD1AF09F9dEC9E3b7a63C9c0B65C514E035238ff", + "interchainSecurityModule": "0xFDd69b3E4c13c6D65f856F99A12BFA0F0cc529D7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xca1b69fA4c4a7c7fD839bC50867c589592bcfe49", "name": "sei", @@ -2816,7 +2825,7 @@ "index": { "from": 1, "mode": "sequence", - "chunk": 100 + "chunk": 20 }, "interchainGasPaymaster": "JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M", "mailbox": "E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi", @@ -2870,7 +2879,7 @@ "interchainAccountIsm": "0xAE557e108b3336130370aC74836f1356B4b30Cf2", "interchainAccountRouter": "0x1F8CF09F060A2AE962c0Bb1F92e209a1E7b0E10B", "interchainGasPaymaster": "0x273Bc6b01D9E88c064b6E5e409BdF998246AEF42", - "interchainSecurityModule": "0x21008aF197f3Ae07fA485d0b34A3b4Dd26FC7633", + "interchainSecurityModule": "0xd663Fb2EE0F8E351869e3E4CdbDeA3b0e440Eb98", "mailbox": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3", "merkleTreeHook": "0x6A55822cf11f9fcBc4c75BC2638AfE8Eb942cAdd", "name": "taiko", @@ -2932,7 +2941,7 @@ "from": 1678063 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xC453a7f24540B90aB43681A5Bc07c9E03acC1a48", + "interchainSecurityModule": "0x56c8e2Ca77EA631D049e9AdbCC81902c4A420AF8", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", @@ -3000,7 +3009,7 @@ "interchainAccountIsm": "0x551BbEc45FD665a8C95ca8731CbC32b7653Bc59B", "interchainAccountRouter": "0xc11f8Cf2343d3788405582F65B8af6A4F7a6FfC8", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xDbfc19C0F633126a8e276ec884297B49CF5E13E2", + "interchainSecurityModule": "0x83D407ebFa89d1D64833F314f6ad6bBc57728D66", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "viction", @@ -3068,7 +3077,7 @@ "interchainAccountIsm": "0xCB9f90EE5d83Ea52ABd922BD70898f0155D54798", "interchainAccountRouter": "0x473884010F0C1742DA8Ad01E7E295624B931076b", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0xFFd81050Be09ee3D565C3a930dA0692e1D9d634F", + "interchainSecurityModule": "0x85bD389452743323afF638a3486A8cC451a33329", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "worldchain", @@ -3130,7 +3139,7 @@ "from": 24395308 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xE18ED85F5fb475B3BB24972c0EdeD1ea7642EB8b", + "interchainSecurityModule": "0x1E6fdCA7fda50eD32c2c54b28a0E0557e6065fba", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "xai", @@ -3198,7 +3207,7 @@ "interchainAccountIsm": "0x29B37088724B745C0ABcE591449Cf042772160C2", "interchainAccountRouter": "0x03cF708E42C89623bd83B281A56935cB562b9258", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0xB80876e19Ce1beF4Db7F52B07155C9d57A3c5135", + "interchainSecurityModule": "0x0F95bC504568913673CB7C1e6BeFc605a31bD945", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "xlayer", @@ -3266,7 +3275,7 @@ "interchainAccountIsm": "0x2b6d3F7d28B5EC8C3C028fBCAdcf774D9709Dd29", "interchainAccountRouter": "0x3AdCBc94ab8C48EC52D06dc65Bb787fD1981E3d5", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x75834EC3d39e0D1307715355dd7Dd2b2E8f3fC0A", + "interchainSecurityModule": "0x0c979c68c88459F6b4cCfEc73B6d6Ac262f7159e", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "zetachain", @@ -3332,7 +3341,7 @@ "from": 1511458 }, "interchainGasPaymaster": "0x03cF708E42C89623bd83B281A56935cB562b9258", - "interchainSecurityModule": "0x52E661244b9c2123EeF1D4cb85D489bba9422D73", + "interchainSecurityModule": "0x3abC1e4D81c96C7cF6E80638dD8dBcc9FF1BD4C3", "mailbox": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", "merkleTreeHook": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", "name": "zircuit", @@ -3405,7 +3414,7 @@ "interchainAccountIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e", "interchainAccountRouter": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "interchainSecurityModule": "0xc5354e5540D9Ae61Ee7ee7D177F4d9B58A1883d1", + "interchainSecurityModule": "0x0393C0ba78562BCC043c0C7c76Ed09230b04A35f", "mailbox": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", "name": "zoramainnet", @@ -3476,7 +3485,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x7d38C592002bB0994fB5Dc2F557AF32a1aF94605", + "interchainSecurityModule": "0xa5a09C88941410515A5Fc6D42FFb4Db3B6722274", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3543,7 +3552,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x203Ac1908BCd2721BbC36B1E7889be3C3d19858E", + "interchainSecurityModule": "0xa221Fda4b2fd4b586BbE14aB39E79F186de16CA9", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3613,7 +3622,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xf83ef66fa36643516b82Af671D43C914B7ea7582", + "interchainSecurityModule": "0xdC6E982Ea3091006b1Ef0CB6Acfe018b058eEd19", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3689,7 +3698,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x6912fb43dE8878bAee4CF38Dd4982c4eb42F81EF", + "interchainSecurityModule": "0x27d3ADAA10646b8787A614D5d1ef257593BEe1Ea", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3753,7 +3762,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xc324703B7bDD8640B85d6be73888D9673DB5736e", + "interchainSecurityModule": "0xe43d5f9b4da11ef7F775Ba2B40b112A7F706dF83", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3826,7 +3835,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xEED58BC374386596a848856aDCD07f597B70e2d9", + "interchainSecurityModule": "0xb9B8bD14661DE405F7597CC5b0D859C568700dF6", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3894,7 +3903,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xC444cA862A91A87fa3DCdbcf1747E4A8B27eAD73", + "interchainSecurityModule": "0x0081c30768c4C91C73d7871E5ae25B0d27276Fc0", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3957,7 +3966,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x8a2320fD0B7c75DE3Ad99Faf1F01930d6a6D166a", + "interchainSecurityModule": "0xa4D428c24332A7E42a49b47A8c56f489ce43426F", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -4027,7 +4036,7 @@ "interchainAccountIsm": "0xcd9D3744512F07AE844c40E27912092d7c503565", "interchainAccountRouter": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", "interchainGasPaymaster": "0xb58257cc81E47EC72fD38aE16297048de23163b4", - "interchainSecurityModule": "0x1f586E16bfB622123E9d166bAd2F3CB1F87Dc117", + "interchainSecurityModule": "0x621711bf30D680256460884a626b6B2A3F8Fa2Bd", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xCC3D1659D50461d27a2F025dDb2c9B06B584B7e1", "pausableHook": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", @@ -4087,7 +4096,7 @@ "interchainAccountIsm": "0xc23BaF5Eb5848D19701BbE7f139645e6bd58a319", "interchainAccountRouter": "0x7c58Cadcc2b60ACF794eE1843488d6f5703f76BE", "interchainGasPaymaster": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86", - "interchainSecurityModule": "0x80140b82E5C4c6Ee0Fe755De0A59aEb0fBBFef8B", + "interchainSecurityModule": "0x70a1E8ee35227962f03a767c2b1073D2000c0c3D", "mailbox": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", "merkleTreeHook": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", "pausableHook": "0x6Fb36672365C7c797028C400A61c58c0ECc53cD2", @@ -4218,7 +4227,7 @@ "interchainAccountIsm": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", "interchainAccountRouter": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "interchainSecurityModule": "0xab325b387731928dADf80E8E5501B474f85e769C", + "interchainSecurityModule": "0x2011044b3949C6406Cf47BC8c3122e7D70331266", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", "pausableHook": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", @@ -4282,7 +4291,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x01F399cD43B492D536a925CF52BEAB620019a479", + "interchainSecurityModule": "0xb35e7Cc9d45F3A5AD7dab46EB71F0b564B985322", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4343,7 +4352,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x5bc300Fa6c5761019425c2919F73C7df7A5cb6aA", + "interchainSecurityModule": "0x9219ED9F72c64961c7745C61A1a3492b16435d06", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4422,7 +4431,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x77F728DAF925089cF9536F89C32aB0abAa2e294D", + "interchainSecurityModule": "0xe24AdcdFA8B77A69F4134c6A7c8AB571911fDD43", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4489,7 +4498,7 @@ "interchainAccountIsm": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", "interchainAccountRouter": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", "interchainGasPaymaster": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", - "interchainSecurityModule": "0x8ca77BeAC75B19052a49Ae6Ef743fe02EC56B41f", + "interchainSecurityModule": "0xD6f9589f8d1Aa8e4f196174Be8046cF94D4364B9", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", "pausableHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", @@ -4560,7 +4569,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x643dC25467048e2e835A44561D913A98f90bD962", + "interchainSecurityModule": "0x9DdF78676e47935f24DbcC6C0FDF9FF6687Fc461", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4625,7 +4634,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0xeAe69924A25c0CC57Ff771689fb05d53a7FD29B1", + "interchainSecurityModule": "0x540605591E8C763FA4C1DeE91A674E39b33c768a", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4689,7 +4698,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x790e7dDEf110E6dF8aA51f713400f6ec0e6758b6", + "interchainSecurityModule": "0x132655e619098058D595E871206e7ec1f4a8cb00", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4756,7 +4765,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x57d57F50065F7390Afc13e8917562061998CfaB2", + "interchainSecurityModule": "0x1277E184770eFF57bc54a8317cff379C53Ed307C", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4820,7 +4829,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x01F399cD43B492D536a925CF52BEAB620019a479", + "interchainSecurityModule": "0xb35e7Cc9d45F3A5AD7dab46EB71F0b564B985322", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4882,7 +4891,7 @@ "fallbackDomainRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", "fallbackRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", "interchainGasPaymaster": "0x318FbdB17d4e743aBF3183658a4730777101B75C", - "interchainSecurityModule": "0xd60690A755C070817AacF88d1FD3F64451D80149", + "interchainSecurityModule": "0x753876a50C354F2EfB35D1D37dA7F25fd5a78250", "mailbox": "0xd7b351D2dE3495eA259DD10ab4b9300A378Afbf3", "merkleTreeHook": "0x55379421409961Ef129738c24261379ef8A547Df", "proxyAdmin": "0x72e2A678442Edc65f14476A0E4c94312C0469f4A", @@ -5006,7 +5015,7 @@ "interchainAccountIsm": "0x4d264424905535E97396Db83bd553D0d73A4EF9d", "interchainAccountRouter": "0x26A29486480BD74f9B830a9B8dB33cb43C40f496", "interchainGasPaymaster": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", - "interchainSecurityModule": "0xd2bFe972583b513302C0d0BD4Ef0Ac146428776C", + "interchainSecurityModule": "0x89Ea8BAEC48ec2EB8F5444aD600a15dABd4426D7", "mailbox": "0x5bdADEAD721Eb4C4038fF7c989E3C7BbBA302435", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "pausableHook": "0xC8E323036AAFB4B4201e7B640E79C4Db285A3FC8", @@ -5070,7 +5079,7 @@ "interchainAccountIsm": "0x545E289B88c6d97b74eC0B96e308cae46Bf5f832", "interchainAccountRouter": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", "interchainGasPaymaster": "0x561BcA8D862536CD9C88f332C1A1Da0fC8F96e40", - "interchainSecurityModule": "0x8Ef057A24ad9E3B5e86ba9015e63E1FDf16E5B83", + "interchainSecurityModule": "0x89E35A66511eF1446DaB436FDF9c1eF93EFaa03D", "mailbox": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", "merkleTreeHook": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", "pausableHook": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", @@ -5137,7 +5146,7 @@ "interchainAccountIsm": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", "interchainAccountRouter": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", "interchainGasPaymaster": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", - "interchainSecurityModule": "0x7A36F64A6CbFd2e6fA82D9616a8557731B283856", + "interchainSecurityModule": "0xF88aFBD5C7D6f804bb525Bc8f3A12906Ded7C715", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", "pausableHook": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", @@ -5202,7 +5211,7 @@ "interchainAccountIsm": "0xcdc31BA959DE8C035A03167ebAE1961208CDf172", "interchainAccountRouter": "0x349831a180eE4265008C5FFB9465Ff97c1CF0028", "interchainGasPaymaster": "0x6AA10748a036a49Cb290C0e12B77319b76792D5E", - "interchainSecurityModule": "0xd03eb8402F1849B9C3317f0ac12Ef8cbB0288ebd", + "interchainSecurityModule": "0xEeEF1a33C123D8a3Ee96C670fC740D4e7A603329", "mailbox": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "merkleTreeHook": "0x2783D98CC073dbcDa90241C804d16982D3d75821", "pausableHook": "0x3bb2D0a828f7dD91bA786091F421f6d7cF376445", @@ -5272,7 +5281,7 @@ "interchainAccountIsm": "0x545E289B88c6d97b74eC0B96e308cae46Bf5f832", "interchainAccountRouter": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", "interchainGasPaymaster": "0xc6835e52C1b976F1ebC71Bc8919738E02849FdA9", - "interchainSecurityModule": "0x666519868126919DD68C24E684D2059cc8991833", + "interchainSecurityModule": "0x06B1B5afCFfB8F49Dcea935A5E9E6A8FF96Da029", "mailbox": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "merkleTreeHook": "0xdAa1B65547fB969c9ff5678956AB2FF9771B883D", "pausableHook": "0xA0e0829DA397CcF55d5B779C31728f21Cb8219DF", @@ -5384,7 +5393,7 @@ "interchainAccountIsm": "0x8c794a781327b819416E7b67908f1D22397f1E67", "interchainAccountRouter": "0x16625230dD6cFe1B2bec3eCaEc7d43bA3A902CD6", "interchainGasPaymaster": "0x2b79328DA089E89A9E9c08732b56dd31F01011Db", - "interchainSecurityModule": "0x6819a646e9Fc12eB98848038314728082F539617", + "interchainSecurityModule": "0x7E81F96355529Cf7Cb768B3CCf49552E727eC7EE", "mailbox": "0x730f8a4128Fa8c53C777B62Baa1abeF94cAd34a9", "merkleTreeHook": "0x9c64f327F0140DeBd430aab3E2F1d6cbcA921227", "pausableHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", @@ -5448,7 +5457,7 @@ "interchainAccountIsm": "0xE67Dc24970B482579923551Ede52BD35a2858989", "interchainAccountRouter": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409", "interchainGasPaymaster": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", - "interchainSecurityModule": "0xfBB6D85fafFE439d470652fBE1F86f782807403f", + "interchainSecurityModule": "0xdA07f00d13E908b7c52C02b38AB748d924DdE890", "mailbox": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", "merkleTreeHook": "0xC8E323036AAFB4B4201e7B640E79C4Db285A3FC8", "pausableHook": "0xdAa1B65547fB969c9ff5678956AB2FF9771B883D", @@ -5512,7 +5521,7 @@ "interchainAccountIsm": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", "interchainAccountRouter": "0x5b3EeADcc0E2d4284eA6816e2E503c24d30a9E54", "interchainGasPaymaster": "0x282629Af1A2f9b8e2c5Cbc54C35C7989f21950c6", - "interchainSecurityModule": "0xea0A028775a07EF27CfD1363Ce7463b31ad57296", + "interchainSecurityModule": "0x90B3696922e6a8128EEB23F08Cbf5b2545f849C6", "mailbox": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "merkleTreeHook": "0xf147bBD944C610F86DaE6C7668497D22932C1E4A", "pausableHook": "0x872Bd98057931c8809927c6dE2ef39738a80Eb0C", @@ -5579,7 +5588,7 @@ "interchainAccountIsm": "0xf40eE9FF75Fa34910b7C4C8d68d4850B3bD184D3", "interchainAccountRouter": "0xf6fB78dc009C1A4286c0E7d90C10c9E8906a62Ea", "interchainGasPaymaster": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409", - "interchainSecurityModule": "0x36Fe416e4415C93352bD33DC77B83A17850d581e", + "interchainSecurityModule": "0x727DaD31073Dcc961B76Cbd625D6d074fA7988c9", "mailbox": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615", "merkleTreeHook": "0x8c794a781327b819416E7b67908f1D22397f1E67", "pausableHook": "0x4d264424905535E97396Db83bd553D0d73A4EF9d", @@ -5646,7 +5655,7 @@ "interchainAccountIsm": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "interchainAccountRouter": "0x7279B1e11142078b8dC9e69620200f4C84FB8aaa", "interchainGasPaymaster": "0x5ae1ECA065aC8ee92Ce98E584fc3CE43070020e7", - "interchainSecurityModule": "0xe93396575310fbfd1815505dA60963f874cb9CC8", + "interchainSecurityModule": "0x8A4C3c1f234e9da8e325902f7105471B078a1e23", "mailbox": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", "merkleTreeHook": "0x086c3947F71BE98A0bDf4AB7239955e7542b0CbA", "pausableHook": "0x9C6e8d989ea7F212e679191BEb44139d83ac927a", @@ -5716,7 +5725,7 @@ "interchainAccountIsm": "0x8a733038eF4BbC314eE0F7595257D8d3799B6aA9", "interchainAccountRouter": "0xCE8260c1b5cF2fAD15bb4B6542716b050Fdf35c9", "interchainGasPaymaster": "0xa1c3884EbE24Cccb120B2E98a55f85140563aa4C", - "interchainSecurityModule": "0x22CD0d9ad151e694cE92Ce07cB921918ad94d87e", + "interchainSecurityModule": "0xff5306993F410008d1d7581695252e95c55bc458", "mailbox": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6", "merkleTreeHook": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", "pausableHook": "0xc6835e52C1b976F1ebC71Bc8919738E02849FdA9", @@ -5777,7 +5786,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x802173518092b5af3079762a1fbeF8bbAf4593a0", + "interchainSecurityModule": "0x16c7A4C2f4BCDBFC8205Baa9F1dCBcd44B4d8d90", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5847,7 +5856,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0xA07D4B3a8da7F513BFD70866fCFfC42D34d91a87", + "interchainSecurityModule": "0x78E12CF0Ee442f7B9E6df137e0c038A4b6e8d8Eb", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5908,7 +5917,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x2d9c837157923B40bdF1e64a2A9fC13E37Dd9246", + "interchainSecurityModule": "0xF2c7E4Ad99b08d1a2CF139cb7b79d645b50BEdEa", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5972,7 +5981,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x8d7912ca475362E055808Bc3Dd67B117e007523d", + "interchainSecurityModule": "0x0D9b693d45690a9FfE8F1e8E0dC41A2C6207E090", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -6036,7 +6045,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0xdBB9466B300AC49BC2C91bB975643b592A126514", + "interchainSecurityModule": "0x78E12CF0Ee442f7B9E6df137e0c038A4b6e8d8Eb", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -6109,7 +6118,7 @@ "interchainAccountIsm": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainAccountRouter": "0x4D50044335dc1d4D26c343AdeDf6E47808475Deb", "interchainGasPaymaster": "0x70EbA87Cd15616f32C736B3f3BdCfaeD0713a82B", - "interchainSecurityModule": "0xcf5B2Fa0cDa42ec6CB947A84384f136416FF8341", + "interchainSecurityModule": "0xE490AbA47A8b6B26dFF22A61aAFd51C52fD386c2", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "pausableHook": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", @@ -6173,7 +6182,7 @@ "interchainAccountIsm": "0x31Bb27f6007C33acD1be83ACEd3164C60f8F7b13", "interchainAccountRouter": "0xEeb5a99a75585fe137c83E7b62b74f87264A5481", "interchainGasPaymaster": "0xb7C9307fE90B9AB093c6D3EdeE3259f5378D5f03", - "interchainSecurityModule": "0x0a698848Dbf4819b22A37E02748588AcF9936F22", + "interchainSecurityModule": "0x56b99cFe785C7e9F61040cdd4081d63Ef48Cf928", "mailbox": "0x0dF25A2d59F03F039b56E90EdC5B89679Ace28Bc", "merkleTreeHook": "0xC88636fFdFAc7cb87b7A76310B7a62AF0A000595", "pausableHook": "0x2AF32cF8e3Cf42d221eDa0c843818fA5ee129E27", @@ -6237,7 +6246,7 @@ "interchainAccountIsm": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", "interchainAccountRouter": "0xe9E3444DDD80c50276c0Fcf316026f6d7fEc2c47", "interchainGasPaymaster": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainSecurityModule": "0xdd8378d5A11ad5B48699dfCF87a06aF595410D49", + "interchainSecurityModule": "0xCb1514f34Dfd366cF74D0F58F3D8BDE49C059533", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "pausableHook": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", @@ -6305,7 +6314,7 @@ "interchainAccountIsm": "0x027eFD1695941969435AA640542B690044dF7E06", "interchainAccountRouter": "0x65F1343AC23D4fF48bf6c7E0c55872d245397567", "interchainGasPaymaster": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", - "interchainSecurityModule": "0x0a297b209EE1Db131F4624819fa0cc8D119d01e5", + "interchainSecurityModule": "0xF0f29D8b9d16aE092886A38571D5E7Ffdb508EB3", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xcd90D49b046772F710250b9119117169CB2e4D8b", "pausableHook": "0x7CE76f5f0C469bBB4cd7Ea6EbabB54437A093127", @@ -6488,7 +6497,7 @@ "interchainAccountIsm": "0xf9609bB22847e0DB5F6fB8f95b84D25A19b46ac5", "interchainAccountRouter": "0x2b6d3F7d28B5EC8C3C028fBCAdcf774D9709Dd29", "interchainGasPaymaster": "0xFb7D175d6F53800D68D32C3Fe1416807A394cC24", - "interchainSecurityModule": "0x565f3Ad39Acb67D673fb568575DcE6Ba1861d723", + "interchainSecurityModule": "0x882eE9A3938501558823514b70d8bBa945953B80", "mailbox": "0x473884010F0C1742DA8Ad01E7E295624B931076b", "merkleTreeHook": "0xdA629E1B79e3420ECd1e80571aDd6a4a3b13AE79", "pausableHook": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", @@ -6555,7 +6564,7 @@ "interchainAccountIsm": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", "interchainAccountRouter": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", "interchainGasPaymaster": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", - "interchainSecurityModule": "0x4DaeEeC1c7FC2316419261ae3C4220191D6e12A3", + "interchainSecurityModule": "0x597210e915409D2B9D4487Ef21ea115444e419Ed", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", "pausableHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", @@ -6625,7 +6634,7 @@ "interchainAccountIsm": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainAccountRouter": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xd285467525196946efDa56fc26DAbFe816A5E4E4", + "interchainSecurityModule": "0x7664aAE6C1bCFfe364F742c75f52D3dC70FF412E", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6692,7 +6701,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x44f0875a9a412849B2167c7a4A3ECE4e13ba1ac4", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6762,7 +6771,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x44f0875a9a412849B2167c7a4A3ECE4e13ba1ac4", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6832,7 +6841,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x03Def25faaABF7aa393eC4108267f0b2c0B1F58f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6896,7 +6905,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x03Def25faaABF7aa393eC4108267f0b2c0B1F58f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6949,7 +6958,7 @@ "protocol": "ethereum", "rpcUrls": [ { - "http": "https://rpc.inkonchain.com" + "http": "https://rpc-qnd.inkonchain.com" } ], "technicalStack": "opstack", @@ -6960,7 +6969,7 @@ "interchainAccountIsm": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", "interchainAccountRouter": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", "interchainGasPaymaster": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", - "interchainSecurityModule": "0x7f124ad38fDF2FDdcD8554fd44f72b0A56EA5644", + "interchainSecurityModule": "0x017a26fE04aB95c77BA6DeE4d441A3F2402BBe17", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", "pausableHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", @@ -7027,7 +7036,7 @@ "interchainAccountIsm": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", "interchainAccountRouter": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", "interchainGasPaymaster": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", - "interchainSecurityModule": "0x8f1953EFbd2C720223faD02d6CB5CD25f97D7fC9", + "interchainSecurityModule": "0x0fB08d319a9Daaf6e8005873D2BA1980Bd59c614", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "pausableHook": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", @@ -7088,7 +7097,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x7664aAE6C1bCFfe364F742c75f52D3dC70FF412E", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -7152,7 +7161,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x794BF08dE42238809d811193A6189D6d03177DF8", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -7219,7 +7228,7 @@ "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf0F40bf7FFc8B05b639D657f02Fc7B75A218e068", + "interchainSecurityModule": "0x557862e7ADd75E72779316b119F75358b3445102", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", diff --git a/rust/main/helm/hyperlane-agent/templates/external-secret.yaml b/rust/main/helm/hyperlane-agent/templates/external-secret.yaml index 9be0700b75..f3a6980d21 100644 --- a/rust/main/helm/hyperlane-agent/templates/external-secret.yaml +++ b/rust/main/helm/hyperlane-agent/templates/external-secret.yaml @@ -30,6 +30,14 @@ spec: {{- if eq .protocol "cosmos" }} HYP_CHAINS_{{ .name | upper }}_CUSTOMGRPCURLS: {{ printf "'{{ .%s_grpcs | mustFromJson | join \",\" }}'" .name }} {{- end }} + {{- if eq .protocol "sealevel" }} + {{- if eq ((.priorityFeeOracle).type) "helius" }} + HYP_CHAINS_{{ .name | upper }}_PRIORITYFEEORACLE_URL: {{ printf "'{{ .%s_helius }}'" .name }} + {{- end }} + {{- if eq ((.transactionSubmitter).url) "helius" }} + HYP_CHAINS_{{ .name | upper }}_TRANSACTIONSUBMITTER_URL: {{ printf "'{{ .%s_helius }}'" .name }} + {{- end }} + {{- end }} {{- end }} data: {{- /* @@ -45,4 +53,9 @@ spec: remoteRef: key: {{ printf "%s-grpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} {{- end }} + {{- if and (eq .protocol "sealevel") (or (eq ((.priorityFeeOracle).type) "helius") (eq ((.transactionSubmitter).url) "helius")) }} + - secretKey: {{ printf "%s_helius" .name }} + remoteRef: + key: {{ printf "%s-rpc-endpoint-helius-%s" $.Values.hyperlane.runEnv .name }} + {{- end }} {{- end }} diff --git a/rust/main/helm/hyperlane-agent/templates/relayer-statefulset.yaml b/rust/main/helm/hyperlane-agent/templates/relayer-statefulset.yaml index da69543d8e..47be9ad2e9 100644 --- a/rust/main/helm/hyperlane-agent/templates/relayer-statefulset.yaml +++ b/rust/main/helm/hyperlane-agent/templates/relayer-statefulset.yaml @@ -17,7 +17,9 @@ spec: metadata: annotations: checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/external-secret: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} checksum/relayer-configmap: {{ include (print $.Template.BasePath "/relayer-configmap.yaml") . | sha256sum }} + checksum/relayer-external-secret: {{ include (print $.Template.BasePath "/relayer-external-secret.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/rust/main/helm/hyperlane-agent/templates/scraper-statefulset.yaml b/rust/main/helm/hyperlane-agent/templates/scraper-statefulset.yaml index 06326e260c..1b419e1123 100644 --- a/rust/main/helm/hyperlane-agent/templates/scraper-statefulset.yaml +++ b/rust/main/helm/hyperlane-agent/templates/scraper-statefulset.yaml @@ -17,6 +17,8 @@ spec: metadata: annotations: checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/external-secret: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} + checksum/scraper-external-secret: {{ include (print $.Template.BasePath "/scraper-external-secret.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/rust/main/helm/hyperlane-agent/templates/validator-statefulset.yaml b/rust/main/helm/hyperlane-agent/templates/validator-statefulset.yaml index 1b0a87dd41..b5929bfd99 100644 --- a/rust/main/helm/hyperlane-agent/templates/validator-statefulset.yaml +++ b/rust/main/helm/hyperlane-agent/templates/validator-statefulset.yaml @@ -17,7 +17,9 @@ spec: metadata: annotations: checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/external-secret: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} checksum/validator-configmap: {{ include (print $.Template.BasePath "/validator-configmap.yaml") . | sha256sum }} + checksum/scraper-external-secret: {{ include (print $.Template.BasePath "/scraper-external-secret.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/rust/main/hyperlane-base/src/metrics/core.rs b/rust/main/hyperlane-base/src/metrics/core.rs index 5603ece225..d3cbaf3db4 100644 --- a/rust/main/hyperlane-base/src/metrics/core.rs +++ b/rust/main/hyperlane-base/src/metrics/core.rs @@ -38,6 +38,7 @@ pub struct CoreMetrics { span_counts: IntCounterVec, span_events: IntCounterVec, last_known_message_nonce: IntGaugeVec, + latest_leaf_index: IntGaugeVec, submitter_queue_length: IntGaugeVec, operations_processed_count: IntCounterVec, @@ -112,6 +113,16 @@ impl CoreMetrics { registry )?; + let latest_leaf_index = register_int_gauge_vec_with_registry!( + opts!( + namespaced!("latest_leaf_index"), + "Latest leaf index inserted into the merkle tree", + const_labels_ref + ), + &["origin"], + registry + )?; + let observed_validator_latest_index = register_int_gauge_vec_with_registry!( opts!( namespaced!("observed_validator_latest_index"), @@ -177,6 +188,7 @@ impl CoreMetrics { span_counts, span_events, last_known_message_nonce, + latest_leaf_index, submitter_queue_length, @@ -309,6 +321,14 @@ impl CoreMetrics { self.last_known_message_nonce.clone() } + /// Reports the current highest leaf index which was inserted into the merkle tree. + /// + /// Labels: + /// - `origin`: Origin chain the leaf index is being tracked at. + pub fn latest_leaf_index(&self) -> IntGaugeVec { + self.latest_leaf_index.clone() + } + /// Latest message nonce in the validator. /// /// Phase: diff --git a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs index 70e1b81835..b3f91ee88e 100644 --- a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs @@ -1,4 +1,7 @@ use eyre::eyre; +use hyperlane_sealevel::{ + HeliusPriorityFeeLevel, HeliusPriorityFeeOracleConfig, PriorityFeeOracleConfig, +}; use url::Url; use h_eth::TransactionOverrides; @@ -162,12 +165,24 @@ fn build_sealevel_connection_conf( chain: &ValueParser, err: &mut ConfigParsingError, operation_batch: OperationBatchConfig, -) -> h_sealevel::ConnectionConf { +) -> Option { + let mut local_err = ConfigParsingError::default(); + let native_token = parse_native_token(chain, err, 9); - h_sealevel::ConnectionConf { - url: url.clone(), - operation_batch, - native_token, + let priority_fee_oracle = parse_sealevel_priority_fee_oracle_config(chain, &mut local_err); + let transaction_submitter = parse_transaction_submitter_config(chain, &mut local_err); + + if !local_err.is_ok() { + err.merge(local_err); + None + } else { + Some(ChainConnectionConf::Sealevel(h_sealevel::ConnectionConf { + url: url.clone(), + operation_batch, + native_token, + priority_fee_oracle: priority_fee_oracle.unwrap(), + transaction_submitter: transaction_submitter.unwrap(), + })) } } @@ -196,6 +211,147 @@ fn parse_native_token( } } +fn parse_sealevel_priority_fee_oracle_config( + chain: &ValueParser, + err: &mut ConfigParsingError, +) -> Option { + let value_parser = chain.chain(err).get_opt_key("priorityFeeOracle").end(); + + let priority_fee_oracle = if let Some(value_parser) = value_parser { + let oracle_type = value_parser + .chain(err) + .get_key("type") + .parse_string() + .end() + .or_else(|| { + err.push( + &value_parser.cwp + "type", + eyre!("Missing priority fee oracle type"), + ); + None + }) + .unwrap_or_default(); + + match oracle_type { + "constant" => { + let fee = value_parser + .chain(err) + .get_key("fee") + .parse_u64() + .end() + .unwrap_or(0); + Some(PriorityFeeOracleConfig::Constant(fee)) + } + "helius" => { + let fee_level = parse_helius_priority_fee_level(&value_parser, err); + if !err.is_ok() { + return None; + } + let config = HeliusPriorityFeeOracleConfig { + url: value_parser + .chain(err) + .get_key("url") + .parse_from_str("Invalid url") + .end() + .unwrap(), + fee_level: fee_level.unwrap(), + }; + Some(PriorityFeeOracleConfig::Helius(config)) + } + _ => { + err.push( + &value_parser.cwp + "type", + eyre!("Unknown priority fee oracle type"), + ); + None + } + } + } else { + // If not specified at all, use default + Some(PriorityFeeOracleConfig::default()) + }; + + priority_fee_oracle +} + +fn parse_helius_priority_fee_level( + value_parser: &ValueParser, + err: &mut ConfigParsingError, +) -> Option { + let level = value_parser + .chain(err) + .get_opt_key("feeLevel") + .parse_string() + .end(); + + if let Some(level) = level { + match level.to_lowercase().as_str() { + "recommended" => Some(HeliusPriorityFeeLevel::Recommended), + "min" => Some(HeliusPriorityFeeLevel::Min), + "low" => Some(HeliusPriorityFeeLevel::Low), + "medium" => Some(HeliusPriorityFeeLevel::Medium), + "high" => Some(HeliusPriorityFeeLevel::High), + "veryhigh" => Some(HeliusPriorityFeeLevel::VeryHigh), + "unsafemax" => Some(HeliusPriorityFeeLevel::UnsafeMax), + _ => { + err.push( + &value_parser.cwp + "feeLevel", + eyre!("Unknown priority fee level"), + ); + None + } + } + } else { + // If not specified at all, use the default + Some(HeliusPriorityFeeLevel::default()) + } +} + +fn parse_transaction_submitter_config( + chain: &ValueParser, + err: &mut ConfigParsingError, +) -> Option { + let submitter_type = chain + .chain(err) + .get_opt_key("transactionSubmitter") + .get_opt_key("type") + .parse_string() + .end(); + + if let Some(submitter_type) = submitter_type { + match submitter_type.to_lowercase().as_str() { + "rpc" => { + let url = chain + .chain(err) + .get_opt_key("transactionSubmitter") + .get_opt_key("url") + .parse_from_str("Invalid url") + .end(); + Some(h_sealevel::TransactionSubmitterConfig::Rpc { url }) + } + "jito" => { + let url = chain + .chain(err) + .get_opt_key("transactionSubmitter") + .get_opt_key("url") + .parse_from_str("Invalid url") + .end(); + Some(h_sealevel::TransactionSubmitterConfig::Jito { url }) + } + _ => { + err.push( + &chain.cwp + "transactionSubmitter.type", + eyre!("Unknown transaction submitter type"), + ); + None + } + } + } else { + // If not specified at all, use default + Some(h_sealevel::TransactionSubmitterConfig::default()) + } +} + pub fn build_connection_conf( domain_protocol: HyperlaneDomainProtocol, rpcs: &[Url], @@ -216,14 +372,10 @@ pub fn build_connection_conf( .iter() .next() .map(|url| ChainConnectionConf::Fuel(h_fuel::ConnectionConf { url: url.clone() })), - HyperlaneDomainProtocol::Sealevel => rpcs.iter().next().map(|url| { - ChainConnectionConf::Sealevel(build_sealevel_connection_conf( - url, - chain, - err, - operation_batch, - )) - }), + HyperlaneDomainProtocol::Sealevel => rpcs + .iter() + .next() + .and_then(|url| build_sealevel_connection_conf(url, chain, err, operation_batch)), HyperlaneDomainProtocol::Cosmos => { build_cosmos_connection_conf(rpcs, chain, err, operation_batch) } diff --git a/rust/main/hyperlane-core/src/accumulator/incremental.rs b/rust/main/hyperlane-core/src/accumulator/incremental.rs index 5595bbc8cf..e71b9cbbe1 100644 --- a/rust/main/hyperlane-core/src/accumulator/incremental.rs +++ b/rust/main/hyperlane-core/src/accumulator/incremental.rs @@ -82,7 +82,7 @@ impl IncrementalMerkle { merkle_root_from_branch(item, &branch, 32, index) } - /// Verify a incremental merkle proof of inclusion + /// Verify an incremental merkle proof of inclusion pub fn verify(&self, proof: &Proof) -> bool { let computed = IncrementalMerkle::branch_root(proof.leaf, proof.path, proof.index); computed == self.root() diff --git a/rust/main/utils/run-locally/Cargo.toml b/rust/main/utils/run-locally/Cargo.toml index 9dedae9cea..a994324687 100644 --- a/rust/main/utils/run-locally/Cargo.toml +++ b/rust/main/utils/run-locally/Cargo.toml @@ -14,6 +14,7 @@ hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } toml_edit.workspace = true k256.workspace = true jobserver.workspace = true +reqwest.workspace = true ripemd.workspace = true sha2.workspace = true serde.workspace = true diff --git a/rust/main/utils/run-locally/src/main.rs b/rust/main/utils/run-locally/src/main.rs index 4686c15446..8855a08d86 100644 --- a/rust/main/utils/run-locally/src/main.rs +++ b/rust/main/utils/run-locally/src/main.rs @@ -51,6 +51,7 @@ mod invariants; mod logging; mod metrics; mod program; +mod server; mod solana; mod utils; @@ -483,6 +484,10 @@ fn main() -> ExitCode { // give things a chance to fully start. sleep(Duration::from_secs(10)); + // test retry request + let resp = server::run_retry_request().expect("Failed to process retry request"); + assert!(resp.matched > 0); + if !post_startup_invariants(&checkpoints_dirs) { log!("Failure: Post startup invariants are not met"); return report_test_result(true); diff --git a/rust/main/utils/run-locally/src/server.rs b/rust/main/utils/run-locally/src/server.rs new file mode 100644 index 0000000000..4df7df78f0 --- /dev/null +++ b/rust/main/utils/run-locally/src/server.rs @@ -0,0 +1,55 @@ +use std::io; + +use reqwest::Url; + +use relayer::server::MessageRetryResponse; + +use crate::RELAYER_METRICS_PORT; + +/// create tokio runtime to send a retry request to +/// relayer to retry all existing messages in the queues +pub fn run_retry_request() -> io::Result { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build(); + runtime + .unwrap() + .block_on(async { call_retry_request().await }) +} + +/// sends a request to relayer to retry all existing messages +/// in the queues +async fn call_retry_request() -> io::Result { + let client = reqwest::Client::new(); + + let url = Url::parse(&format!( + "http://0.0.0.0:{RELAYER_METRICS_PORT}/message_retry" + )) + .map_err(|err| { + eprintln!("Failed to parse url: {err}"); + io::Error::new(io::ErrorKind::InvalidInput, err.to_string()) + })?; + + let body = vec![serde_json::json!({ + "message_id": "*" + })]; + let retry_response = client.post(url).json(&body).send().await.map_err(|err| { + eprintln!("Failed to send request: {err}"); + io::Error::new(io::ErrorKind::InvalidData, err.to_string()) + })?; + + let response_text = retry_response.text().await.map_err(|err| { + eprintln!("Failed to parse response body: {err}"); + io::Error::new(io::ErrorKind::InvalidData, err.to_string()) + })?; + + println!("Retry Request Response: {:?}", response_text); + + let response_json: MessageRetryResponse = + serde_json::from_str(&response_text).map_err(|err| { + eprintln!("Failed to parse response body to json: {err}"); + io::Error::new(io::ErrorKind::InvalidData, err.to_string()) + })?; + + Ok(response_json) +} diff --git a/rust/main/utils/run-locally/src/solana.rs b/rust/main/utils/run-locally/src/solana.rs index 022d086a65..6dd137857f 100644 --- a/rust/main/utils/run-locally/src/solana.rs +++ b/rust/main/utils/run-locally/src/solana.rs @@ -159,7 +159,7 @@ pub fn build_solana_programs(solana_cli_tools_path: PathBuf) -> PathBuf { .working_dir(&out_path) .run() .join(); - log!("Remove temporary solana files"); + log!("Removing temporary solana files"); fs::remove_file(concat_path(&out_path, "spl.tar.gz")) .expect("Failed to remove solana program archive"); @@ -232,8 +232,7 @@ pub fn start_solana_test_validator( .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) .arg("built-so-dir", SBF_OUT_PATH) - .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE) - .flag("use-existing-keys"); + .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE); sealevel_client_deploy_core .clone() diff --git a/rust/sealevel/client/src/cmd_utils.rs b/rust/sealevel/client/src/cmd_utils.rs index f387d3c4fd..4b8da171e1 100644 --- a/rust/sealevel/client/src/cmd_utils.rs +++ b/rust/sealevel/client/src/cmd_utils.rs @@ -1,14 +1,17 @@ use std::{ collections::HashMap, fs::File, - io::Write, + io::{self, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, thread::sleep, time::Duration, }; -use solana_client::{client_error::ClientError, rpc_client::RpcClient}; +use solana_client::{ + client_error::{ClientError, ClientErrorKind}, + rpc_client::RpcClient, +}; use solana_sdk::{ commitment_config::CommitmentConfig, pubkey::Pubkey, @@ -44,37 +47,91 @@ pub(crate) fn account_exists(client: &RpcClient, account: &Pubkey) -> Result Result<(), ClientError> { +) -> Result { + let (program_keypair, program_keypair_path) = create_or_get_keypair( + program_key_dir, + format!("{}-keypair.json", program_name).as_str(), + ); + let program_id = program_keypair.pubkey(); + let client = RpcClient::new(url.to_string()); - if !account_exists(&client, &program_keypair.pubkey())? { - deploy_program( + if account_exists(&client, &program_keypair.pubkey())? { + println!("Program {} already deployed", program_keypair.pubkey()); + return Ok(program_id); + } + + let (buffer_keypair, buffer_keypair_path) = create_or_get_keypair( + program_key_dir, + format!("{}-buffer.json", program_name).as_str(), + ); + + let mut compute_unit_price = get_compute_unit_price_micro_lamports_for_id(local_domain); + + for attempt in 0..10 { + println!("Attempting program deploy Program ID: {}, buffer pubkey: {}, compute unit price: {}, attempt number {}", program_id, buffer_keypair.pubkey(), compute_unit_price, attempt); + + if attempt > 0 { + println!( + "As this is not the first deploy attempt, the buffer {} is re-used", + buffer_keypair.pubkey() + ); + } + + if attempt_program_deploy( payer_keypair_path, - program_keypair_path, + program_name, program_path, + &program_keypair_path, + &buffer_keypair_path, url, - local_domain, + compute_unit_price, + ) + .is_ok() + { + // Success! + return Ok(program_id); + } + + // Failed to deploy program, try again with a higher compute unit price + + println!( + "Failed to deploy program with compute unit price {}", + compute_unit_price ); - } else { - println!("Program {} already deployed", program_keypair.pubkey()); + + // Bump by 10% each time if non-zero, otherwise start at 1000 micro lamports + compute_unit_price = if compute_unit_price > 0 { + compute_unit_price * 11 / 10 + } else { + 1000 + }; + + println!( + "Sleeping 1s, then retrying with new compute unit price {}", + compute_unit_price + ); + sleep(Duration::from_secs(1)); } - Ok(()) + Err(ClientErrorKind::Custom(format!("Failed to deploy program {}", program_name)).into()) } -pub(crate) fn deploy_program( +fn attempt_program_deploy( payer_keypair_path: &str, - program_keypair_path: &str, + program_name: &str, program_path: &str, + program_keypair_path: &Path, + buffer_keypair_path: &Path, url: &str, - local_domain: u32, -) { + compute_unit_price: u64, +) -> Result<(), ClientError> { let mut command = vec![ "solana", "--url", @@ -87,19 +144,28 @@ pub(crate) fn deploy_program( "--upgrade-authority", payer_keypair_path, "--program-id", - program_keypair_path, + program_keypair_path.to_str().unwrap(), + "--buffer", + buffer_keypair_path.to_str().unwrap(), ]; - let compute_unit_price = get_compute_unit_price_micro_lamports_for_id(local_domain).to_string(); - if local_domain.eq(&SOLANA_DOMAIN) { - command.extend(vec!["--with-compute-unit-price", &compute_unit_price]); - } + let compute_unit_price_str = compute_unit_price.to_string(); + command.extend(vec!["--with-compute-unit-price", &compute_unit_price_str]); - build_cmd(command.as_slice(), None, None); + // Success! + if let Ok(true) = run_cmd(command.as_slice(), None, None) { + // TODO: use commitment level instead of just sleeping here? + println!("Sleeping for 2 seconds to fully allow program to be deployed"); + sleep(Duration::from_secs(2)); + + return Ok(()); + } - // TODO: use commitment level instead of just sleeping here? - println!("Sleeping for 2 seconds to allow program to be deployed"); - sleep(Duration::from_secs(2)); + Err(ClientErrorKind::Custom(format!( + "Attempted program deploy failed for {}", + program_name + )) + .into()) } pub(crate) fn create_new_directory(parent_dir: &Path, name: &str) -> PathBuf { @@ -109,20 +175,14 @@ pub(crate) fn create_new_directory(parent_dir: &Path, name: &str) -> PathBuf { path } -pub(crate) fn create_and_write_keypair( - key_dir: &Path, - key_name: &str, - use_existing_key: bool, -) -> (Keypair, PathBuf) { +pub(crate) fn create_or_get_keypair(key_dir: &Path, key_name: &str) -> (Keypair, PathBuf) { let path = key_dir.join(key_name); - if use_existing_key { - if let Ok(file) = File::open(path.clone()) { - println!("Using existing key at path {}", path.display()); - let keypair_bytes: Vec = serde_json::from_reader(file).unwrap(); - let keypair = Keypair::from_bytes(&keypair_bytes[..]).unwrap(); - return (keypair, path); - } + if let Ok(file) = File::open(path.clone()) { + println!("Using existing key at path {}", path.display()); + let keypair_bytes: Vec = serde_json::from_reader(file).unwrap(); + let keypair = Keypair::from_bytes(&keypair_bytes[..]).unwrap(); + return (keypair, path); } let keypair = Keypair::new(); @@ -136,8 +196,14 @@ pub(crate) fn create_and_write_keypair( (keypair, path) } -fn build_cmd(cmd: &[&str], wd: Option<&str>, env: Option<&HashMap<&str, &str>>) { +fn run_cmd(cmd: &[&str], wd: Option<&str>, env: Option<&HashMap<&str, &str>>) -> io::Result { assert!(!cmd.is_empty(), "Must specify a command!"); + if cmd.is_empty() { + return Err(io::Error::new( + io::ErrorKind::Other, + "Must specify a command!", + )); + } let mut c = Command::new(cmd[0]); c.args(&cmd[1..]); c.stdout(Stdio::inherit()); @@ -149,10 +215,6 @@ fn build_cmd(cmd: &[&str], wd: Option<&str>, env: Option<&HashMap<&str, &str>>) c.envs(env); } println!("Running command: {:?}", c); - let status = c.status().expect("Failed to run command"); - assert!( - status.success(), - "Command returned non-zero exit code: {}", - cmd.join(" ") - ); + let status = c.status()?; + Ok(status.success()) } diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index e3abedf035..72ecfcb5ca 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -3,7 +3,6 @@ use hyperlane_sealevel_mailbox::protocol_fee::ProtocolFee; use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; -use solana_sdk::signature::Signer; use solana_sdk::{compute_budget, compute_budget::ComputeBudgetInstruction}; use std::collections::HashMap; @@ -13,7 +12,7 @@ use crate::cmd_utils::get_compute_unit_price_micro_lamports_for_chain_name; use crate::ONE_SOL_IN_LAMPORTS; use crate::{ artifacts::{read_json, write_json}, - cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program}, + cmd_utils::{create_new_directory, deploy_program}, multisig_ism::deploy_multisig_ism_message_id, Context, CoreCmd, CoreDeploy, CoreSubCmd, }; @@ -89,7 +88,6 @@ pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { let ism_program_id = deploy_multisig_ism_message_id( &mut ctx, &core.built_so_dir, - core.use_existing_keys, &key_dir, core.local_domain, ); @@ -123,23 +121,18 @@ fn deploy_mailbox( default_ism: Pubkey, local_domain: u32, ) -> Pubkey { - let (keypair, keypair_path) = create_and_write_keypair( - key_dir, - "hyperlane_sealevel_mailbox-keypair.json", - core.use_existing_keys, - ); - let program_id = keypair.pubkey(); - - deploy_program( + let program_id = deploy_program( ctx.payer_keypair_path(), - keypair_path.to_str().unwrap(), + key_dir, + "hyperlane_sealevel_mailbox", core.built_so_dir .join("hyperlane_sealevel_mailbox.so") .to_str() .unwrap(), &ctx.client.url(), local_domain, - ); + ) + .unwrap(); println!("Deployed Mailbox at program ID {}", program_id); @@ -182,23 +175,18 @@ fn deploy_validator_announce( key_dir: &Path, mailbox_program_id: Pubkey, ) -> Pubkey { - let (keypair, keypair_path) = create_and_write_keypair( - key_dir, - "hyperlane_sealevel_validator_announce-keypair.json", - core.use_existing_keys, - ); - let program_id = keypair.pubkey(); - - deploy_program( + let program_id = deploy_program( ctx.payer_keypair_path(), - keypair_path.to_str().unwrap(), + key_dir, + "hyperlane_sealevel_validator_announce", core.built_so_dir .join("hyperlane_sealevel_validator_announce.so") .to_str() .unwrap(), &ctx.client.url(), core.local_domain, - ); + ) + .unwrap(); println!("Deployed ValidatorAnnounce at program ID {}", program_id); @@ -225,13 +213,6 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, instruction::{GasOracleConfig, GasOverheadConfig}, }; - let (keypair, keypair_path) = create_and_write_keypair( - key_dir, - "hyperlane_sealevel_igp-keypair.json", - core.use_existing_keys, - ); - let program_id = keypair.pubkey(); - let mut gas_oracle_configs = core .gas_oracle_config_file .as_deref() @@ -275,16 +256,18 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, .into_values() .collect::>(); - deploy_program( + let program_id = deploy_program( ctx.payer_keypair_path(), - keypair_path.to_str().unwrap(), + key_dir, + "hyperlane_sealevel_igp", core.built_so_dir .join("hyperlane_sealevel_igp.so") .to_str() .unwrap(), &ctx.client.url(), core.local_domain, - ); + ) + .unwrap(); println!("Deployed IGP at program ID {}", program_id); diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index 0d4c5c0e9e..cf9d6042b2 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -3,7 +3,7 @@ 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}, + cmd_utils::{create_new_directory, deploy_program}, read_core_program_ids, router::ChainMetadata, Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, @@ -75,7 +75,7 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { .expect("Invalid chain name"); let program_id = - deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir, local_domain); + deploy_igp_program(&mut ctx, &deploy.built_so_dir, &key_dir, local_domain); write_json::( &chain_dir.join("program-ids.json"), @@ -413,27 +413,21 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { fn deploy_igp_program( ctx: &mut Context, built_so_dir: &Path, - use_existing_keys: bool, key_dir: &Path, local_domain: u32, ) -> 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( + let program_id = deploy_program( ctx.payer_keypair_path(), - keypair_path.to_str().unwrap(), + key_dir, + "hyperlane_sealevel_igp", built_so_dir .join("hyperlane_sealevel_igp.so") .to_str() .unwrap(), &ctx.client.url(), local_domain, - ); + ) + .unwrap(); println!("Deployed IGP at program ID {}", program_id); diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index bf014785c1..4b76a7cf0a 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -184,8 +184,6 @@ struct CoreDeploy { overhead_config_file: Option, #[arg(long)] chain: String, - #[arg(long)] - use_existing_keys: bool, #[arg(long, num_args = 1.., value_delimiter = ',')] remote_domains: Vec, #[arg(long)] diff --git a/rust/sealevel/client/src/multisig_ism.rs b/rust/sealevel/client/src/multisig_ism.rs index 9f1f2c5c89..753e89c7ab 100644 --- a/rust/sealevel/client/src/multisig_ism.rs +++ b/rust/sealevel/client/src/multisig_ism.rs @@ -3,11 +3,10 @@ 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}, + cmd_utils::{create_new_directory, deploy_program}, router::ChainMetadata, Context, MultisigIsmMessageIdCmd, MultisigIsmMessageIdSubCmd, }; @@ -64,7 +63,6 @@ pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: Multisi let ism_program_id = deploy_multisig_ism_message_id( &mut ctx, &deploy.built_so_dir, - true, &key_dir, local_domain, ); @@ -168,27 +166,21 @@ pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: Multisi pub(crate) fn deploy_multisig_ism_message_id( ctx: &mut Context, built_so_dir: &Path, - use_existing_keys: bool, key_dir: &Path, local_domain: u32, ) -> 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( + let program_id = deploy_program( ctx.payer_keypair_path(), - keypair_path.to_str().unwrap(), + key_dir, + "hyperlane_sealevel_multisig_ism_message_id", built_so_dir .join("hyperlane_sealevel_multisig_ism_message_id.so") .to_str() .unwrap(), &ctx.client.url(), local_domain, - ); + ) + .unwrap(); println!( "Deployed Multisig ISM Message ID at program ID {}", diff --git a/rust/sealevel/client/src/router.rs b/rust/sealevel/client/src/router.rs index ad7ee36950..2b75ea5cae 100644 --- a/rust/sealevel/client/src/router.rs +++ b/rust/sealevel/client/src/router.rs @@ -8,7 +8,7 @@ use std::{ use solana_client::rpc_client::RpcClient; use solana_program::instruction::Instruction; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signer}; +use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use account_utils::DiscriminatorData; use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; @@ -17,7 +17,7 @@ use hyperlane_sealevel_igp::accounts::{Igp, InterchainGasPaymasterType, Overhead use crate::{ adjust_gas_price_if_needed, artifacts::{write_json, HexAndBase58ProgramIdArtifact}, - cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program_idempotent}, + cmd_utils::{create_new_directory, deploy_program}, read_core_program_ids, warp_route, Context, CoreProgramIds, }; @@ -183,17 +183,12 @@ pub(crate) trait RouterDeployer: }) }) .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(); + let chain_program_name = format!("{}-{}", program_name, chain_config.name); - deploy_program_idempotent( + let program_id = deploy_program( ctx.payer_keypair_path(), - &keypair, - keypair_path.to_str().unwrap(), + key_dir, + &chain_program_name, built_so_dir .join(format!("{}.so", program_name)) .to_str() diff --git a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token-sealeveltest2.json b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token-sealeveltest2-keypair.json similarity index 100% rename from rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token-sealeveltest2.json rename to rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token-sealeveltest2-keypair.json diff --git a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token_native-sealeveltest1.json b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token_native-sealeveltest1-keypair.json similarity index 100% rename from rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token_native-sealeveltest1.json rename to rust/sealevel/environments/local-e2e/warp-routes/testwarproute/keys/hyperlane_sealevel_token_native-sealeveltest1-keypair.json diff --git a/solidity/contracts/client/MailboxClient.sol b/solidity/contracts/client/MailboxClient.sol index 9c986a0cce..d5359f1d77 100644 --- a/solidity/contracts/client/MailboxClient.sol +++ b/solidity/contracts/client/MailboxClient.sol @@ -58,7 +58,7 @@ abstract contract MailboxClient is OwnableUpgradeable, PackageVersioned { } /** - * @notice Only accept messages from an Hyperlane Mailbox contract + * @notice Only accept messages from a Hyperlane Mailbox contract */ modifier onlyMailbox() { require( diff --git a/solidity/contracts/client/Router.sol b/solidity/contracts/client/Router.sol index 5d98f2cb9c..9ae6310dc5 100644 --- a/solidity/contracts/client/Router.sol +++ b/solidity/contracts/client/Router.sol @@ -145,7 +145,7 @@ abstract contract Router is MailboxClient, IMessageRecipient { } /** - * @notice Assert that the given domain has a Application Router registered and return its address + * @notice Assert that the given domain has an Application Router registered and return its address * @param _domain The domain of the chain for which to get the Application Router * @return _router The address of the remote Application Router on _domain */ diff --git a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol index a133de5d8d..ac5601125d 100644 --- a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol +++ b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol @@ -27,7 +27,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini /** * @title AbstractMessageIdAuthorizedIsm - * @notice Uses external verification options to verify interchain messages which need a authorized caller + * @notice Uses external verification options to verify interchain messages which need an authorized caller */ abstract contract AbstractMessageIdAuthorizedIsm is IInterchainSecurityModule, diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 3a249e154c..c8f53b25df 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -11,6 +11,7 @@ "@inquirer/core": "9.0.10", "@inquirer/figures": "1.0.5", "@inquirer/prompts": "3.3.2", + "@inquirer/search": "^3.0.1", "ansi-escapes": "^7.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/cli/src/avs/check.ts b/typescript/cli/src/avs/check.ts index 27055c6848..b2af3f7b73 100644 --- a/typescript/cli/src/avs/check.ts +++ b/typescript/cli/src/avs/check.ts @@ -6,7 +6,12 @@ import { MerkleTreeHook__factory, ValidatorAnnounce__factory, } from '@hyperlane-xyz/core'; -import { ChainMap, ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainName, + MultiProvider, + isValidValidatorStorageLocation, +} from '@hyperlane-xyz/sdk'; import { Address, ProtocolType, isObjEmpty } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; @@ -288,19 +293,18 @@ const setValidatorInfo = async ( const storageLocation = validatorStorageLocations[i]; const warnings: string[] = []; - // Skip if no storage location is found, address is not validating on this chain or if storage location string doesn't not start with s3:// - if ( - storageLocation.length === 0 || - !storageLocation[0].startsWith('s3://') - ) { + const lastStorageLocation = + storageLocation.length > 0 ? storageLocation.slice(-1)[0] : ''; + + // Skip if no storage location is found, address is not validating on this chain or if not a valid storage location + if (!isValidValidatorStorageLocation(lastStorageLocation)) { continue; } const [latestValidatorCheckpointIndex, latestCheckpointUrl] = - (await getLatestValidatorCheckpointIndexAndUrl(storageLocation[0])) ?? [ - undefined, - undefined, - ]; + (await getLatestValidatorCheckpointIndexAndUrl( + lastStorageLocation, + )) ?? [undefined, undefined]; if (!latestMerkleTreeCheckpointIndex) { warnings.push( diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts index a9c50918da..bfe0157021 100644 --- a/typescript/cli/src/avs/stakeRegistry.ts +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -136,7 +136,7 @@ async function getOperatorSignature( // random salt is ok, because we register the operator right after const salt = utils.hexZeroPad(utils.randomBytes(32), 32); - // give a expiry timestamp 1 hour from now + // give an expiry timestamp 1 hour from now const expiry = utils.hexZeroPad( utils.hexlify(Math.floor(Date.now() / 1000) + 60 * 60), 32, diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index a75f6ae61e..51801d9db0 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -35,7 +35,6 @@ import { fromAddressCommandOption, inputFileCommandOption, outputFileCommandOption, - skipConfirmationOption, } from './options.js'; /** @@ -117,7 +116,6 @@ export const deploy: CommandModuleWithWriteContext<{ ), 'dry-run': dryRunCommandOption, 'from-address': fromAddressCommandOption, - 'skip-confirmation': skipConfirmationOption, }, handler: async ({ context, chain, config: configFilePath, dryRun }) => { logCommandHeader(`Hyperlane Core deployment${dryRun ? ' dry-run' : ''}`); diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 1167b3b559..7879523a19 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -17,17 +17,13 @@ export const sendCommand: CommandModule = { }; /** - * Message command + * Base options for all message/warp send/status commands */ export const messageOptions: { [k: string]: Options } = { origin: { type: 'string', description: 'Origin chain to send message from', }, - destination: { - type: 'string', - description: 'Destination chain to send message to', - }, timeout: { type: 'number', description: 'Timeout in seconds', @@ -45,6 +41,17 @@ export const messageOptions: { [k: string]: Options } = { }, }; +/** + * Options for message/warp send command with destination chain specified + */ +export const messageSendOptions: { [k: string]: Options } = { + ...messageOptions, + destination: { + type: 'string', + description: 'Destination chain to send message to', + }, +}; + export interface MessageOptionsArgTypes { origin?: string; destination?: string; @@ -59,7 +66,7 @@ const messageCommand: CommandModuleWithWriteContext< command: 'message', describe: 'Send a test message to a remote chain', builder: { - ...messageOptions, + ...messageSendOptions, body: { type: 'string', description: 'Optional Message body', diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts index 2b4c204880..0875b64675 100644 --- a/typescript/cli/src/commands/status.ts +++ b/typescript/cli/src/commands/status.ts @@ -19,12 +19,11 @@ export const statusCommand: CommandModuleWithWriteContext< description: 'Dispatch transaction hash', }, }, - handler: async ({ context, origin, destination, id, relay, dispatchTx }) => { + handler: async ({ context, origin, id, relay, dispatchTx }) => { await checkMessageStatus({ context, dispatchTx, messageId: id, - destination, origin, selfRelay: relay, }); diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 3cd99fe6a0..5d09047617 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -40,7 +40,7 @@ import { warpCoreConfigCommandOption, warpDeploymentConfigCommandOption, } from './options.js'; -import { MessageOptionsArgTypes, messageOptions } from './send.js'; +import { MessageOptionsArgTypes, messageSendOptions } from './send.js'; /** * Parent command @@ -245,7 +245,7 @@ const send: CommandModuleWithWriteContext< command: 'send', describe: 'Send a test token transfer on a warp route', builder: { - ...messageOptions, + ...messageSendOptions, symbol: { ...symbolCommandOption, demandOption: false, diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 65536c15d7..07064b5e87 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -70,14 +70,6 @@ export async function createChainConfig({ default: name[0].toUpperCase() + name.slice(1), }); - const technicalStack = (await select({ - choices: Object.entries(ChainTechnicalStack).map(([_, value]) => ({ - value, - })), - message: 'Select the correct chain technical stack', - pageSize: 10, - })) as ChainTechnicalStack; - const chainId = parseInt( await detectAndConfirmOrPrompt( async () => { @@ -96,6 +88,30 @@ export async function createChainConfig({ 'Is this chain a testnet (a chain used for testing & development)?', }); + const technicalStack = (await select({ + choices: Object.entries(ChainTechnicalStack).map(([_, value]) => ({ + value, + })), + message: 'Select the chain technical stack', + pageSize: 10, + })) as ChainTechnicalStack; + + const arbitrumNitroMetadata: Pick = {}; + if (technicalStack === ChainTechnicalStack.ArbitrumNitro) { + const indexFrom = await detectAndConfirmOrPrompt( + async () => { + return (await provider.getBlockNumber()).toString(); + }, + `Enter`, + 'starting block number for indexing', + 'JSON RPC provider', + ); + + arbitrumNitroMetadata.index = { + from: parseInt(indexFrom), + }; + } + const metadata: ChainMetadata = { name, displayName, @@ -105,6 +121,7 @@ export async function createChainConfig({ technicalStack, rpcUrls: [{ http: rpcUrl }], isTestnet, + ...arbitrumNitroMetadata, }; await addBlockExplorerConfig(metadata); diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 5ad005dc24..1ca07a9a70 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -270,7 +270,7 @@ async function getOwnerAndBeneficiary( async function selectIgpChains(context: CommandContext) { const localChain = await runSingleChainSelectionStep( context.chainMetadata, - 'Select local chain for IGP hook', + 'Select local chain for IGP hook:', ); const isTestnet = context.chainMetadata[localChain].isTestnet; const remoteChains = await runMultiChainSelectionStep({ diff --git a/typescript/cli/src/config/strategy.ts b/typescript/cli/src/config/strategy.ts index f57c7d3378..cb2abaf437 100644 --- a/typescript/cli/src/config/strategy.ts +++ b/typescript/cli/src/config/strategy.ts @@ -10,17 +10,15 @@ import { import { ProtocolType, assert, - errorToString, isAddress, isPrivateKeyEvm, } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; -import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; import { indentYamlOrJson, - isFile, readYamlOrJson, writeYamlOrJson, } from '../utils/files.js'; @@ -33,38 +31,9 @@ export async function readChainSubmissionStrategyConfig( filePath: string, ): Promise { log(`Reading submission strategy in ${filePath}`); - try { - const strategyConfig = readYamlOrJson(filePath); - - const parseResult = ChainSubmissionStrategySchema.parse(strategyConfig); - - return parseResult; - } catch (error) { - logRed(`⛔️ Error reading strategy config:`, errorToString(error)); - throw error; // Re-throw to let caller handle the error - } -} - -/** - * Safely reads chain submission strategy config, returns empty object if any errors occur - */ -export async function safeReadChainSubmissionStrategyConfig( - filePath: string, -): Promise { - try { - const trimmedFilePath = filePath.trim(); - if (!isFile(trimmedFilePath)) { - logBlue(`File ${trimmedFilePath} does not exist, returning empty config`); - return {}; - } - return await readChainSubmissionStrategyConfig(trimmedFilePath); - } catch (error) { - logRed( - `Failed to read strategy config, defaulting to empty config:`, - errorToString(error), - ); - return {}; - } + const strategyConfig = readYamlOrJson(filePath); + const parseResult = ChainSubmissionStrategySchema.parse(strategyConfig); + return parseResult; } export async function createStrategyConfig({ diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index 39e67aab6f..55ce42cbfb 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -17,9 +17,8 @@ import { } from '@hyperlane-xyz/sdk'; import { isHttpsUrl, isNullish, rootLogger } from '@hyperlane-xyz/utils'; -import { DEFAULT_STRATEGY_CONFIG_PATH } from '../commands/options.js'; import { isSignCommand } from '../commands/signCommands.js'; -import { safeReadChainSubmissionStrategyConfig } from '../config/strategy.js'; +import { readChainSubmissionStrategyConfig } from '../config/strategy.js'; import { PROXY_DEPLOYED_URL } from '../consts.js'; import { forkNetworkToMultiProvider, verifyAnvil } from '../deploy/dry-run.js'; import { logBlue } from '../logger.js'; @@ -63,9 +62,9 @@ export async function signerMiddleware(argv: Record) { if (!requiresKey) return argv; - const strategyConfig = await safeReadChainSubmissionStrategyConfig( - strategyPath ?? DEFAULT_STRATEGY_CONFIG_PATH, - ); + const strategyConfig = strategyPath + ? await readChainSubmissionStrategyConfig(strategyPath) + : {}; /** * Intercepts Hyperlane command to determine chains. @@ -107,6 +106,7 @@ export async function getContext({ requiresKey, skipConfirmation, disableProxy = false, + strategyPath, }: ContextSettings): Promise { const registry = getRegistry(registryUri, registryOverrideUri, !disableProxy); @@ -126,6 +126,7 @@ export async function getContext({ key, skipConfirmation: !!skipConfirmation, signerAddress, + strategyPath, } as CommandContext; } diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index a36955a3f3..034a2e5136 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -25,7 +25,7 @@ export async function runKurtosisAgentDeploy({ if (!originChain) { originChain = await runSingleChainSelectionStep( context.chainMetadata, - 'Select the origin chain', + 'Select the origin chain:', ); } if (!relayChains) { diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index f7d5eb4e15..e43d08fe2c 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -33,14 +33,14 @@ export async function sendTestMessage({ if (!origin) { origin = await runSingleChainSelectionStep( chainMetadata, - 'Select the origin chain', + 'Select the origin chain:', ); } if (!destination) { destination = await runSingleChainSelectionStep( chainMetadata, - 'Select the destination chain', + 'Select the destination chain:', ); } diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 5e0472685c..e042657ff6 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -53,14 +53,14 @@ export async function sendTestTransfer({ if (!origin) { origin = await runSingleChainSelectionStep( chainMetadata, - 'Select the origin chain', + 'Select the origin chain:', ); } if (!destination) { destination = await runSingleChainSelectionStep( chainMetadata, - 'Select the destination chain', + 'Select the destination chain:', ); } diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 5e22aec7b0..a63caa4e3e 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -2,17 +2,15 @@ import type { TransactionReceipt } from '@ethersproject/providers'; import { input } from '@inquirer/prompts'; import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk'; -import { assert, parseWarpRouteMessage } from '@hyperlane-xyz/utils'; import { WriteCommandContext } from '../context/types.js'; -import { log, logBlue, logGray, logGreen, logRed } from '../logger.js'; +import { log, logBlue, logGreen, logRed } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; import { stubMerkleTreeConfig } from '../utils/relay.js'; export async function checkMessageStatus({ context, messageId, - destination, origin, selfRelay, dispatchTx, @@ -20,26 +18,19 @@ export async function checkMessageStatus({ context: WriteCommandContext; dispatchTx?: string; messageId?: string; - destination?: ChainName; origin?: ChainName; selfRelay?: boolean; }) { if (!origin) { origin = await runSingleChainSelectionStep( context.chainMetadata, - 'Select the origin chain', + 'Select the origin chain:', ); } - if (!messageId) { - messageId = await input({ - message: 'Please specify the message id', - }); - } - - const chainAddresses = await context.registry.getAddresses(); + const coreAddresses = await context.registry.getAddresses(); const core = HyperlaneCore.fromAddressesMap( - chainAddresses, + coreAddresses, context.multiProvider, ); @@ -50,6 +41,9 @@ export async function checkMessageStatus({ .getProvider(origin) .getTransactionReceipt(dispatchTx); } else { + messageId ??= await input({ + message: 'Please specify the message id', + }); try { dispatchedReceipt = await core.getDispatchTx(origin, messageId); } catch { @@ -64,48 +58,29 @@ export async function checkMessageStatus({ } } - const messages = core.getDispatchedMessages(dispatchedReceipt!); - const match = messages.find((m) => m.id === messageId); - assert(match, `Message ${messageId} not found in dispatch tx ${dispatchTx}`); - const message = match; - try { - const { amount, recipient } = parseWarpRouteMessage(message.parsed.body); - logGray(`Warping ${amount} to ${recipient}`); - // eslint-disable-next-line no-empty - } catch {} - - let deliveredTx: TransactionReceipt; - - log(`Checking status of message ${messageId} on ${destination}`); - const delivered = await core.isDelivered(message); - if (delivered) { - logGreen(`Message ${messageId} was delivered`); - deliveredTx = await core.getProcessedReceipt(message); - } else { - logBlue(`Message ${messageId} was not yet delivered`); + const messages = core.getDispatchedMessages(dispatchedReceipt); - if (!selfRelay) { - return; + const undelivered = []; + for (const message of messages) { + log( + `Checking status of message ${message.id} on ${message.parsed.destinationChain}`, + ); + const delivered = await core.isDelivered(message); + if (delivered) { + logGreen(`Message ${message.id} was delivered`); + } else { + logBlue(`Message ${message.id} was not yet delivered`); + undelivered.push(message); } + } + if (selfRelay) { const relayer = new HyperlaneRelayer({ core }); - - const hookAddress = await core.getSenderHookAddress(message); - const merkleAddress = chainAddresses[origin].merkleTreeHook; - stubMerkleTreeConfig(relayer, origin, hookAddress, merkleAddress); - - deliveredTx = await relayer.relayMessage( - dispatchedReceipt, - undefined, - message, - ); + for (const message of undelivered) { + const hookAddress = await core.getSenderHookAddress(message); + const merkleAddress = coreAddresses[origin].merkleTreeHook; + stubMerkleTreeConfig(relayer, origin, hookAddress, merkleAddress); + } + await relayer.relayAll(dispatchedReceipt, undelivered); } - - logGreen( - `Message ${messageId} delivered in ${ - context.multiProvider.tryGetExplorerTxUrl(message.parsed.destination, { - hash: deliveredTx.transactionHash, - }) ?? deliveredTx.transactionHash - }`, - ); } diff --git a/typescript/cli/src/tests/commands/core.ts b/typescript/cli/src/tests/commands/core.ts index e47db15e20..d74b310d84 100644 --- a/typescript/cli/src/tests/commands/core.ts +++ b/typescript/cli/src/tests/commands/core.ts @@ -1,11 +1,45 @@ -import { $ } from 'zx'; +import { $, ProcessPromise } from 'zx'; import { DerivedCoreConfig } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; import { readYamlOrJson } from '../../utils/files.js'; import { ANVIL_KEY, REGISTRY_PATH } from './helpers.js'; +/** + * Deploys the Hyperlane core contracts to the specified chain using the provided config. + */ +export function hyperlaneCoreDeployRaw( + coreInputPath: string, + privateKey?: string, + skipConfirmationPrompts?: boolean, + hypKey?: string, +): ProcessPromise { + if (hypKey) { + return $`HYP_KEY=${hypKey} yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ + --registry ${REGISTRY_PATH} \ + --config ${coreInputPath} \ + --verbosity debug \ + ${skipConfirmationPrompts ? '--yes' : ''}`; + } + + if (privateKey) { + return $`yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ + --registry ${REGISTRY_PATH} \ + --config ${coreInputPath} \ + --key ${privateKey} \ + --verbosity debug \ + ${skipConfirmationPrompts ? '--yes' : ''}`; + } + + return $`yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ + --registry ${REGISTRY_PATH} \ + --config ${coreInputPath} \ + --verbosity debug \ + ${skipConfirmationPrompts ? '--yes' : ''}`; +} + /** * Deploys the Hyperlane core contracts to the specified chain using the provided config. */ @@ -36,6 +70,68 @@ export async function hyperlaneCoreRead(chain: string, coreOutputPath: string) { --yes`; } +/** + * Verifies that a Hyperlane core deployment matches the provided config on the specified chain. + */ +export function hyperlaneCoreCheck( + chain: string, + coreOutputPath: string, + mailbox?: Address, +): ProcessPromise { + if (mailbox) { + return $`yarn workspace @hyperlane-xyz/cli run hyperlane core check \ + --registry ${REGISTRY_PATH} \ + --config ${coreOutputPath} \ + --chain ${chain} \ + --mailbox ${mailbox} \ + --verbosity debug \ + --yes`; + } + + return $`yarn workspace @hyperlane-xyz/cli run hyperlane core check \ + --registry ${REGISTRY_PATH} \ + --config ${coreOutputPath} \ + --chain ${chain} \ + --verbosity debug \ + --yes`; +} + +/** + * Creates a Hyperlane core deployment config + */ +export function hyperlaneCoreInit( + coreOutputPath: string, + privateKey?: string, + hyp_key?: string, +): ProcessPromise { + if (hyp_key) { + return $`${ + hyp_key ? `HYP_KEY=${hyp_key}` : '' + } yarn workspace @hyperlane-xyz/cli run hyperlane core init \ + --registry ${REGISTRY_PATH} \ + --config ${coreOutputPath} \ + --verbosity debug \ + --yes`; + } + + if (privateKey) { + return $`${ + hyp_key ? 'HYP_KEY=${hyp_key}' : '' + } yarn workspace @hyperlane-xyz/cli run hyperlane core init \ + --registry ${REGISTRY_PATH} \ + --config ${coreOutputPath} \ + --verbosity debug \ + --key ${privateKey} \ + --yes`; + } + + return $`yarn workspace @hyperlane-xyz/cli run hyperlane core init \ + --registry ${REGISTRY_PATH} \ + --config ${coreOutputPath} \ + --verbosity debug \ + --yes`; +} + /** * Updates a Hyperlane core deployment on the specified chain using the provided config. */ diff --git a/typescript/cli/src/tests/commands/helpers.ts b/typescript/cli/src/tests/commands/helpers.ts index 7d21425886..abba68aa2d 100644 --- a/typescript/cli/src/tests/commands/helpers.ts +++ b/typescript/cli/src/tests/commands/helpers.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { $ } from 'zx'; +import { $, ProcessOutput, ProcessPromise } from 'zx'; import { ERC20Test__factory, ERC4626Test__factory } from '@hyperlane-xyz/core'; import { ChainAddresses } from '@hyperlane-xyz/registry'; @@ -8,7 +8,7 @@ import { WarpCoreConfig, WarpCoreConfigSchema, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, sleep } from '@hyperlane-xyz/utils'; import { getContext } from '../../context/context.js'; import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; @@ -36,6 +36,7 @@ export const CHAIN_NAME_3 = 'anvil3'; export const EXAMPLES_PATH = './examples'; export const CORE_CONFIG_PATH = `${EXAMPLES_PATH}/core-config.yaml`; +export const CORE_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config.yaml`; export const CORE_READ_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config-read.yaml`; export const CHAIN_2_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_2}/metadata.yaml`; export const CHAIN_3_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_3}/metadata.yaml`; @@ -46,6 +47,105 @@ export const WARP_CORE_CONFIG_PATH_2 = `${REGISTRY_PATH}/deployments/warp_routes export const DEFAULT_E2E_TEST_TIMEOUT = 100_000; // Long timeout since these tests can take a while +export enum KeyBoardKeys { + ARROW_DOWN = '\x1b[B', + ARROW_UP = '\x1b[A', + ENTER = '\n', + TAB = '\t', +} + +export async function asyncStreamInputWrite( + stream: NodeJS.WritableStream, + data: string | Buffer, +): Promise { + stream.write(data); + // Adding a slight delay to allow the buffer to update the output + await sleep(500); +} + +export type TestPromptAction = { + check: (currentOutput: string) => boolean; + input: string; +}; + +/** + * Takes a {@link ProcessPromise} and a list of inputs that will be supplied + * in the provided order when the check in the {@link TestPromptAction} matches the output + * of the {@link ProcessPromise}. + */ +export async function handlePrompts( + processPromise: Readonly, + actions: TestPromptAction[], +): Promise { + let expectedStep = 0; + for await (const out of processPromise.stdout) { + const currentLine: string = out.toString(); + + const currentAction = actions[expectedStep]; + if (currentAction && currentAction.check(currentLine)) { + // Select mainnet chains + await asyncStreamInputWrite(processPromise.stdin, currentAction.input); + expectedStep++; + } + } + + return processPromise; +} + +export const SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat( + 3, +)}${KeyBoardKeys.TAB}`; + +export const SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat( + 2, +)}${KeyBoardKeys.TAB}`; + +export const SELECT_MAINNET_CHAIN_TYPE_STEP: TestPromptAction = { + check: (currentOutput: string) => + currentOutput.includes('Select network type'), + // Select mainnet chains + input: KeyBoardKeys.ENTER, +}; + +export const SELECT_ANVIL_2_AND_ANVIL_3_STEPS: ReadonlyArray = + [ + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + input: `${SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + input: `${SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER}${KeyBoardKeys.ENTER}`, + }, + ]; + +export const CONFIRM_DETECTED_OWNER_STEP: Readonly = { + check: (currentOutput: string) => + currentOutput.includes('Detected owner address as'), + input: KeyBoardKeys.ENTER, +}; + +export const SETUP_CHAIN_SIGNERS_MANUALLY_STEPS: ReadonlyArray = + [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + ]; + /** * Retrieves the deployed Warp address from the Warp core config. */ @@ -199,7 +299,11 @@ export async function getDomainId( return String(chainMetadata?.domainId); } -export async function deployToken(privateKey: string, chain: string) { +export async function deployToken( + privateKey: string, + chain: string, + decimals = 18, +) { const { multiProvider } = await getContext({ registryUri: REGISTRY_PATH, registryOverrideUri: '', @@ -211,7 +315,7 @@ export async function deployToken(privateKey: string, chain: string) { const token = await new ERC20Test__factory( multiProvider.getSigner(chain), - ).deploy('token', 'token', '100000000000000000000', 18); + ).deploy('token', 'token', '100000000000000000000', decimals); await token.deployed(); return token; diff --git a/typescript/cli/src/tests/commands/warp.ts b/typescript/cli/src/tests/commands/warp.ts index 60c5a5d9fd..86e6de5338 100644 --- a/typescript/cli/src/tests/commands/warp.ts +++ b/typescript/cli/src/tests/commands/warp.ts @@ -1,4 +1,4 @@ -import { $ } from 'zx'; +import { $, ProcessPromise } from 'zx'; import { WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; @@ -8,6 +8,17 @@ import { ANVIL_KEY, REGISTRY_PATH, getDeployedWarpAddress } from './helpers.js'; $.verbose = true; +export function hyperlaneWarpInit(warpCorePath: string): ProcessPromise { + // --overrides is " " to allow local testing to work + return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp init \ + --registry ${REGISTRY_PATH} \ + --overrides " " \ + --out ${warpCorePath} \ + --key ${ANVIL_KEY} \ + --verbosity debug \ + --yes`; +} + /** * Deploys the Warp route to the specified chain using the provided config. */ @@ -16,6 +27,7 @@ export async function hyperlaneWarpDeploy( key?: string, registryPath?: string, ) { + // --overrides is " " to allow local testing to work return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ --registry ${registryPath ?? REGISTRY_PATH} \ --overrides " " \ diff --git a/typescript/cli/src/tests/core-deploy.e2e-test.ts b/typescript/cli/src/tests/core-deploy.e2e-test.ts deleted file mode 100644 index 192f0121b4..0000000000 --- a/typescript/cli/src/tests/core-deploy.e2e-test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { expect } from 'chai'; -import { Signer, Wallet, ethers } from 'ethers'; - -import { - ChainMetadata, - CoreConfig, - ProtocolFeeHookConfig, - randomAddress, -} from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; - -import { hyperlaneCoreDeploy, readCoreConfig } from './commands/core.js'; -import { - ANVIL_KEY, - CHAIN_2_METADATA_PATH, - CHAIN_NAME_2, - CORE_CONFIG_PATH, - CORE_READ_CONFIG_PATH_2, - DEFAULT_E2E_TEST_TIMEOUT, -} from './commands/helpers.js'; - -describe('hyperlane core deploy e2e tests', async function () { - this.timeout(DEFAULT_E2E_TEST_TIMEOUT); - - let signer: Signer; - let initialOwnerAddress: Address; - - before(async () => { - const chainMetadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); - - const provider = new ethers.providers.JsonRpcProvider( - chainMetadata.rpcUrls[0].http, - ); - - const wallet = new Wallet(ANVIL_KEY); - signer = wallet.connect(provider); - - initialOwnerAddress = await signer.getAddress(); - }); - - it('should create a core deployment with the signer as the mailbox owner', async () => { - await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_CONFIG_PATH); - - const coreConfig: CoreConfig = await readCoreConfig( - CHAIN_NAME_2, - CORE_READ_CONFIG_PATH_2, - ); - - expect(coreConfig.owner).to.equal(initialOwnerAddress); - expect(coreConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); - // Assuming that the ProtocolFeeHook is used for deployment - expect((coreConfig.requiredHook as ProtocolFeeHookConfig).owner).to.equal( - initialOwnerAddress, - ); - }); - - it('should create a core deployment with the mailbox owner set to the address in the config', async () => { - const coreConfig: CoreConfig = await readYamlOrJson(CORE_CONFIG_PATH); - - const newOwner = randomAddress().toLowerCase(); - - coreConfig.owner = newOwner; - writeYamlOrJson(CORE_READ_CONFIG_PATH_2, coreConfig); - - // Deploy the core contracts with the updated mailbox owner - await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_READ_CONFIG_PATH_2); - - // Verify that the owner has been set correctly without modifying any other owner values - const updatedConfig: CoreConfig = await readCoreConfig( - CHAIN_NAME_2, - CORE_READ_CONFIG_PATH_2, - ); - - expect(updatedConfig.owner.toLowerCase()).to.equal(newOwner); - expect(updatedConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); - // Assuming that the ProtocolFeeHook is used for deployment - expect( - (updatedConfig.requiredHook as ProtocolFeeHookConfig).owner, - ).to.equal(initialOwnerAddress); - }); - - it('should create a core deployment with ProxyAdmin owner of the mailbox set to the address in the config', async () => { - const coreConfig: CoreConfig = await readYamlOrJson(CORE_CONFIG_PATH); - - const newOwner = randomAddress().toLowerCase(); - - coreConfig.proxyAdmin = { owner: newOwner }; - writeYamlOrJson(CORE_READ_CONFIG_PATH_2, coreConfig); - - // Deploy the core contracts with the updated mailbox owner - await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_READ_CONFIG_PATH_2); - - // Verify that the owner has been set correctly without modifying any other owner values - const updatedConfig: CoreConfig = await readCoreConfig( - CHAIN_NAME_2, - CORE_READ_CONFIG_PATH_2, - ); - - expect(updatedConfig.owner).to.equal(initialOwnerAddress); - expect(updatedConfig.proxyAdmin?.owner.toLowerCase()).to.equal(newOwner); - // Assuming that the ProtocolFeeHook is used for deployment - expect( - (updatedConfig.requiredHook as ProtocolFeeHookConfig).owner, - ).to.equal(initialOwnerAddress); - }); -}); diff --git a/typescript/cli/src/tests/core-apply.e2e-test.ts b/typescript/cli/src/tests/core/core-apply.e2e-test.ts similarity index 98% rename from typescript/cli/src/tests/core-apply.e2e-test.ts rename to typescript/cli/src/tests/core/core-apply.e2e-test.ts index 27667fd719..c062d5b43b 100644 --- a/typescript/cli/src/tests/core-apply.e2e-test.ts +++ b/typescript/cli/src/tests/core/core-apply.e2e-test.ts @@ -11,13 +11,12 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, Domain, addressToBytes32 } from '@hyperlane-xyz/utils'; -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; - +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; import { hyperlaneCoreApply, hyperlaneCoreDeploy, readCoreConfig, -} from './commands/core.js'; +} from '../commands/core.js'; import { ANVIL_KEY, CHAIN_2_METADATA_PATH, @@ -28,7 +27,7 @@ import { CORE_READ_CONFIG_PATH_2, DEFAULT_E2E_TEST_TIMEOUT, TEMP_PATH, -} from './commands/helpers.js'; +} from '../commands/helpers.js'; const CORE_READ_CHAIN_2_CONFIG_PATH = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config-read.yaml`; const CORE_READ_CHAIN_3_CONFIG_PATH = `${TEMP_PATH}/${CHAIN_NAME_3}/core-config-read.yaml`; diff --git a/typescript/cli/src/tests/core/core-check.e2e-test.ts b/typescript/cli/src/tests/core/core-check.e2e-test.ts new file mode 100644 index 0000000000..f1dd609914 --- /dev/null +++ b/typescript/cli/src/tests/core/core-check.e2e-test.ts @@ -0,0 +1,93 @@ +import { expect } from 'chai'; +import { $ } from 'zx'; + +import { randomAddress } from '@hyperlane-xyz/sdk'; + +import { writeYamlOrJson } from '../../utils/files.js'; +import { + hyperlaneCoreCheck, + hyperlaneCoreDeploy, + readCoreConfig, +} from '../commands/core.js'; +import { + ANVIL_KEY, + CHAIN_NAME_2, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + DEFAULT_E2E_TEST_TIMEOUT, + REGISTRY_PATH, + deployOrUseExistingCore, +} from '../commands/helpers.js'; + +describe('hyperlane core check e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + before(async () => { + await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); + }); + + it('should throw an error if the --chain param is not provided', async () => { + const wrongCommand = + $`yarn workspace @hyperlane-xyz/cli run hyperlane core check \ + --registry ${REGISTRY_PATH} \ + --config ${CORE_CONFIG_PATH} \ + --verbosity debug \ + --yes`.nothrow(); + + const output = await wrongCommand; + + expect(output.exitCode).to.equal(1); + expect(output.text().includes('Missing required argument: chain')).to.be + .true; + }); + + it('should successfully run the core check command', async () => { + await readCoreConfig(CHAIN_NAME_2, CORE_READ_CONFIG_PATH_2); + + const output = await hyperlaneCoreCheck( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + + expect(output.exitCode).to.equal(0); + expect(output.text().includes('No violations found')).to.be.true; + }); + + it('should find differences between the local and onchain config', async () => { + const coreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + coreConfig.owner = randomAddress(); + writeYamlOrJson(CORE_READ_CONFIG_PATH_2, coreConfig); + const expectedDiffText = `EXPECTED: "${coreConfig.owner}"\n`; + const expectedActualText = `ACTUAL: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"\n`; + + const output = await hyperlaneCoreCheck( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ).nothrow(); + + expect(output.exitCode).to.equal(1); + expect(output.text().includes(expectedDiffText)).to.be.true; + expect(output.text().includes(expectedActualText)).to.be.true; + }); + + it('should successfully check the config when provided with a custom mailbox', async () => { + await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_CONFIG_PATH); + const coreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + expect(coreConfig.interchainAccountRouter?.mailbox).not.to.be.undefined; + + const output = await hyperlaneCoreCheck( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + coreConfig.interchainAccountRouter!.mailbox, + ); + + expect(output.exitCode).to.equal(0); + expect(output.text().includes('No violations found')).to.be.true; + }); +}); diff --git a/typescript/cli/src/tests/core/core-deploy.e2e-test.ts b/typescript/cli/src/tests/core/core-deploy.e2e-test.ts new file mode 100644 index 0000000000..f7544fc8f7 --- /dev/null +++ b/typescript/cli/src/tests/core/core-deploy.e2e-test.ts @@ -0,0 +1,286 @@ +import { expect } from 'chai'; +import { Signer, Wallet, ethers } from 'ethers'; + +import { + ChainMetadata, + CoreConfig, + HookType, + ProtocolFeeHookConfig, + randomAddress, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; +import { + hyperlaneCoreDeploy, + hyperlaneCoreDeployRaw, + readCoreConfig, +} from '../commands/core.js'; +import { + ANVIL_KEY, + CHAIN_2_METADATA_PATH, + CHAIN_NAME_2, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + SELECT_MAINNET_CHAIN_TYPE_STEP, + SETUP_CHAIN_SIGNERS_MANUALLY_STEPS, + TestPromptAction, + handlePrompts, +} from '../commands/helpers.js'; + +describe('hyperlane core deploy e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + let signer: Signer; + let initialOwnerAddress: Address; + + before(async () => { + const chainMetadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); + + const provider = new ethers.providers.JsonRpcProvider( + chainMetadata.rpcUrls[0].http, + ); + + const wallet = new Wallet(ANVIL_KEY); + signer = wallet.connect(provider); + + initialOwnerAddress = await signer.getAddress(); + }); + + describe('hyperlane core deploy', () => { + it('should create a core deployment with the signer as the mailbox owner', async () => { + const steps: TestPromptAction[] = [ + ...SETUP_CHAIN_SIGNERS_MANUALLY_STEPS, + SELECT_MAINNET_CHAIN_TYPE_STEP, + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + // Scroll down through the mainnet chains list and select anvil2 + input: `${KeyBoardKeys.ARROW_DOWN.repeat(2)}}${KeyBoardKeys.ENTER}`, + }, + { + // When running locally the e2e tests, the chains folder might already have the chain contracts + check: (currentOutput) => + currentOutput.includes('Mailbox already exists at') || + currentOutput.includes('Is this deployment plan correct?'), + input: `yes${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneCoreDeployRaw(CORE_CONFIG_PATH).stdio('pipe'); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const coreConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + expect(coreConfig.owner).to.equal(initialOwnerAddress); + expect(coreConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); + // Assuming that the ProtocolFeeHook is used for deployment + const requiredHookConfig = coreConfig.requiredHook as Exclude< + CoreConfig['requiredHook'], + string + >; + expect(requiredHookConfig.type).to.equal(HookType.PROTOCOL_FEE); + expect((requiredHookConfig as ProtocolFeeHookConfig).owner).to.equal( + initialOwnerAddress, + ); + }); + }); + + describe('hyperlane core deploy --yes', () => { + it('should fail if the --chain flag is not provided but the --yes flag is', async () => { + const steps: TestPromptAction[] = [...SETUP_CHAIN_SIGNERS_MANUALLY_STEPS]; + + const output = hyperlaneCoreDeployRaw(CORE_CONFIG_PATH, undefined, true) + .nothrow() + .stdio('pipe'); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text().includes('No chain provided')).to.be.true; + }); + }); + + describe('hyperlane core deploy --key ...', () => { + it('should create a core deployment with the signer as the mailbox owner', async () => { + const steps: TestPromptAction[] = [ + SELECT_MAINNET_CHAIN_TYPE_STEP, + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + // Scroll down through the mainnet chains list and select anvil2 + input: `${KeyBoardKeys.ARROW_DOWN.repeat(2)}}${KeyBoardKeys.ENTER}`, + }, + { + // When running locally the e2e tests, the chains folder might already have the chain contracts + check: (currentOutput) => + currentOutput.includes('Mailbox already exists at') || + currentOutput.includes('Is this deployment plan correct?'), + input: `yes${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneCoreDeployRaw(CORE_CONFIG_PATH, ANVIL_KEY).stdio( + 'pipe', + ); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const coreConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + expect(coreConfig.owner).to.equal(initialOwnerAddress); + expect(coreConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); + // Assuming that the ProtocolFeeHook is used for deployment + const requiredHookConfig = coreConfig.requiredHook as Exclude< + CoreConfig['requiredHook'], + string + >; + expect(requiredHookConfig.type).to.equal(HookType.PROTOCOL_FEE); + expect((requiredHookConfig as ProtocolFeeHookConfig).owner).to.equal( + initialOwnerAddress, + ); + }); + }); + + describe('HYP_KEY= ... hyperlane core deploy', () => { + it('should create a core deployment with the signer as the mailbox owner', async () => { + const steps: TestPromptAction[] = [ + SELECT_MAINNET_CHAIN_TYPE_STEP, + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + // Scroll down through the mainnet chains list and select anvil2 + input: `${KeyBoardKeys.ARROW_DOWN.repeat(2)}}${KeyBoardKeys.ENTER}`, + }, + { + // When running locally the e2e tests, the chains folder might already have the chain contracts + check: (currentOutput) => + currentOutput.includes('Mailbox already exists at') || + currentOutput.includes('Is this deployment plan correct?'), + input: `yes${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneCoreDeployRaw( + CORE_CONFIG_PATH, + undefined, + undefined, + ANVIL_KEY, + ).stdio('pipe'); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const coreConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + expect(coreConfig.owner).to.equal(initialOwnerAddress); + expect(coreConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); + // Assuming that the ProtocolFeeHook is used for deployment + const requiredHookConfig = coreConfig.requiredHook as Exclude< + CoreConfig['requiredHook'], + string + >; + expect(requiredHookConfig.type).to.equal(HookType.PROTOCOL_FEE); + expect((requiredHookConfig as ProtocolFeeHookConfig).owner).to.equal( + initialOwnerAddress, + ); + }); + }); + + describe('hyperlane core deploy --yes --key ...', () => { + it('should create a core deployment with the signer as the mailbox owner', async () => { + await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_CONFIG_PATH); + + const coreConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + + expect(coreConfig.owner).to.equal(initialOwnerAddress); + expect(coreConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); + // Assuming that the ProtocolFeeHook is used for deployment + expect((coreConfig.requiredHook as ProtocolFeeHookConfig).owner).to.equal( + initialOwnerAddress, + ); + }); + + it('should create a core deployment with the mailbox owner set to the address in the config', async () => { + const coreConfig: CoreConfig = await readYamlOrJson(CORE_CONFIG_PATH); + + const newOwner = randomAddress().toLowerCase(); + + coreConfig.owner = newOwner; + writeYamlOrJson(CORE_READ_CONFIG_PATH_2, coreConfig); + + // Deploy the core contracts with the updated mailbox owner + await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_READ_CONFIG_PATH_2); + + // Verify that the owner has been set correctly without modifying any other owner values + const updatedConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + + expect(updatedConfig.owner.toLowerCase()).to.equal(newOwner); + expect(updatedConfig.proxyAdmin?.owner).to.equal(initialOwnerAddress); + // Assuming that the ProtocolFeeHook is used for deployment + expect( + (updatedConfig.requiredHook as ProtocolFeeHookConfig).owner, + ).to.equal(initialOwnerAddress); + }); + + it('should create a core deployment with ProxyAdmin owner of the mailbox set to the address in the config', async () => { + const coreConfig: CoreConfig = await readYamlOrJson(CORE_CONFIG_PATH); + + const newOwner = randomAddress().toLowerCase(); + + coreConfig.proxyAdmin = { owner: newOwner }; + writeYamlOrJson(CORE_READ_CONFIG_PATH_2, coreConfig); + + // Deploy the core contracts with the updated mailbox owner + await hyperlaneCoreDeploy(CHAIN_NAME_2, CORE_READ_CONFIG_PATH_2); + + // Verify that the owner has been set correctly without modifying any other owner values + const updatedConfig: CoreConfig = await readCoreConfig( + CHAIN_NAME_2, + CORE_READ_CONFIG_PATH_2, + ); + + expect(updatedConfig.owner).to.equal(initialOwnerAddress); + expect(updatedConfig.proxyAdmin?.owner.toLowerCase()).to.equal(newOwner); + // Assuming that the ProtocolFeeHook is used for deployment + expect( + (updatedConfig.requiredHook as ProtocolFeeHookConfig).owner, + ).to.equal(initialOwnerAddress); + }); + }); +}); diff --git a/typescript/cli/src/tests/core/core-init.e2e-test.ts b/typescript/cli/src/tests/core/core-init.e2e-test.ts new file mode 100644 index 0000000000..5c308df4a5 --- /dev/null +++ b/typescript/cli/src/tests/core/core-init.e2e-test.ts @@ -0,0 +1,216 @@ +import { expect } from 'chai'; +import { Wallet } from 'ethers'; + +import { + CoreConfig, + HookType, + MerkleTreeHookConfig, + ProtocolFeeHookConfig, + randomAddress, +} from '@hyperlane-xyz/sdk'; +import { Address, normalizeAddress } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson } from '../../utils/files.js'; +import { hyperlaneCoreInit } from '../commands/core.js'; +import { + ANVIL_KEY, + CONFIRM_DETECTED_OWNER_STEP, + CORE_CONFIG_PATH_2, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + TestPromptAction, + handlePrompts, +} from '../commands/helpers.js'; + +describe('hyperlane core init e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + function assertCoreInitConfig( + coreConfig: CoreConfig, + owner: Address, + feeHookOwner: Address = owner, + feeHookBeneficiary: Address = feeHookOwner, + ): void { + expect(coreConfig.owner).to.equal(owner); + expect(coreConfig.proxyAdmin?.owner).to.equal(owner); + + const defaultHookConfig = coreConfig.defaultHook as MerkleTreeHookConfig; + expect(defaultHookConfig.type).to.equal(HookType.MERKLE_TREE); + + const requiredHookConfig = coreConfig.requiredHook as ProtocolFeeHookConfig; + expect(requiredHookConfig.type).to.equal(HookType.PROTOCOL_FEE); + expect(normalizeAddress(requiredHookConfig.owner)).to.equal(feeHookOwner); + expect(normalizeAddress(requiredHookConfig.beneficiary)).to.equal( + feeHookBeneficiary, + ); + } + + describe('hyperlane core init', () => { + it('should successfully generate the core contract deployment config', async () => { + const output = hyperlaneCoreInit(CORE_CONFIG_PATH_2).stdio('pipe'); + + const owner = normalizeAddress(randomAddress()); + const feeHookOwner = normalizeAddress(randomAddress()); + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Enter the desired owner address:'), + input: `${owner}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes( + 'For trusted relayer ISM, enter relayer address:', + ), + input: `${owner}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes( + 'For Protocol Fee Hook, enter owner address:', + ), + input: `${feeHookOwner}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + !!currentOutput.match(/Use this same address \((.*?)\) for/), + input: KeyBoardKeys.ENTER, + }, + ]; + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const deploymentCoreConfig: CoreConfig = + readYamlOrJson(CORE_CONFIG_PATH_2); + assertCoreInitConfig(deploymentCoreConfig, owner, feeHookOwner); + }); + }); + + describe('HYP_KEY=... hyperlane core init', () => { + it('should successfully generate the core contract deployment config when confirming owner prompts', async () => { + const owner = new Wallet(ANVIL_KEY).address; + const steps: TestPromptAction[] = [ + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput) => + !!currentOutput.match(/Use this same address \((.*?)\) for/), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneCoreInit( + CORE_CONFIG_PATH_2, + undefined, + ANVIL_KEY, + ).stdio('pipe'); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const deploymentCoreConfig: CoreConfig = + readYamlOrJson(CORE_CONFIG_PATH_2); + assertCoreInitConfig(deploymentCoreConfig, owner); + }); + + it('should successfully generate the core contract deployment config when not confirming owner prompts', async () => { + const owner = new Wallet(ANVIL_KEY).address; + const feeHookOwner = normalizeAddress(randomAddress()); + const steps: TestPromptAction[] = [ + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput) => + !!currentOutput.match(/Use this same address \((.*?)\) for/), + input: `no${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Enter beneficiary address for'), + input: `${feeHookOwner}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneCoreInit( + CORE_CONFIG_PATH_2, + undefined, + ANVIL_KEY, + ).stdio('pipe'); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const deploymentCoreConfig: CoreConfig = + readYamlOrJson(CORE_CONFIG_PATH_2); + assertCoreInitConfig( + deploymentCoreConfig, + owner, + undefined, + feeHookOwner, + ); + }); + }); + + describe('hyperlane core init --key ...', () => { + it('should successfully generate the core contract deployment config when confirming owner prompts', async () => { + const owner = new Wallet(ANVIL_KEY).address; + const steps: TestPromptAction[] = [ + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput) => + !!currentOutput.match(/Use this same address \((.*?)\) for/), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneCoreInit(CORE_CONFIG_PATH_2, ANVIL_KEY).stdio( + 'pipe', + ); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const deploymentCoreConfig: CoreConfig = + readYamlOrJson(CORE_CONFIG_PATH_2); + assertCoreInitConfig(deploymentCoreConfig, owner); + }); + + it('should successfully generate the core contract deployment config when not confirming owner prompts', async () => { + const owner = new Wallet(ANVIL_KEY).address; + const feeHookOwner = normalizeAddress(randomAddress()); + const steps: TestPromptAction[] = [ + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput) => + !!currentOutput.match(/Use this same address \((.*?)\) for/), + input: `no${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Enter beneficiary address for'), + input: `${feeHookOwner}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneCoreInit(CORE_CONFIG_PATH_2, ANVIL_KEY).stdio( + 'pipe', + ); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const deploymentCoreConfig: CoreConfig = + readYamlOrJson(CORE_CONFIG_PATH_2); + assertCoreInitConfig( + deploymentCoreConfig, + owner, + undefined, + feeHookOwner, + ); + }); + }); +}); diff --git a/typescript/cli/src/tests/core-read.e2e-test.ts b/typescript/cli/src/tests/core/core-read.e2e-test.ts similarity index 90% rename from typescript/cli/src/tests/core-read.e2e-test.ts rename to typescript/cli/src/tests/core/core-read.e2e-test.ts index 7390e8dca4..9ed0a3ca01 100644 --- a/typescript/cli/src/tests/core-read.e2e-test.ts +++ b/typescript/cli/src/tests/core/core-read.e2e-test.ts @@ -8,9 +8,8 @@ import { } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; -import { readYamlOrJson } from '../utils/files.js'; - -import { hyperlaneCoreDeploy, readCoreConfig } from './commands/core.js'; +import { readYamlOrJson } from '../../utils/files.js'; +import { hyperlaneCoreDeploy, readCoreConfig } from '../commands/core.js'; import { ANVIL_KEY, CHAIN_2_METADATA_PATH, @@ -18,7 +17,7 @@ import { CORE_CONFIG_PATH, CORE_READ_CONFIG_PATH_2, DEFAULT_E2E_TEST_TIMEOUT, -} from './commands/helpers.js'; +} from '../commands/helpers.js'; describe('hyperlane core read e2e tests', async function () { this.timeout(DEFAULT_E2E_TEST_TIMEOUT); diff --git a/typescript/cli/src/tests/warp-init.e2e-test.ts b/typescript/cli/src/tests/warp-init.e2e-test.ts new file mode 100644 index 0000000000..66cfa3bfc7 --- /dev/null +++ b/typescript/cli/src/tests/warp-init.e2e-test.ts @@ -0,0 +1,182 @@ +import { expect } from 'chai'; +import { Wallet } from 'ethers'; + +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainMap, + ChainName, + TokenType, + WarpRouteDeployConfig, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson } from '../utils/files.js'; + +import { + ANVIL_KEY, + CHAIN_NAME_2, + CHAIN_NAME_3, + CONFIRM_DETECTED_OWNER_STEP, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + SELECT_ANVIL_2_AND_ANVIL_3_STEPS, + SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER, + SELECT_MAINNET_CHAIN_TYPE_STEP, + TestPromptAction, + WARP_CONFIG_PATH_2, + deployOrUseExistingCore, + deployToken, + handlePrompts, +} from './commands/helpers.js'; +import { hyperlaneWarpInit } from './commands/warp.js'; + +describe('hyperlane warp init e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + let initialOwnerAddress: Address; + let chainMapAddresses: ChainMap = {}; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + chainMapAddresses = { + [CHAIN_NAME_2]: chain2Addresses, + [CHAIN_NAME_3]: chain3Addresses, + }; + + const wallet = new Wallet(ANVIL_KEY); + initialOwnerAddress = wallet.address; + }); + + describe('hyperlane warp init --yes', () => { + function assertWarpConfig( + warpConfig: WarpRouteDeployConfig, + chainMapAddresses: ChainMap, + chainName: ChainName, + ) { + expect(warpConfig[chainName]).not.to.be.undefined; + + const chain2TokenConfig = warpConfig[chainName]; + expect(chain2TokenConfig.mailbox).equal( + chainMapAddresses[chainName].mailbox, + ); + expect(chain2TokenConfig.owner).equal(initialOwnerAddress); + expect(chain2TokenConfig.type).equal(TokenType.native); + } + + it('it should generate a warp deploy config with a single chain', async function () { + const steps: TestPromptAction[] = [ + SELECT_MAINNET_CHAIN_TYPE_STEP, + { + check: (currentOutput: string) => + currentOutput.includes('--Mainnet Chains--'), + // Scroll down through the mainnet chains list and select anvil2 + input: `${SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER}${KeyBoardKeys.ENTER}`, + }, + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput: string) => + !!currentOutput.match(/Select .+?'s token type/), + // Scroll up through the token type list and select native + input: `${KeyBoardKeys.ARROW_UP.repeat(2)}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpInit(WARP_CONFIG_PATH_2).stdio('pipe'); + + await handlePrompts(output, steps); + + const warpConfig: WarpRouteDeployConfig = + readYamlOrJson(WARP_CONFIG_PATH_2); + + assertWarpConfig(warpConfig, chainMapAddresses, CHAIN_NAME_2); + }); + + it('it should generate a warp deploy config with a 2 chains warp route (native->native)', async function () { + const steps: TestPromptAction[] = [ + SELECT_MAINNET_CHAIN_TYPE_STEP, + ...SELECT_ANVIL_2_AND_ANVIL_3_STEPS, + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput: string) => + !!currentOutput.match(/Select .+?'s token type/), + input: `${KeyBoardKeys.ARROW_UP.repeat(2)}${KeyBoardKeys.ENTER}`, + }, + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput: string) => + !!currentOutput.match(/Select .+?'s token type/), + input: `${KeyBoardKeys.ARROW_UP.repeat(2)}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpInit(WARP_CONFIG_PATH_2).stdio('pipe'); + + await handlePrompts(output, steps); + + const warpConfig: WarpRouteDeployConfig = + readYamlOrJson(WARP_CONFIG_PATH_2); + + [CHAIN_NAME_2, CHAIN_NAME_3].map((chainName) => + assertWarpConfig(warpConfig, chainMapAddresses, chainName), + ); + }); + + it('it should generate a warp deploy config with a 2 chains warp route (collateral->synthetic)', async function () { + const erc20Token = await deployToken(ANVIL_KEY, CHAIN_NAME_2, 6); + const steps: TestPromptAction[] = [ + SELECT_MAINNET_CHAIN_TYPE_STEP, + ...SELECT_ANVIL_2_AND_ANVIL_3_STEPS, + // First chain token config + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput: string) => + !!currentOutput.match(/Select .+?'s token type/), + // Scroll down through the token type list and select collateral + input: `${KeyBoardKeys.ARROW_DOWN.repeat(4)}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes('Enter the existing token address on chain'), + input: `${erc20Token.address}${KeyBoardKeys.ENTER}`, + }, + // Other chain token config + CONFIRM_DETECTED_OWNER_STEP, + { + check: (currentOutput: string) => + !!currentOutput.match(/Select .+?'s token type/), + // Select the synthetic token type + input: `${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpInit(WARP_CONFIG_PATH_2).stdio('pipe'); + + await handlePrompts(output, steps); + + const warpConfig: WarpRouteDeployConfig = + readYamlOrJson(WARP_CONFIG_PATH_2); + + expect(warpConfig[CHAIN_NAME_2]).not.to.be.undefined; + + const chain2TokenConfig = warpConfig[CHAIN_NAME_2]; + expect(chain2TokenConfig.mailbox).equal(chain2Addresses.mailbox); + expect(chain2TokenConfig.owner).equal(initialOwnerAddress); + expect(chain2TokenConfig.type).equal(TokenType.collateral); + expect((chain2TokenConfig as any).token).equal(erc20Token.address); + + expect(warpConfig[CHAIN_NAME_3]).not.to.be.undefined; + + const chain3TokenConfig = warpConfig[CHAIN_NAME_3]; + expect(chain3TokenConfig.mailbox).equal(chain3Addresses.mailbox); + expect(chain3TokenConfig.owner).equal(initialOwnerAddress); + expect(chain3TokenConfig.type).equal(TokenType.synthetic); + }); + }); +}); diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 7e2eaccd0a..7f2f88fad8 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -1,4 +1,5 @@ import { Separator, confirm } from '@inquirer/prompts'; +import search from '@inquirer/search'; import select from '@inquirer/select'; import chalk from 'chalk'; @@ -23,9 +24,21 @@ export async function runSingleChainSelectionStep( chainMetadata, networkType, ); - const chain = (await select({ - message, - choices: [networkTypeSeparator, ...choices], + + const formattedMessage = message.endsWith(':') ? message : `${message}:`; + const options = [networkTypeSeparator, ...choices]; + const chain = (await search({ + message: formattedMessage, + source: (searchTerm) => { + if (!searchTerm) { + return options; + } + + return options.filter( + (value) => + Separator.isSeparator(value) || value.value.includes(searchTerm), + ); + }, pageSize: calculatePageSize(2), })) as string; handleNewChain([chain]); @@ -142,7 +155,9 @@ function getChainChoices( networkType: 'mainnet' | 'testnet', ) { const chainsToChoices = (chains: ChainMetadata[]) => - chains.map((c) => ({ name: c.name, value: c.name })); + chains + .map((c) => ({ name: c.name, value: c.name })) + .sort((a, b) => a.name.localeCompare(b.name)); const chains = Object.values(chainMetadata); const filteredChains = chains.filter((c) => diff --git a/typescript/cli/src/validator/utils.ts b/typescript/cli/src/validator/utils.ts index c96aa66125..f1218c2b1b 100644 --- a/typescript/cli/src/validator/utils.ts +++ b/typescript/cli/src/validator/utils.ts @@ -1,5 +1,6 @@ import { MerkleTreeHook, ValidatorAnnounce } from '@hyperlane-xyz/core'; -import { S3Validator } from '@hyperlane-xyz/sdk'; +import { getValidatorFromStorageLocation } from '@hyperlane-xyz/sdk'; +import { BaseValidator } from '@hyperlane-xyz/utils'; import { logDebug } from '../logger.js'; @@ -36,23 +37,23 @@ export const getValidatorStorageLocations = async ( }; export const getLatestValidatorCheckpointIndexAndUrl = async ( - s3StorageLocation: string, + storageLocation: string, ): Promise<[number, string] | undefined> => { - let s3Validator: S3Validator; + let validator: BaseValidator; try { - s3Validator = await S3Validator.fromStorageLocation(s3StorageLocation); + validator = await getValidatorFromStorageLocation(storageLocation); } catch (err) { logDebug( - `Failed to instantiate S3Validator at location ${s3StorageLocation}: ${err}`, + `Failed to instantiate Validator at location ${storageLocation}: ${err}`, ); return undefined; } try { - const latestCheckpointIndex = await s3Validator.getLatestCheckpointIndex(); - return [latestCheckpointIndex, s3Validator.getLatestCheckpointUrl()]; + const latestCheckpointIndex = await validator.getLatestCheckpointIndex(); + return [latestCheckpointIndex, validator.getLatestCheckpointUrl()]; } catch (err) { logDebug( - `Failed to get latest checkpoint index from S3Validator at location ${s3StorageLocation}: ${err}`, + `Failed to get latest checkpoint index from Validator at location ${storageLocation}: ${err}`, ); return undefined; } diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 29366ebcf0..f886bd802b 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -1,4 +1,10 @@ import { + AgentSealevelHeliusFeeLevel, + AgentSealevelPriorityFeeOracle, + AgentSealevelPriorityFeeOracleType, + AgentSealevelTransactionSubmitter, + AgentSealevelTransactionSubmitterType, + ChainName, GasPaymentEnforcement, GasPaymentEnforcementPolicyType, MatchingList, @@ -7,6 +13,7 @@ import { import { AgentChainConfig, + HELIUS_SECRET_URL_MARKER, RootAgentConfig, getAgentChainNamesFromConfig, } from '../../../src/config/agent/agent.js'; @@ -396,6 +403,56 @@ export const hyperlaneContextAgentChainNames = getAgentChainNamesFromConfig( mainnet3SupportedChainNames, ); +const sealevelPriorityFeeOracleConfigGetter = ( + chain: ChainName, +): AgentSealevelPriorityFeeOracle => { + // Special case for Solana mainnet + if (chain === 'solanamainnet') { + return { + type: AgentSealevelPriorityFeeOracleType.Helius, + feeLevel: AgentSealevelHeliusFeeLevel.Recommended, + // URL is auto populated by the external secrets in the helm chart + url: '', + }; + } else if (chain === 'eclipsemainnet') { + // As of Dec 23: + // Eclipse has recently seen some increased usage with their referral program, + // and we have had intermittent issues landing txs. Not many txs on Eclipse use + // priority fees, so we use a low priority fee. + return { + type: AgentSealevelPriorityFeeOracleType.Constant, + // 2000 micro lamports of ETH, which at a compute unit limit of 400K + // and an ETH price of $3450 (Dec 23, 2024) comes to about $0.00276 USD: + // >>> (((2000 / 1e6) * 400000) / 1e9) * 3450 + // 0.00276 + fee: '2000', + }; + } + + // For all other chains, we use the constant fee oracle with a fee of 0 + return { + type: AgentSealevelPriorityFeeOracleType.Constant, + fee: '0', + }; +}; + +const sealevelTransactionSubmitterConfigGetter = ( + chain: ChainName, +): AgentSealevelTransactionSubmitter => { + // Special case for Solana mainnet + if (chain === 'solanamainnet') { + return { + type: AgentSealevelTransactionSubmitterType.Rpc, + url: HELIUS_SECRET_URL_MARKER, + }; + } + + // For all other chains, use the default RPC transaction submitter + return { + type: AgentSealevelTransactionSubmitterType.Rpc, + }; +}; + const contextBase = { namespace: environment, runEnv: environment, @@ -403,6 +460,10 @@ const contextBase = { aws: { region: 'us-east-1', }, + sealevel: { + priorityFeeOracleConfigGetter: sealevelPriorityFeeOracleConfigGetter, + transactionSubmitterConfigGetter: sealevelTransactionSubmitterConfigGetter, + }, } as const; const gasPaymentEnforcement: GasPaymentEnforcement[] = [ @@ -412,6 +473,8 @@ const gasPaymentEnforcement: GasPaymentEnforcement[] = [ matchingList: [ // Temporary workaround due to funky Mantle gas amounts. { destinationDomain: getDomainId('mantle') }, + // Temporary workaround for some high gas amount estimates on Treasure + ...warpRouteMatchingList(WarpRouteIds.ArbitrumTreasureMAGIC), ], }, { @@ -480,21 +543,21 @@ const metricAppContextsGetter = (): MetricAppContext[] => { const relayerResources = { requests: { cpu: '14000m', - memory: '15Gi', + memory: '20G', }, }; const validatorResources = { requests: { cpu: '500m', - memory: '1Gi', + memory: '1G', }, }; const scraperResources = { requests: { cpu: '2000m', - memory: '4Gi', + memory: '4G', }, }; @@ -530,6 +593,19 @@ const blacklistedMessageIds = [ // MAGIC/ethereum-treasure native funding txs '0x9d51f4123be816cbaeef2e2b34a5760f633a7cb8a019fe16f88a3227cc22451e', '0x663c221137028ceeeb102a98e48b362a7b48d626b93c88c7fdf1871a948b1223', + + // txs between unenrolled routers of + // ETH/arbitrum-base-blast-bsc-ethereum-gnosis-lisk-mantle-mode-optimism-polygon-scroll-zeronetwork-zoramainnet + '0x229a832dfdfa23dfc27eb773e6b34e87f329067393f4f7b616251b3d7d52d294', + '0xcdfd5294e8b1253263908e1919d27675f80a2e9a3bb339b759810efdbb81faa5', + + // txs between unenrolled routers of + // USDT/arbitrum-ethereum-mantle-mode-polygon-scroll-zeronetwork + '0x10159bf1b5b2142b882cb060d1da9f9123d82974ca265ba432138221e52c2a27', + + // test tx when route was first deployed, no merkle tree insertion + // USDC/ethereum-inevm + '0x998746dc822dc15332b8683fb8a29aec22ed3e2f2fb8245c40f56303c5cb6032', ]; // Blacklist matching list intended to be used by all contexts. @@ -546,7 +622,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '7c0c967-20241218-173053', + tag: 'f73dcc3-20241229-154524', }, blacklist, gasPaymentEnforcement: gasPaymentEnforcement, @@ -566,7 +642,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'd84d8da-20241217-172447', + tag: '3812453-20241224-020703', }, resources: scraperResources, }, @@ -581,7 +657,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '7c0c967-20241218-173053', + tag: '234704d-20241226-192528', }, blacklist, // We're temporarily (ab)using the RC relayer as a way to increase @@ -615,7 +691,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '25a927d-20241114-171323', + tag: '234704d-20241226-192528', }, blacklist, gasPaymentEnforcement, diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 62c5dffdad..7cdc82abd0 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -18,14 +18,6 @@ export const chainMetadataOverrides: ChainMap> = { gasPrice: 3 * 10 ** 9, // 3 gwei }, }, - polygon: { - transactionOverrides: { - // A very high max fee per gas is used as Polygon is susceptible - // to large swings in gas prices. - maxFeePerGas: 800 * 10 ** 9, // 800 gwei - maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei - }, - }, polygonzkevm: { transactionOverrides: { gasPrice: 1 * 10 ** 9, // 1 gwei @@ -91,6 +83,14 @@ export const chainMetadataOverrides: ChainMap> = { // maxPriorityFeePerGas: 10 * 10 ** 9, // 10 gwei // }, // }, + // polygon: { + // transactionOverrides: { + // // A very high max fee per gas is used as Polygon is susceptible + // // to large swings in gas prices. + // maxFeePerGas: 800 * 10 ** 9, // 800 gwei + // maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei + // }, + // }, }; export const getRegistry = async ( diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index 81d1251723..f1fea75213 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -46,7 +46,7 @@ export const keyFunderConfig: KeyFunderConfig< boba: '0.05', bsc: '5', bsquared: '0.002', - celo: '3', + celo: '4', cheesechain: '50', chilizmainnet: '200', conflux: '100', @@ -90,14 +90,14 @@ export const keyFunderConfig: KeyFunderConfig< mode: '0.2', molten: '3', moonbeam: '100', - morph: '0.05', + morph: '0.1', oortmainnet: '2000', optimism: '0.5', orderly: '0.05', polygon: '40', polygonzkevm: '0.5', polynomialfi: '0.05', - prom: '5', + prom: '18', proofofplay: '0.05', rarichain: '0.05', real: '0.1', @@ -119,7 +119,7 @@ export const keyFunderConfig: KeyFunderConfig< taiko: '0.2', tangle: '2', telos: '100', - treasure: '800', + treasure: '900', unichain: '0.05', // temporarily low until we're able to fund more vana: '0.001', diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index b260dfee48..c9698725ad 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -132,7 +132,7 @@ "decimals": 9 }, "ethereum": { - "amount": "32.059172476", + "amount": "10.0", "decimals": 9 }, "everclear": { diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 2b388afdd2..832b3e231b 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -1,6 +1,7 @@ import { ChainMap, ChainName, + ChainTechnicalStack, HookType, IgpConfig, getTokenExchangeRateFromValues, @@ -14,6 +15,7 @@ import { getOverhead, } from '../../../src/config/gas-oracle.js'; import { mustGetChainNativeToken } from '../../../src/utils/utils.js'; +import { getChain } from '../../registry.js'; import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; @@ -25,9 +27,15 @@ const tokenPrices: ChainMap = rawTokenPrices; export function getOverheadWithOverrides(local: ChainName, remote: ChainName) { let overhead = getOverhead(local, remote, ethereumChainNames); + // Moonbeam gas usage can be up to 4x higher than vanilla EVM if (remote === 'moonbeam') { overhead *= 4; } + // ZkSync gas usage is different from the EVM and tends to give high + // estimates. We double the overhead to help account for this. + if (getChain(remote).technicalStack === ChainTechnicalStack.ZKSync) { + overhead *= 2; + } return overhead; } diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getAppchainBaseUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getAppchainBaseUSDCWarpConfig.ts new file mode 100644 index 0000000000..fce2c5c6ae --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getAppchainBaseUSDCWarpConfig.ts @@ -0,0 +1,45 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + HypTokenRouterConfig, + IsmConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { + RouterConfigWithoutOwner, + tokens, +} from '../../../../../src/config/warp.js'; + +const safeOwners: ChainMap

= { + appchain: '0xe3436b3335fa6d4f1b58153079FB360c6Aa83Fd9', + base: '0xE3b50a565fbcdb6CC67B30bEB112f9e7FC855359', +}; + +export const getAppChainBaseUSDCWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const ISM_CONFIG: IsmConfig = ethers.constants.AddressZero; // Use the default ISM + + const appchain: HypTokenRouterConfig = { + mailbox: routerConfig.appchain.mailbox, + owner: safeOwners.appchain, + type: TokenType.synthetic, + interchainSecurityModule: ISM_CONFIG, + }; + + const base: HypTokenRouterConfig = { + mailbox: routerConfig.base.mailbox, + owner: safeOwners.base, + type: TokenType.collateral, + token: tokens.base.USDC, + interchainSecurityModule: ISM_CONFIG, + }; + + return { + appchain, + base, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.ts new file mode 100644 index 0000000000..fcd48c686c --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.ts @@ -0,0 +1,54 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + HypTokenRouterConfig, + IsmConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { + RouterConfigWithoutOwner, + tokens, +} from '../../../../../src/config/warp.js'; + +const safeOwners: ChainMap
= { + bsquared: '0x7A363efD42305BeDBA307d25351F8ea157b69A1A', + swell: '0xC11e22A31787394950B31e2DEb1d2b5546689B65', + boba: '0x207FfFa7325fC5d0362aB01605D84B268b61888f', +}; + +export const getBobaBsquaredSwellUBTCWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const ISM_CONFIG: IsmConfig = ethers.constants.AddressZero; // Use the default ISM + + const boba: HypTokenRouterConfig = { + mailbox: routerConfig.boba.mailbox, + owner: safeOwners.boba, + type: TokenType.synthetic, + interchainSecurityModule: ISM_CONFIG, + }; + + const bsquared: HypTokenRouterConfig = { + mailbox: routerConfig.bsquared.mailbox, + owner: safeOwners.bsquared, + type: TokenType.collateral, + token: tokens.bsquared.uBTC, + interchainSecurityModule: ISM_CONFIG, + }; + + const swell: HypTokenRouterConfig = { + mailbox: routerConfig.swell.mailbox, + owner: safeOwners.swell, + type: TokenType.synthetic, + interchainSecurityModule: ISM_CONFIG, + }; + + return { + boba, + bsquared, + swell, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts index 73c3a5655d..cb90ba42a8 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts @@ -15,6 +15,7 @@ import { // Elixir const owner = '0x00000000F51340906F767C6999Fe512b1275955C'; +const elixirSafe = '0x738744237b7fd97af670d9ddf54390c24263cea8'; const ownerConfig = getOwnerConfigForAddress(owner); export const getEthereumSeiFastUSDWarpConfig = async ( @@ -34,7 +35,10 @@ export const getEthereumSeiFastUSDWarpConfig = async ( const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...ownerConfig, + owner: elixirSafe, + ownerOverrides: { + proxyAdmin: owner, + }, type: TokenType.collateral, token: tokens.ethereum.deUSD, interchainSecurityModule: ethers.constants.AddressZero, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts new file mode 100644 index 0000000000..9d073230a8 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts @@ -0,0 +1,42 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + HypTokenRouterConfig, + OwnableConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; +import { + RouterConfigWithoutOwner, + tokens, +} from '../../../../../src/config/warp.js'; + +const ethereumOwner = '0x77A0545Dc1Dc6bAee8d9c1d436c6688a75Ae5777'; +const seiOwner = '0x14A359aE2446eaC89495b3F28b7a29cE2A17f392'; + +export const getEthereumSeiPumpBTCWarpConfig = async ( + routerConfig: ChainMap, + _abacusWorksEnvOwnerConfig: ChainMap, +): Promise> => { + const ethereum: HypTokenRouterConfig = { + ...routerConfig.ethereum, + ...getOwnerConfigForAddress(ethereumOwner), + type: TokenType.collateral, + token: tokens.ethereum.pumpBTCsei, + interchainSecurityModule: ethers.constants.AddressZero, + }; + + const sei: HypTokenRouterConfig = { + ...routerConfig.sei, + ...getOwnerConfigForAddress(seiOwner), + type: TokenType.synthetic, + interchainSecurityModule: ethers.constants.AddressZero, + }; + + return { + ethereum, + sei, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 2dc43bbae2..58d8c7912e 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -14,6 +14,8 @@ export enum WarpRouteIds { EclipseEthereumWeETHs = 'weETHs/eclipsemainnet-ethereum', EclipseSolanaEzSOL = 'EZSOL/eclipsemainnet-solanamainnet', EclipseSolanaORCA = 'ORCA/eclipsemainnet-solanamainnet', + EclipseSolanaJitoSOL = 'jitoSOL/eclipsemainnet-solanamainnet', + EclipseSolanaKySOL = 'kySOL/eclipsemainnet-solanamainnet', EclipseSolanaSOL = 'SOL/eclipsemainnet-solanamainnet', EclipseSolanaWIF = 'WIF/eclipsemainnet-solanamainnet', EclipseStrideSTTIA = 'stTIA/eclipsemainnet-stride', @@ -22,6 +24,7 @@ export enum WarpRouteIds { EthereumInevmUSDC = 'USDC/ethereum-inevm', EthereumInevmUSDT = 'USDT/ethereum-inevm', EthereumSeiFastUSD = 'FASTUSD/ethereum-sei', + EthereumSeiPumpBTC = 'pumpBTCsei/ethereum-sei', EthereumVanaETH = 'ETH/ethereum-vana', EthereumVanaVANA = 'VANA/ethereum-vana', EthereumVictionETH = 'ETH/ethereum-viction', @@ -36,4 +39,6 @@ export enum WarpRouteIds { ArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDT = 'USDT/arbitrum-ethereum-mantle-mode-polygon-scroll-zeronetwork', ArbitrumBaseEthereumLiskOptimismPolygonZeroNetworkUSDC = 'USDC/arbitrum-base-ethereum-lisk-optimism-polygon-zeronetwork', ArbitrumBaseBlastBscEthereumGnosisLiskMantleModeOptimismPolygonScrollZeroNetworkZoraMainnet = 'ETH/arbitrum-base-blast-bsc-ethereum-gnosis-lisk-mantle-mode-optimism-polygon-scroll-zeronetwork-zoramainnet', + AppchainBaseUSDC = 'USDC/appchain-base', + BobaBsquaredSwellUBTC = 'UBTC/boba-bsquared-swell', } diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 9f01e26466..d87a010be1 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -13,6 +13,7 @@ import { import { RouterConfigWithoutOwner } from '../src/config/warp.js'; import { getAncient8EthereumUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getAncient8EthereumUSDCWarpConfig.js'; +import { getAppChainBaseUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getAppchainBaseUSDCWarpConfig.js'; import { getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig.js'; import { getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDC } from './environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDCWarpConfig.js'; import { getArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBscEthereumMantleModePolygonScrollZeronetworkUSDTWarpConfig.js'; @@ -20,6 +21,7 @@ import { getArbitrumEthereumZircuitAmphrETHWarpConfig } from './environments/mai import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.js'; import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; import { getBaseZeroNetworkCBBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseZeroNetworkCBBTCWarpConfig.js'; +import { getBobaBsquaredSwellUBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.js'; import { getEclipseEthereumApxEthWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.js'; import { getEclipseEthereumSolanaUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumSolanaUSDTWarpConfig.js'; import { getEclipseEthereumWBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumWBTCWarpConfig.js'; @@ -31,6 +33,7 @@ import { getEthereumFlowCbBTCWarpConfig } from './environments/mainnet3/warp/con import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; import { getEthereumSeiFastUSDWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.js'; +import { getEthereumSeiPumpBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.js'; import { getEthereumVictionETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.js'; import { getEthereumVictionUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.js'; import { getEthereumVictionUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.js'; @@ -59,6 +62,7 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, [WarpRouteIds.EthereumFlowCbBTC]: getEthereumFlowCbBTCWarpConfig, [WarpRouteIds.EthereumSeiFastUSD]: getEthereumSeiFastUSDWarpConfig, + [WarpRouteIds.EthereumSeiPumpBTC]: getEthereumSeiPumpBTCWarpConfig, [WarpRouteIds.EthereumVictionETH]: getEthereumVictionETHWarpConfig, [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, @@ -79,6 +83,8 @@ export const warpConfigGetterMap: Record = { getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig, [WarpRouteIds.EclipseStrideTIA]: getEclipseStrideTiaWarpConfig, [WarpRouteIds.EclipseStrideSTTIA]: getEclipseStrideStTiaWarpConfig, + [WarpRouteIds.AppchainBaseUSDC]: getAppChainBaseUSDCWarpConfig, + [WarpRouteIds.BobaBsquaredSwellUBTC]: getBobaBsquaredSwellUBTCWarpConfig, [WarpRouteIds.EthereumZircuitRe7LRT]: getEthereumZircuitRe7LRTWarpConfig, }; @@ -93,7 +99,11 @@ export async function getWarpConfig( ); // Strip the owners from the router config const routerConfigWithoutOwner = objMap(routerConfig, (_chain, config) => { - const { owner, ownerOverrides, ...configWithoutOwner } = config; + const { + owner: _owner, + ownerOverrides: _ownerOverrides, + ...configWithoutOwner + } = config; return configWithoutOwner; }); // Isolate the owners from the router config diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index e60d137f95..1832889cac 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -261,6 +261,13 @@ export function withConcurrentDeploy(args: Argv) { .default('concurrentDeploy', false); } +export function withConcurrency(args: Argv) { + return args + .describe('concurrency', 'Number of concurrent deploys') + .number('concurrency') + .default('concurrency', 1); +} + export function withRpcUrls(args: Argv) { return args .describe( diff --git a/typescript/infra/scripts/agents/utils.ts b/typescript/infra/scripts/agents/utils.ts index fa3a1da4ce..9621705a00 100644 --- a/typescript/infra/scripts/agents/utils.ts +++ b/typescript/infra/scripts/agents/utils.ts @@ -1,3 +1,5 @@ +import { concurrentMap } from '@hyperlane-xyz/utils'; + import { AgentHelmManager, RelayerHelmManager, @@ -7,12 +9,13 @@ import { import { RootAgentConfig } from '../../src/config/agent/agent.js'; import { EnvironmentConfig } from '../../src/config/environment.js'; import { Role } from '../../src/roles.js'; -import { HelmCommand } from '../../src/utils/helm.js'; +import { HelmCommand, HelmManager } from '../../src/utils/helm.js'; import { assertCorrectKubeContext, getArgs, withAgentRolesRequired, withChains, + withConcurrency, withContext, } from '../agent-utils.js'; import { getConfigsBasedOnArgs } from '../core-utils.js'; @@ -24,6 +27,7 @@ export class AgentCli { initialized = false; dryRun = false; chains?: string[]; + concurrency = 1; public async runHelmCommand(command: HelmCommand) { await this.init(); @@ -63,15 +67,20 @@ export class AgentCli { console.log('Dry run values:\n', JSON.stringify(values, null, 2)); } - for (const m of Object.values(managers)) { - await m.runHelmCommand(command, this.dryRun); - } + await concurrentMap( + this.concurrency, + Object.entries(managers), + async ([key, manager]) => { + console.log(`Running helm command for ${key}`); + await manager.runHelmCommand(command, { dryRun: this.dryRun }); + }, + ); } protected async init() { if (this.initialized) return; - const argv = await withChains( - withAgentRolesRequired(withContext(getArgs())), + const argv = await withConcurrency( + withChains(withAgentRolesRequired(withContext(getArgs()))), ) .describe('dry-run', 'Run through the steps without making any changes') .boolean('dry-run').argv; @@ -92,5 +101,6 @@ export class AgentCli { this.dryRun = argv.dryRun || false; this.initialized = true; this.chains = argv.chains; + this.concurrency = argv.concurrency; } } diff --git a/typescript/infra/scripts/check/check-validator-version.ts b/typescript/infra/scripts/check/check-validator-version.ts index ee6992d88a..f87f1d32fd 100644 --- a/typescript/infra/scripts/check/check-validator-version.ts +++ b/typescript/infra/scripts/check/check-validator-version.ts @@ -36,12 +36,9 @@ const acceptableValidatorVersions: Record = { 'a64af8be9a76120d0cfc727bb70660fa07e70cce': '1.0.0-beta', // 1.0.0 'ffbe1dd82e2452dbc111b6fb469a34fb870da8f1': '1.0.0', - // Tessellated's Build from November 2024 - // https://github.com/Tessellated-io/hyperlane-monorepo/commit/9b855686d3e2b3d6b81238ce51a576ff5e0f770f - '9b855686d3e2b3d6b81238ce51a576ff5e0f770f': 'Tesselated - November 2024', - // Tesselated's Build from December 2024 - // https://github.com/Tessellated-io/hyperlane-monorepo/commit/2bd17da1b30725a6d741197914f5c4b2ae25b566 - '2bd17da1b30725a6d741197914f5c4b2ae25b566': 'Tesselated - December 2024', + // Tessellated's Build from December 2024 + // https://github.com/Tessellated-io/hyperlane-monorepo/commit/c0719c7f5387c4801a9dec8edc1227fb33b44ea7 + 'c0719c7f5387c4801a9dec8edc1227fb33b44ea7': 'Tessellated - December 2024', }; type ValidatorInfo = { diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 729dd6a4fd..2d95e72d37 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -127,7 +127,7 @@ export async function getHelloWorldMultiProtocolApp( ); // TODO we need a MultiProtocolIgp - // Using an standard IGP for just evm chains for now + // Using a standard IGP for just evm chains for now // Unfortunately this requires hacking surgically around certain addresses const filteredAddresses = filterChainMapToProtocol( envAddresses, diff --git a/typescript/infra/scripts/warp-routes/monitor/metrics.ts b/typescript/infra/scripts/warp-routes/monitor/metrics.ts index 52c2604f96..4879415f60 100644 --- a/typescript/infra/scripts/warp-routes/monitor/metrics.ts +++ b/typescript/infra/scripts/warp-routes/monitor/metrics.ts @@ -59,6 +59,7 @@ export function updateTokenBalanceMetrics( warpCore: WarpCore, token: Token, balanceInfo: WarpRouteBalance, + collateralTokenSymbol: string, ) { const metrics: WarpRouteMetrics = { chain_name: token.chainName, @@ -67,7 +68,7 @@ export function updateTokenBalanceMetrics( wallet_address: token.addressOrDenom, token_standard: token.standard, warp_route_id: createWarpRouteConfigId( - token.symbol, + collateralTokenSymbol, warpCore.getTokenChains(), ), related_chain_names: warpCore diff --git a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts index 4eecc1f736..c04ed7418c 100644 --- a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts @@ -89,12 +89,18 @@ async function pollAndUpdateWarpRouteMetrics( chainMetadata, apiKey: await getCoinGeckoApiKey(), }); + const collateralTokenSymbol = getWarpRouteCollateralTokenSymbol(warpCore); while (true) { await tryFn(async () => { await Promise.all( warpCore.tokens.map((token) => - updateTokenMetrics(warpCore, token, tokenPriceGetter), + updateTokenMetrics( + warpCore, + token, + tokenPriceGetter, + collateralTokenSymbol, + ), ), ); }, 'Updating warp route metrics'); @@ -107,6 +113,7 @@ async function updateTokenMetrics( warpCore: WarpCore, token: Token, tokenPriceGetter: CoinGeckoTokenPriceGetter, + collateralTokenSymbol: string, ) { const promises = [ tryFn(async () => { @@ -118,7 +125,12 @@ async function updateTokenMetrics( if (!balanceInfo) { return; } - updateTokenBalanceMetrics(warpCore, token, balanceInfo); + updateTokenBalanceMetrics( + warpCore, + token, + balanceInfo, + collateralTokenSymbol, + ); }, 'Getting bridged balance and value'), ]; @@ -310,6 +322,41 @@ async function getCoinGeckoApiKey(): Promise { return apiKey; } +function getWarpRouteCollateralTokenSymbol(warpCore: WarpCore): string { + // We need to have a deterministic way to determine the symbol of the warp route + // as its used to identify the warp route in metrics. This method should support routes where: + // - All tokens have the same symbol, token standards can be all collateral, all synthetic or a mix + // - All tokens have different symbol, but there is a collateral token to break the tie, where there are multiple collateral tokens, alphabetically first is chosen + // - All tokens have different symbol, but there is no collateral token to break the tie, pick the alphabetically first symbol + + // Get all unique symbols from the tokens array + const uniqueSymbols = new Set(warpCore.tokens.map((token) => token.symbol)); + + // If all tokens have the same symbol, return that symbol + if (uniqueSymbols.size === 1) { + return warpCore.tokens[0].symbol; + } + + // Find all collateralized tokens + const collateralTokens = warpCore.tokens.filter( + (token) => + token.isCollateralized() || + token.standard === TokenStandard.EvmHypXERC20Lockbox, + ); + + if (collateralTokens.length === 0) { + // If there are no collateralized tokens, return the alphabetically first symbol + return [...uniqueSymbols].sort()[0]; + } + + // if there is a single unique collateral symbol return it or + // ifthere are multiple, return the alphabetically first symbol + const collateralSymbols = collateralTokens.map((token) => token.symbol); + const uniqueCollateralSymbols = [...new Set(collateralSymbols)]; + + return uniqueCollateralSymbols.sort()[0]; +} + main().catch((err) => { logger.error('Error in main:', err); process.exit(1); diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index b7be0e0098..24fccbac07 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -1,8 +1,14 @@ import fs from 'fs'; import { join } from 'path'; -import { ChainName, RelayerConfig, RpcConsensusType } from '@hyperlane-xyz/sdk'; -import { objOmitKeys } from '@hyperlane-xyz/utils'; +import { + AgentSealevelPriorityFeeOracle, + AgentSealevelTransactionSubmitter, + ChainName, + RelayerConfig, + RpcConsensusType, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType, objOmitKeys } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { getChain } from '../../config/registry.js'; @@ -87,12 +93,33 @@ export abstract class AgentHelmManager extends HelmManager if (reorgPeriod === undefined) { throw new Error(`No reorg period found for chain ${chain}`); } + + let priorityFeeOracle: AgentSealevelPriorityFeeOracle | undefined; + if (getChain(chain).protocol === ProtocolType.Sealevel) { + priorityFeeOracle = + this.config.rawConfig.sealevel?.priorityFeeOracleConfigGetter?.( + chain, + ); + } + + let transactionSubmitter: + | AgentSealevelTransactionSubmitter + | undefined; + if (getChain(chain).protocol === ProtocolType.Sealevel) { + transactionSubmitter = + this.config.rawConfig.sealevel?.transactionSubmitterConfigGetter?.( + chain, + ); + } + return { name: chain, rpcConsensusType: this.rpcConsensusType(chain), protocol: metadata.protocol, blocks: { reorgPeriod }, maxBatchSize: 32, + priorityFeeOracle, + transactionSubmitter, }; }), }, diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index 987a05f0ea..7d2dbe54b6 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -1,5 +1,7 @@ import { AgentChainMetadata, + AgentSealevelPriorityFeeOracle, + AgentSealevelTransactionSubmitter, AgentSignerAwsKey, AgentSignerKeyType, ChainName, @@ -85,8 +87,21 @@ export interface AgentContextConfig extends AgentEnvConfig { rolesWithKeys: Role[]; // Names of chains this context cares about (subset of environmentChainNames) contextChainNames: AgentChainNames; + sealevel?: SealevelAgentConfig; } +export interface SealevelAgentConfig { + priorityFeeOracleConfigGetter?: ( + chain: ChainName, + ) => AgentSealevelPriorityFeeOracle; + transactionSubmitterConfigGetter?: ( + chain: ChainName, + ) => AgentSealevelTransactionSubmitter; +} + +// An ugly way to mark a URL as a the secret Helius URL when Helm templating +export const HELIUS_SECRET_URL_MARKER = 'helius'; + // incomplete common agent configuration for a role interface AgentRoleConfig { // K8s-specific diff --git a/typescript/infra/src/config/warp.ts b/typescript/infra/src/config/warp.ts index f07a5e8fb6..3a0ddb4110 100644 --- a/typescript/infra/src/config/warp.ts +++ b/typescript/infra/src/config/warp.ts @@ -12,6 +12,7 @@ export const tokens: ChainMap> = { USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', WBTC: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', weETHs: '0x917cee801a67f933f2e6b33fc0cd1ed2d5909d88', + pumpBTCsei: '0xe9ebd666954B7F0B5B044704c86B126651f6235d', Re7LRT: '0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a', }, sei: { @@ -21,6 +22,9 @@ export const tokens: ChainMap> = { cbBTC: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', }, + bsquared: { + uBTC: '0x796e4D53067FF374B89b2Ac101ce0c1f72ccaAc2', + }, arbitrum: { USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', diff --git a/typescript/infra/src/helloworld/kathy.ts b/typescript/infra/src/helloworld/kathy.ts index 47dafc851c..dbb8aded61 100644 --- a/typescript/infra/src/helloworld/kathy.ts +++ b/typescript/infra/src/helloworld/kathy.ts @@ -15,6 +15,7 @@ import { import { Role } from '../roles.js'; import { HelmCommand, + HelmCommandOptions, HelmManager, HelmValues, helmifyValues, @@ -90,7 +91,10 @@ export class KathyHelmManager extends HelmManager { }; } - async runHelmCommand(action: HelmCommand, dryRun?: boolean): Promise { + async runHelmCommand( + action: HelmCommand, + options?: HelmCommandOptions, + ): Promise { // If using AWS keys, ensure the Kathy user and key has been created if (this.agentConfig.aws) { const awsUser = new AgentAwsUser( @@ -112,6 +116,6 @@ export class KathyHelmManager extends HelmManager { ); await kathyKey.createIfNotExists(); - super.runHelmCommand(action, dryRun); + await super.runHelmCommand(action, options); } } diff --git a/typescript/infra/src/utils/helm.ts b/typescript/infra/src/utils/helm.ts index 855393473a..4bd23f1047 100644 --- a/typescript/infra/src/utils/helm.ts +++ b/typescript/infra/src/utils/helm.ts @@ -13,12 +13,13 @@ export enum HelmCommand { } export function helmifyValues(config: any, prefix?: string): string[] { + if (config === null || config === undefined) { + return []; + } + if (typeof config !== 'object') { // Helm incorrectly splits on unescaped commas. - const value = - config !== undefined - ? JSON.stringify(config).replaceAll(',', '\\,') - : undefined; + const value = JSON.stringify(config).replaceAll(',', '\\,'); return [`--set ${prefix}=${value}`]; } @@ -89,8 +90,17 @@ export function getDeployableHelmChartName(helmChartConfig: HelmChartConfig) { return helmChartConfig.name; } -export function buildHelmChartDependencies(chartPath: string) { - return execCmd(`cd ${chartPath} && helm dependency build`, {}, false, true); +export function buildHelmChartDependencies( + chartPath: string, + updateRepoCache: boolean, +) { + const flags = updateRepoCache ? '' : '--skip-refresh'; + return execCmd( + `cd ${chartPath} && helm dependency build ${flags}`, + {}, + false, + true, + ); } // Convenience function to remove a helm release without having a HelmManger for it. @@ -100,6 +110,11 @@ export function removeHelmRelease(releaseName: string, namespace: string) { export type HelmValues = Record; +export interface HelmCommandOptions { + dryRun?: boolean; + updateRepoCache?: boolean; +} + export abstract class HelmManager { abstract readonly helmReleaseName: string; abstract readonly helmChartPath: string; @@ -111,7 +126,13 @@ export abstract class HelmManager { */ abstract helmValues(): Promise; - async runHelmCommand(action: HelmCommand, dryRun?: boolean): Promise { + async runHelmCommand( + action: HelmCommand, + options?: HelmCommandOptions, + ): Promise { + const dryRun = options?.dryRun ?? false; + const updateRepoCache = options?.updateRepoCache ?? false; + const cmd = ['helm', action]; if (dryRun) cmd.push('--dry-run'); @@ -141,7 +162,7 @@ export abstract class HelmManager { } } - await buildHelmChartDependencies(this.helmChartPath); + await buildHelmChartDependencies(this.helmChartPath, updateRepoCache); cmd.push( this.helmReleaseName, this.helmChartPath, diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index 80c2f2353b..4c7ddf60ef 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -28,7 +28,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '7e520fb-20241215-234731', + tag: 'de11906-20241227-214834', }, warpRouteId: this.warpRouteId, fullnameOverride: this.helmReleaseName, diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 43940105db..5511ea3653 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -14,6 +14,7 @@ import { ProtocolType, addBufferToGasLimit, addressToBytes32, + assert, bytes32ToAddress, isZeroishAddress, messageId, @@ -322,7 +323,7 @@ export class HyperlaneCore extends HyperlaneApp { message: DispatchedMessage, ): Promise { const destinationChain = this.getDestination(message); - const mailbox = this.contractsMap[destinationChain].mailbox; + const mailbox = this.getContracts(destinationChain).mailbox; const processedBlock = await mailbox.processedAt(message.id); const events = await mailbox.queryFilter( @@ -330,6 +331,11 @@ export class HyperlaneCore extends HyperlaneApp { processedBlock, processedBlock, ); + + assert( + events.length === 1, + `Expected exactly one process event, got ${events.length}`, + ); const processedEvent = events[0]; return processedEvent.getTransactionReceipt(); } @@ -437,6 +443,8 @@ export class HyperlaneCore extends HyperlaneApp { if (matching.length === 0) { throw new Error(`No dispatch event found for message ${messageId}`); } + + assert(matching.length === 1, 'Multiple dispatch events found'); const event = matching[0]; // only 1 event per message ID return event.getTransactionReceipt(); } diff --git a/typescript/sdk/src/core/HyperlaneRelayer.ts b/typescript/sdk/src/core/HyperlaneRelayer.ts index 9b82f90cbb..c36a37aef3 100644 --- a/typescript/sdk/src/core/HyperlaneRelayer.ts +++ b/typescript/sdk/src/core/HyperlaneRelayer.ts @@ -207,6 +207,38 @@ export class HyperlaneRelayer { return this.getIsmConfig(destinationChain, ism, message); } + async relayAll( + dispatchTx: providers.TransactionReceipt, + messages = HyperlaneCore.getDispatchedMessages(dispatchTx), + ): Promise> { + const destinationMap: ChainMap = {}; + messages.forEach((message) => { + destinationMap[message.parsed.destination] ??= []; + destinationMap[message.parsed.destination].push(message); + }); + + // parallelize relaying to different destinations + return promiseObjAll( + objMap(destinationMap, async (_destination, messages) => { + const receipts: ethers.ContractReceipt[] = []; + // serially relay messages to the same destination + for (const message of messages) { + try { + const receipt = await this.relayMessage( + dispatchTx, + undefined, + message, + ); + receipts.push(receipt); + } catch (e) { + this.logger.error(`Failed to relay message ${message.id}, ${e}`); + } + } + return receipts; + }), + ); + } + async relayMessage( dispatchTx: providers.TransactionReceipt, messageIndex = 0, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index a2af7df614..4b238b56e0 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -19,12 +19,12 @@ export { } from './consts/multisigIsm.js'; export { SEALEVEL_SPL_NOOP_ADDRESS } from './consts/sealevel.js'; export { - TestChainName, multiProtocolTestChainMetadata, test1, test2, test3, testChainMetadata, + TestChainName, testChains, testCosmosChain, testSealevelChain, @@ -210,18 +210,24 @@ export { AgentCosmosGasPrice, AgentLogFormat, AgentLogLevel, + AgentSealevelChainMetadata, + AgentSealevelHeliusFeeLevel, + AgentSealevelPriorityFeeOracle, + AgentSealevelPriorityFeeOracleType, + AgentSealevelTransactionSubmitter, + AgentSealevelTransactionSubmitterType, AgentSigner, AgentSignerAwsKey, AgentSignerHexKey, AgentSignerKeyType, AgentSignerNode, + buildAgentConfig, GasPaymentEnforcement, GasPaymentEnforcementPolicyType, RelayerConfig, RpcConsensusType, ScraperConfig, ValidatorConfig, - buildAgentConfig, } from './metadata/agentConfig.js'; export { ChainMetadataManager, @@ -237,15 +243,15 @@ export { EthJsonRpcBlockParameterTag, ExplorerFamily, ExplorerFamilyValue, - NativeToken, - RpcUrl, - RpcUrlSchema, getChainIdNumber, getDomainId, getReorgPeriod, isValidChainMetadata, mergeChainMetadata, mergeChainMetadataMap, + NativeToken, + RpcUrl, + RpcUrlSchema, } from './metadata/chainMetadataTypes.js'; export { ZChainName, ZHash } from './metadata/customZodTypes.js'; export { @@ -300,9 +306,6 @@ export { MultiProviderOptions, } from './providers/MultiProvider.js'; export { - ProviderBuilderFn, - ProviderBuilderMap, - TypedProviderBuilderFn, defaultEthersV5ProviderBuilder, defaultFuelProviderBuilder, defaultProviderBuilder, @@ -310,6 +313,9 @@ export { defaultSolProviderBuilder, defaultViemProviderBuilder, protocolToDefaultProviderBuilder, + ProviderBuilderFn, + ProviderBuilderMap, + TypedProviderBuilderFn, } from './providers/providerBuilders.js'; export { CosmJsContract, @@ -349,9 +355,9 @@ export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneE export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js'; export { AllProviderMethods, + excludeProviderMethods, IProviderMethods, ProviderMethod, - excludeProviderMethods, } from './providers/SmartProvider/ProviderMethods.js'; export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js'; export { @@ -413,13 +419,14 @@ export { export { ChainGasOracleParams, GasPriceConfig, - NativeTokenPriceConfig, getCosmosChainGasPrice, getGasPrice, getLocalStorageGasOracleConfig, getTokenExchangeRateFromValues, + NativeTokenPriceConfig, } from './gas/utils.js'; export { GcpValidator } from './gcp/validator.js'; +export { EvmHookModule } from './hook/EvmHookModule.js'; export { DerivedIcaRouterConfig, DerivedIcaRouterConfigSchema, @@ -459,17 +466,17 @@ export { MailboxClientConfig, MailboxClientConfigSchema, ProxiedFactories, + proxiedFactories, ProxiedRouterConfig, RemoteRouters, RouterAddress, RouterConfig, RouterViolation, RouterViolationType, - proxiedFactories, } from './router/types.js'; export { - CosmIbcToWarpTokenAdapter, CosmIbcTokenAdapter, + CosmIbcToWarpTokenAdapter, CosmNativeTokenAdapter, } from './token/adapters/CosmosTokenAdapter.js'; export { @@ -492,8 +499,8 @@ export { export { IHypTokenAdapter, IHypXERC20Adapter, - ITokenAdapter, InterchainGasQuote, + ITokenAdapter, TransferParams, TransferRemoteParams, } from './token/adapters/ITokenAdapter.js'; @@ -506,9 +513,9 @@ export { SealevelTokenAdapter, } from './token/adapters/SealevelTokenAdapter.js'; export { - SealevelHypTokenInstruction, SealevelHyperlaneTokenData, SealevelHyperlaneTokenDataSchema, + SealevelHypTokenInstruction, SealevelTransferRemoteInstruction, SealevelTransferRemoteSchema, } from './token/adapters/serialization.js'; @@ -516,11 +523,11 @@ export { HypERC20App } from './token/app.js'; export { HypERC20Checker } from './token/checker.js'; export { TokenType } from './token/config.js'; export { + hypERC20contracts, HypERC20Factories, + hypERC20factories, HypERC721Factories, TokenFactories, - hypERC20contracts, - hypERC20factories, } from './token/contracts.js'; export { HypERC20Deployer, HypERC721Deployer } from './token/deploy.js'; export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js'; @@ -529,16 +536,17 @@ export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken.js'; export { Token } from './token/Token.js'; export { TokenAmount } from './token/TokenAmount.js'; export { + getTokenConnectionId, HyperlaneTokenConnection, IbcToHyperlaneTokenConnection, IbcTokenConnection, + parseTokenConnectionId, TokenConnection, TokenConnectionConfigSchema, TokenConnectionType, - getTokenConnectionId, - parseTokenConnectionId, } from './token/TokenConnection.js'; export { + MINT_LIMITED_STANDARDS, PROTOCOL_TO_NATIVE_STANDARD, TOKEN_COLLATERALIZED_STANDARDS, TOKEN_COSMWASM_STANDARDS, @@ -546,8 +554,10 @@ export { TOKEN_MULTI_CHAIN_STANDARDS, TOKEN_NFT_STANDARDS, TOKEN_STANDARD_TO_PROTOCOL, + TOKEN_STANDARD_TO_PROVIDER_TYPE, TOKEN_TYPE_TO_STANDARD, TokenStandard, + XERC20_STANDARDS, } from './token/TokenStandard.js'; export { CollateralRebaseTokenConfigSchema, @@ -557,6 +567,12 @@ export { HypTokenConfigSchema, HypTokenRouterConfig, HypTokenRouterConfigSchema, + isCollateralRebaseTokenConfig, + isCollateralTokenConfig, + isNativeTokenConfig, + isSyntheticRebaseTokenConfig, + isSyntheticTokenConfig, + isTokenMetadata, NativeTokenConfig, NativeTokenConfigSchema, SyntheticRebaseTokenConfig, @@ -568,12 +584,6 @@ export { WarpRouteDeployConfig, WarpRouteDeployConfigSchema, WarpRouteDeployConfigSchemaErrors, - isCollateralRebaseTokenConfig, - isCollateralTokenConfig, - isNativeTokenConfig, - isSyntheticRebaseTokenConfig, - isSyntheticTokenConfig, - isTokenMetadata, } from './token/types.js'; export { ChainMap, @@ -611,12 +621,15 @@ export { multisigIsmVerificationCost, normalizeConfig } from './utils/ism.js'; export { MultiGeneric } from './utils/MultiGeneric.js'; export { isCompliant, validateZodResult } from './utils/schemas.js'; export { + getSealevelAccountDataSchema, SealevelAccountDataWrapper, SealevelInstructionWrapper, - getSealevelAccountDataSchema, } from './utils/sealevelSerialization.js'; export { getChainIdFromTxs } from './utils/transactions.js'; -export { getValidatorFromStorageLocation } from './utils/validator.js'; +export { + getValidatorFromStorageLocation, + isValidValidatorStorageLocation, +} from './utils/validator.js'; export { FeeConstantConfig, RouteBlacklist, @@ -626,7 +639,5 @@ export { WarpTypedTransaction, } from './warp/types.js'; export { WarpCore, WarpCoreOptions } from './warp/WarpCore.js'; - export { MailboxClientConfigSchema as mailboxClientConfigSchema } from './router/types.js'; export { shouldSkipStaticDeployment } from './deploy/protocolDeploymentConfig.js'; -export { EvmHookModule } from './hook/EvmHookModule.js'; diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index cb570e29eb..9ff999c0dc 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -51,6 +51,26 @@ export enum AgentSignerKeyType { Cosmos = 'cosmosKey', } +export enum AgentSealevelPriorityFeeOracleType { + Helius = 'helius', + Constant = 'constant', +} + +export enum AgentSealevelHeliusFeeLevel { + Recommended = 'recommended', + Min = 'min', + Low = 'low', + Medium = 'medium', + High = 'high', + VeryHigh = 'veryHigh', + UnsafeMax = 'unsafeMax', +} + +export enum AgentSealevelTransactionSubmitterType { + Rpc = 'rpc', + Jito = 'jito', +} + const AgentSignerHexKeySchema = z .object({ type: z.literal(AgentSignerKeyType.Hex).optional(), @@ -120,6 +140,40 @@ export type AgentCosmosGasPrice = z.infer< typeof AgentCosmosChainMetadataSchema >['gasPrice']; +const AgentSealevelChainMetadataSchema = z.object({ + priorityFeeOracle: z + .union([ + z.object({ + type: z.literal(AgentSealevelPriorityFeeOracleType.Helius), + url: z.string(), + // TODO add options + feeLevel: z.nativeEnum(AgentSealevelHeliusFeeLevel), + }), + z.object({ + type: z.literal(AgentSealevelPriorityFeeOracleType.Constant), + // In microlamports + fee: ZUWei, + }), + ]) + .optional(), + transactionSubmitter: z + .object({ + type: z.nativeEnum(AgentSealevelTransactionSubmitterType), + url: z.string().optional(), + }) + .optional(), +}); + +export type AgentSealevelChainMetadata = z.infer< + typeof AgentSealevelChainMetadataSchema +>; + +export type AgentSealevelPriorityFeeOracle = + AgentSealevelChainMetadata['priorityFeeOracle']; + +export type AgentSealevelTransactionSubmitter = + AgentSealevelChainMetadata['transactionSubmitter']; + export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( HyperlaneDeploymentArtifactsSchema, ) @@ -155,6 +209,7 @@ export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( .optional(), }) .merge(AgentCosmosChainMetadataSchema.partial()) + .merge(AgentSealevelChainMetadataSchema.partial()) .refine((metadata) => { // Make sure that the signer is valid for the protocol @@ -201,6 +256,13 @@ export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( } } + // If the protocol type is Sealevel, require everything in AgentSealevelChainMetadataSchema + if (metadata.protocol === ProtocolType.Sealevel) { + if (!AgentSealevelChainMetadataSchema.safeParse(metadata).success) { + return false; + } + } + return true; }); diff --git a/typescript/sdk/src/token/checker.ts b/typescript/sdk/src/token/checker.ts index cf29d28c81..7f18e27b80 100644 --- a/typescript/sdk/src/token/checker.ts +++ b/typescript/sdk/src/token/checker.ts @@ -68,29 +68,7 @@ export class HypERC20Checker extends ProxiedRouterChecker< const expectedConfig = this.configMap[chain]; const hypToken = this.app.router(this.app.getContracts(chain)); - // Check all actual decimals are consistent - const actualChainDecimals = await this.getEvmActualDecimals(); - this.checkDecimalConsistency( - chain, - hypToken, - actualChainDecimals, - 'actual', - true, - ); - - // Check all config decimals are consistent as well - const configDecimals = objMap( - this.configMap, - (_chain, config) => config.decimals, - ); - this.checkDecimalConsistency( - chain, - hypToken, - configDecimals, - 'config', - false, - ); - + // Check if configured token type matches actual token type if (isNativeTokenConfig(expectedConfig)) { try { await this.multiProvider.estimateGas(chain, { @@ -126,6 +104,29 @@ export class HypERC20Checker extends ProxiedRouterChecker< this.addViolation(violation); } } + + // Check all actual decimals are consistent, this should be done after checking the token type to avoid 'decimal()' calls to non collateral token that would fail + const actualChainDecimals = await this.getEvmActualDecimals(); + this.checkDecimalConsistency( + chain, + hypToken, + actualChainDecimals, + 'actual', + true, + ); + + // Check all config decimals are consistent as well + const configDecimals = objMap( + this.configMap, + (_chain, config) => config.decimals, + ); + this.checkDecimalConsistency( + chain, + hypToken, + configDecimals, + 'config', + false, + ); } private cachedAllActualDecimals: Record | undefined = diff --git a/typescript/sdk/src/utils/validator.ts b/typescript/sdk/src/utils/validator.ts index c49612f8a7..10f5a0d510 100644 --- a/typescript/sdk/src/utils/validator.ts +++ b/typescript/sdk/src/utils/validator.ts @@ -10,3 +10,10 @@ export async function getValidatorFromStorageLocation(location: string) { throw new Error('Invalid storage location'); } } + +export function isValidValidatorStorageLocation(location: string) { + return ( + location?.startsWith(GCP_LOCATION_PREFIX) || + location?.startsWith(S3_LOCATION_PREFIX) + ); +} diff --git a/typescript/utils/src/async.ts b/typescript/utils/src/async.ts index f9bda74eb1..505318e32a 100644 --- a/typescript/utils/src/async.ts +++ b/typescript/utils/src/async.ts @@ -1,4 +1,5 @@ import { rootLogger } from './logging.js'; +import { assert } from './validation.js'; /** * Return a promise that resolves in ms milliseconds. @@ -159,6 +160,7 @@ export async function concurrentMap( mapFn: (val: A, idx: number) => Promise, ): Promise { let res: B[] = []; + assert(concurrency > 0, 'concurrency must be greater than 0'); for (let i = 0; i < xs.length; i += concurrency) { const remaining = xs.length - i; const sliceSize = Math.min(remaining, concurrency); diff --git a/typescript/utils/src/ids.ts b/typescript/utils/src/ids.ts index ff7bca0420..c3f3296377 100644 --- a/typescript/utils/src/ids.ts +++ b/typescript/utils/src/ids.ts @@ -20,7 +20,7 @@ export function canonizeId(data: BytesLike): Uint8Array { } /** - * Converts an Hyperlane ID of 20 or 32 bytes to the corresponding EVM Address. + * Converts a Hyperlane ID of 20 or 32 bytes to the corresponding EVM Address. * * For 32-byte IDs this enforces the EVM convention of using the LAST 20 bytes. * diff --git a/typescript/utils/src/validator.ts b/typescript/utils/src/validator.ts index 99cb9772ee..63f135909d 100644 --- a/typescript/utils/src/validator.ts +++ b/typescript/utils/src/validator.ts @@ -93,4 +93,16 @@ export class BaseValidator { ); return eqAddress(address, this.config.address); } + + getLatestCheckpointIndex(): Promise { + throw new Error('Not implemented'); + } + + storageLocation(): string { + throw new Error('Not implemented'); + } + + getLatestCheckpointUrl(): string { + throw new Error('Not implemented'); + } } diff --git a/typescript/widgets/src/walletIntegrations/utils.ts b/typescript/widgets/src/walletIntegrations/utils.ts index b0a994f1d8..6c2d485857 100644 --- a/typescript/widgets/src/walletIntegrations/utils.ts +++ b/typescript/widgets/src/walletIntegrations/utils.ts @@ -11,11 +11,10 @@ export function ethers5TxToWagmiTx( tx: Ethers5Transaction, ): SendTransactionParameters { if (!tx.to) throw new Error('No tx recipient address specified'); - if (!tx.data) throw new Error('No tx data specified'); return { to: tx.to as `0x${string}`, value: ethersBnToBigInt(tx.value || EthersBN.from('0')), - data: tx.data as `0x{string}`, + data: tx.data as `0x{string}` | undefined, nonce: tx.nonce, chainId: tx.chainId, gas: tx.gasLimit ? ethersBnToBigInt(tx.gasLimit) : undefined, diff --git a/yarn.lock b/yarn.lock index 13a4f46e30..52bd63f78a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7381,6 +7381,7 @@ __metadata: "@inquirer/core": "npm:9.0.10" "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:3.3.2" + "@inquirer/search": "npm:^3.0.1" "@types/chai-as-promised": "npm:^8" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -7796,6 +7797,23 @@ __metadata: languageName: node linkType: hard +"@inquirer/core@npm:^10.1.2": + version: 10.1.2 + resolution: "@inquirer/core@npm:10.1.2" + dependencies: + "@inquirer/figures": "npm:^1.0.9" + "@inquirer/type": "npm:^3.0.2" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^2.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/e92ade5ba7dbcfd83629db2df7fb91877ac777a7f1e03a16b0d5c08621dafe09d321c5f14b37c2dca80a3db2d68e5a478f8eaeafcb62ed42c46e7349b7276094 + languageName: node + linkType: hard + "@inquirer/core@npm:^6.0.0": version: 6.0.0 resolution: "@inquirer/core@npm:6.0.0" @@ -7849,6 +7867,13 @@ __metadata: languageName: node linkType: hard +"@inquirer/figures@npm:^1.0.9": + version: 1.0.9 + resolution: "@inquirer/figures@npm:1.0.9" + checksum: 10/7ced1275a5826cdeb61797d6c068417e7d52aa87894de18cedd259f783f42d731226c3f8b92cab27b8e7b0e31ab1dd3cd77f16935b67ebe1cbb271e5972d7758 + languageName: node + linkType: hard + "@inquirer/input@npm:^1.2.16": version: 1.2.16 resolution: "@inquirer/input@npm:1.2.16" @@ -7900,6 +7925,20 @@ __metadata: languageName: node linkType: hard +"@inquirer/search@npm:^3.0.1": + version: 3.0.4 + resolution: "@inquirer/search@npm:3.0.4" + dependencies: + "@inquirer/core": "npm:^10.1.2" + "@inquirer/figures": "npm:^1.0.9" + "@inquirer/type": "npm:^3.0.2" + yoctocolors-cjs: "npm:^2.1.2" + peerDependencies: + "@types/node": ">=18" + checksum: 10/e2363f808e32983d659049cabc21f57e8b7dd7b591677b4bd1be0c45653e3d86f98f1772f3866bc97f14f63983399b259e8956eda181fafbe7eea07f8169bd95 + languageName: node + linkType: hard + "@inquirer/select@npm:^1.3.3": version: 1.3.3 resolution: "@inquirer/select@npm:1.3.3" @@ -7931,6 +7970,15 @@ __metadata: languageName: node linkType: hard +"@inquirer/type@npm:^3.0.2": + version: 3.0.2 + resolution: "@inquirer/type@npm:3.0.2" + peerDependencies: + "@types/node": ">=18" + checksum: 10/d1a2879b1baa357421cef441fc7b43181e110243933763ae922c55c2fc9af2d459ceaca8b71ed57e3dabd5077542fa0dd1d0ff0cf362ce054e61202386b545ed + languageName: node + linkType: hard + "@interchain-ui/react@npm:^1.23.28": version: 1.26.1 resolution: "@interchain-ui/react@npm:1.26.1" @@ -29386,6 +29434,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10/d2e4fd2f5aa342b89b98134a8d899d8ef9b0a6d69274c4af9df46faa2d97aeb1f2ce83d867880d6de63643c52386579b99139801e24e7526c3b9b0a6d1e18d6c + languageName: node + linkType: hard + "mz@npm:^2.7.0": version: 2.7.0 resolution: "mz@npm:2.7.0"