-
Notifications
You must be signed in to change notification settings - Fork 0
feat: refactor bridge function split #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
790b7cb
a4e62b9
8e49e44
4480be3
eb5301d
f195dc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,6 @@ alloy-provider = { version = "1", default-features = false, features = [ | |
| alloy-rpc-types = "1" | ||
| alloy-sol-types = { version = "1", features = ["json"] } | ||
| alloy-transport = { version = "1", default-features = false } | ||
| bincode = "1" | ||
| nitrogen-circle-message-transmitter-v2-encoder = { git = "https://github.com/CarteraMesh/nitrogen.git", branch = "main" } | ||
| nitrogen-circle-token-messenger-minter-v2-encoder = { git = "https://github.com/CarteraMesh/nitrogen.git", branch = "main" } | ||
| nitrogen-instruction-builder = { git = "https://github.com/CarteraMesh/nitrogen.git", branch = "main" } | ||
|
|
@@ -42,6 +41,7 @@ solana-signature = "2" | |
| solana-signer = "2" | ||
| spl-associated-token-account = { version = "7.0.0", features = ["no-entrypoint"] } | ||
| thiserror = "2" | ||
| tokio = { version = "1", default-features = false, features = ["time"] } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dependency Addition: Adding Consideration: The PR changes from blocking
|
||
| tracing = "0.1" | ||
|
|
||
| [dev-dependencies] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,14 +5,62 @@ | |
| [](https://github.com/CarteraMesh/cctp-bridge/actions) | ||
| [](https://codecov.io/github/CarteraMesh/cctp-bridge) | ||
|
|
||
| ## Installation | ||
|
|
||
| ### Cargo | ||
|
|
||
| * Install the rust toolchain in order to have cargo installed by following | ||
| [this](https://www.rust-lang.org/tools/install) guide. | ||
| * run `cargo install cctp-bridge` | ||
|
|
||
| ## About | ||
dougEfresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| cctp-bridge is a Rust-based helper library for the Cross-Chain Token Protocol [CCTP](https://developers.circle.com/cctp). It facilitates the transfer of USDC between different blockchain networks. | ||
| This crates provides flexible control over the transfer process, allowing users to customize various aspects of the transfer. | ||
|
|
||
| This project is a fork of the [cctp-rs](https://github.com/semiotic-ai/cctp-rs) [crate](https://crates.io/crates/cctp-rs) | ||
|
|
||
| ## Example | ||
|
|
||
| ```rust | ||
|
|
||
| mod common; | ||
|
|
||
| use { | ||
| alloy_chains::NamedChain, | ||
| alloy_provider::WalletProvider, | ||
| cctp_bridge::{Cctp, SolanSigners}, | ||
| common::*, | ||
| solana_signer::Signer, | ||
| tracing::info, | ||
| }; | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> anyhow::Result<()> { | ||
| dotenvy::dotenv().ok(); | ||
| tracing_subscriber::fmt::init(); | ||
| // Setup wallets | ||
| let base_sepolia_wallet_provider = evm_base_setup()?; | ||
| let (solana_keypair, rpc) = solana_setup()?; | ||
| info!( | ||
| "solana address {} sends to base address {}", | ||
| solana_keypair.pubkey(), | ||
| base_sepolia_wallet_provider.default_signer_address() | ||
| ); | ||
|
|
||
| // Convenience wrapper for cctp_bridge::SolanaProvider trait | ||
| let rpc_wrapper: cctp_bridge::SolanaWrapper = rpc.into(); | ||
| // Convenience wrapper for solana_signer::Signer for use of CCTP operations | ||
| let signers = SolanSigners::new(solana_keypair); | ||
|
|
||
| let bridge = Cctp::new_solana_evm( | ||
| rpc_wrapper, | ||
| base_sepolia_wallet_provider, | ||
| cctp_bridge::SOLANA_DEVNET, // source chain | ||
| NamedChain::BaseSepolia, // destination chain | ||
| ); | ||
| // 0.000010 USDC to base sepolia | ||
| let result = bridge.bridge_sol_evm(10, signers, None, None, None).await?; | ||
| println!("Solana burn txHash {}", result.burn); | ||
| println!( | ||
| "Base Receive txHash {}", | ||
| alloy_primitives::hex::encode(result.recv) | ||
| ); | ||
| Ok(()) | ||
| } | ||
| ``` | ||
|
Comment on lines
+17
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent Documentation Improvement ✓ The new example in the README is much more comprehensive than before. It includes:
Minor Suggestions:
|
||
|
|
||
| ## Development | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ use { | |
| }; | ||
|
|
||
| /// To be passed to message transmitter to claim/mint | ||
| #[derive(Clone)] | ||
| #[derive(Clone, Eq, PartialEq)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API Change: Added trait derives Adding
Current implementation looks correct for direct byte comparison. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is This adds
Recommendation: If this is for testing, consider adding a comment or only deriving these in test configuration: #[derive(Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]If it's needed in production code, that's fine, but it would be good to understand the use case.
dougEfresh marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Derive Changes: Adding Note: Since |
||
| pub struct Attestation { | ||
| pub attestation: Vec<u8>, | ||
| pub message: Vec<u8>, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,18 +8,22 @@ use { | |
| CctpChain, | ||
| error::{Error, Result}, | ||
| }, | ||
| alloy_chains::{Chain, NamedChain}, | ||
| alloy_chains::{Chain, ChainKind, NamedChain}, | ||
| alloy_network::Ethereum, | ||
| alloy_primitives::{FixedBytes, TxHash, hex}, | ||
| alloy_primitives::{ | ||
| FixedBytes, | ||
| TxHash, | ||
| hex::{self, encode}, | ||
| }, | ||
| alloy_provider::Provider, | ||
| alloy_sol_types::SolEvent, | ||
| reqwest::{Client, Response}, | ||
| solana_signature::Signature as SolanaSignature, | ||
| std::{ | ||
| fmt::{Debug, Display}, | ||
| thread::sleep, | ||
| time::Duration, | ||
| }, | ||
| tokio::time::sleep, | ||
| tracing::{Level, debug, error, info, instrument, trace}, | ||
| }; | ||
|
|
||
|
|
@@ -51,12 +55,15 @@ pub const CHAIN_CONFIRMATION_CONFIG: &[(NamedChain, u64, Duration)] = &[ | |
| ]; | ||
|
|
||
| /// Gets the chain-specific confirmation configuration | ||
| pub fn get_chain_confirmation_config(chain: &NamedChain) -> (u64, Duration) { | ||
| CHAIN_CONFIRMATION_CONFIG | ||
| .iter() | ||
| .find(|(ch, _, _)| ch == chain) | ||
| .map(|(_, confirmations, timeout)| (*confirmations, *timeout)) | ||
| .unwrap_or((1, DEFAULT_CONFIRMATION_TIMEOUT)) | ||
| pub fn get_chain_confirmation_config(chain: &Chain) -> (u64, Duration) { | ||
| match chain.kind() { | ||
| ChainKind::Named(n) => CHAIN_CONFIRMATION_CONFIG | ||
| .iter() | ||
| .find(|(ch, _, _)| ch == n) | ||
| .map(|(_, confirmations, timeout)| (*confirmations, *timeout)) | ||
| .unwrap_or((1, DEFAULT_CONFIRMATION_TIMEOUT)), | ||
| ChainKind::Id(_) => (2, Duration::from_secs(4)), // TODO add specific timeout for id chain | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded Default: The fallback timeout value of 4 seconds seems quite short for blockchain confirmation, especially for chains with variable block times. This could lead to premature timeouts under network congestion. Recommendation: Consider:
|
||
| } | ||
| } | ||
|
|
||
| /// For solana reclaim accounts | ||
|
|
@@ -114,7 +121,7 @@ impl Display for EvmBridgeResult { | |
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| #[derive(Clone)] | ||
| pub struct Cctp<SrcProvider, DstProvider> { | ||
| source_provider: SrcProvider, | ||
| destination_provider: DstProvider, | ||
|
|
@@ -124,6 +131,18 @@ pub struct Cctp<SrcProvider, DstProvider> { | |
| client: Client, | ||
| } | ||
|
|
||
| impl<SrcProvider, DstProvider> Debug for Cctp<SrcProvider, DstProvider> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Quality: Good implementation of Debug trait Nice custom Minor suggestion: Consider also including whether it's sandbox/mainnet: write!(
f,
"CCTP[{}({})->{}({})][{}]",
self.source_chain, src_domain,
self.destination_chain, dst_domain,
if self.source_chain.sandbox() { "sandbox" } else { "mainnet" }
) |
||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| let src_domain = self.source_chain.cctp_domain_id().unwrap_or(u32::MAX); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential panic in Debug implementation The Recommendation: Consider indicating an error state more explicitly: let src_domain = self.source_chain.cctp_domain_id()
.map(|d| d.to_string())
.unwrap_or_else(|_| "UNSUPPORTED".to_string());Or just use
dougEfresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let dst_domain = self.destination_chain.cctp_domain_id().unwrap_or(u32::MAX); | ||
| write!( | ||
| f, | ||
| "CCTP[{}({})->{}({})]", | ||
| self.source_chain, src_domain, self.destination_chain, dst_domain | ||
| ) | ||
| } | ||
|
Comment on lines
+134
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good Debug Implementation ✓ The custom Observation: Using Minor Enhancement: Consider also showing the recipient address in debug output for complete context: write!(
f,
"CCTP[{}({})->{}({}) recipient={}]",
self.source_chain, src_domain, self.destination_chain, dst_domain, self.recipient
) |
||
| } | ||
|
|
||
| impl<SrcProvider, DstProvider> Cctp<SrcProvider, DstProvider> { | ||
| /// Returns the CCTP API URL for the current environment | ||
| pub fn api_url(&self) -> &'static str { | ||
|
|
@@ -196,6 +215,21 @@ impl<SrcProvider, DstProvider> Cctp<SrcProvider, DstProvider> { | |
| ) | ||
| } | ||
|
|
||
| /// Wrapper call to [`get_attestation_with_retry`] for evm [`TxHash`] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Quality: Good addition of convenience method The Minor suggestion: Consider adding a doc comment explaining when to use this vs /// Wrapper call to [`get_attestation_with_retry`] for EVM [`TxHash`].
///
/// Use this method when you have an EVM transaction hash. It handles
/// the hex encoding automatically. For non-EVM chains or raw message hashes,
/// use [`get_attestation_with_retry`] directly.
|
||
| pub async fn get_attestation_evm( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing documentation about error behavior This new helper method is great for ergonomics, but the documentation doesn't mention what happens if the attestation retrieval fails (timeouts, network errors, etc.). Recommendation: Add a /// Wrapper call to [`get_attestation_with_retry`] for evm [`TxHash`]
///
/// # Errors
/// Returns an error if:
/// - The attestation times out after max_attempts
/// - Network errors occur during API calls
/// - The message hash is not found or attestation fails
dougEfresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| &self, | ||
| message_hash: TxHash, | ||
| max_attempts: Option<u32>, | ||
| poll_interval: Option<u64>, | ||
| ) -> Result<Attestation> { | ||
| self.get_attestation_with_retry( | ||
| format!("0x{}", encode(message_hash)), | ||
| max_attempts, | ||
| poll_interval, | ||
| ) | ||
| .await | ||
| } | ||
|
Comment on lines
+218
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good API Addition ✓ The This is a good example of providing specialized convenience methods while keeping the generic |
||
|
|
||
| /// Gets the attestation for a message hash from the CCTP API | ||
| /// | ||
| /// # Arguments | ||
|
|
@@ -238,7 +272,7 @@ impl<SrcProvider, DstProvider> Cctp<SrcProvider, DstProvider> { | |
| if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS { | ||
| let secs = 5 * 60; | ||
| debug!(sleep_secs = ?secs, "Rate limit exceeded, waiting before retrying"); | ||
| sleep(Duration::from_secs(secs)); | ||
| sleep(Duration::from_secs(secs)).await; | ||
| continue; | ||
|
Comment on lines
272
to
276
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded Sleep Duration: The 5-minute sleep on rate limiting is quite aggressive and blocks the entire async task. If multiple rate limits occur, this could lead to very long wait times. Considerations:
|
||
| } | ||
|
|
||
|
|
@@ -251,7 +285,7 @@ impl<SrcProvider, DstProvider> Cctp<SrcProvider, DstProvider> { | |
| poll_interval = ?poll_interval, | ||
| "Attestation not found (404), waiting before retrying" | ||
| ); | ||
| sleep(Duration::from_secs(poll_interval)); | ||
| sleep(Duration::from_secs(poll_interval)).await; | ||
| continue; | ||
| } | ||
|
|
||
|
|
@@ -322,7 +356,7 @@ impl<SrcProvider, DstProvider> Cctp<SrcProvider, DstProvider> { | |
| poll_interval = ?poll_interval, | ||
| "Attestation pending, waiting before retrying" | ||
| ); | ||
| sleep(Duration::from_secs(poll_interval)); | ||
| sleep(Duration::from_secs(poll_interval)).await; | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dependency Removed: The
bincodedependency has been removed but there's no context in the PR about why.Questions:
bincodeactually unused?Consider documenting dependency removals in commit messages or PR descriptions.