//! Cross-Chain Bridge Infrastructure for Synor //! //! This crate provides bridge infrastructure for cross-chain asset transfers, //! enabling Synor to interoperate with external blockchains like Ethereum. //! //! # Architecture //! //! ```text //! ┌────────────────────────────────────────────────────────────────────┐ //! │ Synor Bridge Architecture │ //! ├────────────────────────────────────────────────────────────────────┤ //! │ │ //! │ ┌──────────────┐ Lock-Mint ┌──────────────────────────┐ │ //! │ │ External │ ──────────────► │ Synor Chain │ │ //! │ │ Chain │ │ │ │ //! │ │ (Ethereum) │ ◄────────────── │ Wrapped Tokens (sETH) │ │ //! │ │ │ Burn-Unlock │ │ │ //! │ └──────────────┘ └──────────────────────────┘ │ //! │ │ //! │ ┌─────────────────────────────────────────────────────────────┐ │ //! │ │ Bridge Components │ │ //! │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐ │ │ //! │ │ │ Vault │ │ Relayer │ │ Validator │ │ Oracle │ │ │ //! │ │ │ (Locks) │ │ (Events) │ │ (Proofs) │ │ (Price) │ │ │ //! │ │ └───────────┘ └───────────┘ └───────────┘ └──────────┘ │ │ //! │ └─────────────────────────────────────────────────────────────┘ │ //! │ │ //! └────────────────────────────────────────────────────────────────────┘ //! ``` //! //! # Bridge Flow (Lock-Mint) //! //! ```text //! User Ethereum Relayer Synor //! │ │ │ │ //! ├── Lock ETH ─────────►│ │ │ //! │ ├── LockEvent ─────►│ │ //! │ │ ├── SubmitProof ───►│ //! │ │ │ ├─ Verify //! │ │ │ ├─ Mint sETH //! │◄─────────────────────────────────────────────────── sETH ──────┤ //! ``` //! //! # Supported Bridges //! //! - **Ethereum**: Lock-Mint bridge for ETH and ERC-20 tokens //! - **Bitcoin**: HTLC-based atomic swaps (via synor-ibc) //! - **IBC Chains**: Native IBC transfers (via synor-ibc) #![allow(dead_code)] pub mod ethereum; pub mod transfer; pub mod vault; pub use ethereum::{ EthereumBridge, EthereumBridgeConfig, EthereumEvent, EthereumEventType, WrappedToken, }; pub use transfer::{ BridgeTransfer, TransferDirection, TransferId, TransferManager, TransferStatus, }; pub use vault::{LockedAsset, Vault, VaultId, VaultManager}; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use std::fmt; use thiserror::Error; /// Bridge protocol version pub const BRIDGE_VERSION: &str = "1.0.0"; /// Maximum transfer amount for safety (in smallest units) pub const MAX_TRANSFER_AMOUNT: u128 = 1_000_000_000_000_000_000_000_000; // 1M tokens /// Minimum confirmations for Ethereum deposits pub const ETH_MIN_CONFIRMATIONS: u64 = 12; /// Minimum confirmations for Bitcoin deposits pub const BTC_MIN_CONFIRMATIONS: u64 = 6; /// Bridge chain identifier #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum ChainType { /// Synor native chain Synor, /// Ethereum mainnet Ethereum, /// Ethereum Sepolia testnet EthereumSepolia, /// Bitcoin mainnet Bitcoin, /// Bitcoin testnet BitcoinTestnet, /// Cosmos SDK chain (IBC) Cosmos(String), /// Custom chain Custom(String), } impl ChainType { /// Get chain ID for Ethereum networks pub fn eth_chain_id(&self) -> Option { match self { ChainType::Ethereum => Some(1), ChainType::EthereumSepolia => Some(11155111), _ => None, } } /// Check if this is an EVM-compatible chain pub fn is_evm(&self) -> bool { matches!(self, ChainType::Ethereum | ChainType::EthereumSepolia) } } impl fmt::Display for ChainType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ChainType::Synor => write!(f, "synor"), ChainType::Ethereum => write!(f, "ethereum"), ChainType::EthereumSepolia => write!(f, "ethereum-sepolia"), ChainType::Bitcoin => write!(f, "bitcoin"), ChainType::BitcoinTestnet => write!(f, "bitcoin-testnet"), ChainType::Cosmos(id) => write!(f, "cosmos:{}", id), ChainType::Custom(id) => write!(f, "custom:{}", id), } } } /// Asset identifier across chains #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct AssetId { /// Chain where the asset originates pub chain: ChainType, /// Asset identifier (contract address for ERC-20, "native" for ETH) pub identifier: String, /// Asset symbol pub symbol: String, /// Decimal places pub decimals: u8, } impl AssetId { /// Create native Ethereum asset pub fn eth() -> Self { Self { chain: ChainType::Ethereum, identifier: "native".to_string(), symbol: "ETH".to_string(), decimals: 18, } } /// Create ERC-20 asset pub fn erc20(address: impl Into, symbol: impl Into, decimals: u8) -> Self { Self { chain: ChainType::Ethereum, identifier: address.into(), symbol: symbol.into(), decimals, } } /// Create Synor native asset pub fn synor() -> Self { Self { chain: ChainType::Synor, identifier: "native".to_string(), symbol: "SYNOR".to_string(), decimals: 18, } } /// Create wrapped asset on Synor pub fn wrapped(original: &AssetId) -> Self { Self { chain: ChainType::Synor, identifier: format!("wrapped:{}", original.identifier), symbol: format!("s{}", original.symbol), decimals: original.decimals, } } /// Check if this is a wrapped asset pub fn is_wrapped(&self) -> bool { self.identifier.starts_with("wrapped:") } /// Get the original asset if this is wrapped pub fn unwrap_identifier(&self) -> Option<&str> { self.identifier.strip_prefix("wrapped:") } } impl fmt::Display for AssetId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.chain, self.symbol) } } /// Bridge address (unified format for cross-chain addresses) #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct BridgeAddress { /// Chain type pub chain: ChainType, /// Address bytes pub address: Vec, } impl BridgeAddress { /// Create from Ethereum address (20 bytes) pub fn from_eth(address: [u8; 20]) -> Self { Self { chain: ChainType::Ethereum, address: address.to_vec(), } } /// Create from Synor address (32 bytes) pub fn from_synor(address: [u8; 32]) -> Self { Self { chain: ChainType::Synor, address: address.to_vec(), } } /// Create from hex string pub fn from_hex(chain: ChainType, hex: &str) -> Result { let hex = hex.strip_prefix("0x").unwrap_or(hex); let address = hex::decode(hex) .map_err(|e| BridgeError::InvalidAddress(format!("invalid hex: {}", e)))?; Ok(Self { chain, address }) } /// Get as Ethereum address pub fn as_eth(&self) -> Option<[u8; 20]> { if self.address.len() == 20 { let mut arr = [0u8; 20]; arr.copy_from_slice(&self.address); Some(arr) } else { None } } /// Get as Synor address pub fn as_synor(&self) -> Option<[u8; 32]> { if self.address.len() == 32 { let mut arr = [0u8; 32]; arr.copy_from_slice(&self.address); Some(arr) } else { None } } /// Get as hex string pub fn to_hex(&self) -> String { format!("0x{}", hex::encode(&self.address)) } } impl fmt::Display for BridgeAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.chain, self.to_hex()) } } /// Bridge error types #[derive(Debug, Error)] pub enum BridgeError { #[error("Invalid address: {0}")] InvalidAddress(String), #[error("Invalid amount: {0}")] InvalidAmount(String), #[error("Transfer not found: {0}")] TransferNotFound(String), #[error("Vault not found: {0}")] VaultNotFound(String), #[error("Insufficient balance: need {required}, have {available}")] InsufficientBalance { required: u128, available: u128 }, #[error("Asset not supported: {0}")] AssetNotSupported(String), #[error("Chain not supported: {0}")] ChainNotSupported(String), #[error("Transfer already exists: {0}")] TransferAlreadyExists(String), #[error("Transfer already completed: {0}")] TransferAlreadyCompleted(String), #[error("Invalid proof: {0}")] InvalidProof(String), #[error("Insufficient confirmations: need {required}, have {actual}")] InsufficientConfirmations { required: u64, actual: u64 }, #[error("Bridge paused")] BridgePaused, #[error("Rate limit exceeded")] RateLimitExceeded, #[error("Signature verification failed: {0}")] SignatureVerificationFailed(String), #[error("Oracle error: {0}")] OracleError(String), #[error("Relayer error: {0}")] RelayerError(String), #[error("Internal error: {0}")] Internal(String), } /// Result type for bridge operations pub type BridgeResult = std::result::Result; /// Bridge trait for implementing cross-chain bridges #[async_trait::async_trait] pub trait Bridge: Send + Sync { /// Get the source chain type fn source_chain(&self) -> ChainType; /// Get the destination chain type fn destination_chain(&self) -> ChainType; /// Check if an asset is supported fn supports_asset(&self, asset: &AssetId) -> bool; /// Get minimum confirmations required fn min_confirmations(&self) -> u64; /// Lock assets on the source chain async fn lock(&self, transfer: &BridgeTransfer) -> BridgeResult; /// Verify a lock proof async fn verify_lock(&self, transfer_id: &TransferId) -> BridgeResult; /// Mint wrapped tokens on the destination chain async fn mint(&self, transfer: &BridgeTransfer) -> BridgeResult<()>; /// Burn wrapped tokens (for redemption) async fn burn(&self, transfer: &BridgeTransfer) -> BridgeResult; /// Unlock original tokens async fn unlock(&self, transfer_id: &TransferId) -> BridgeResult<()>; /// Get transfer status async fn get_transfer_status(&self, transfer_id: &TransferId) -> BridgeResult; } /// Bridge event for tracking cross-chain activity #[derive(Debug, Clone, Serialize, Deserialize)] pub enum BridgeEvent { /// Asset locked on source chain AssetLocked { transfer_id: TransferId, asset: AssetId, amount: u128, sender: BridgeAddress, recipient: BridgeAddress, }, /// Lock verified LockVerified { transfer_id: TransferId, confirmations: u64, }, /// Wrapped token minted TokenMinted { transfer_id: TransferId, asset: AssetId, amount: u128, recipient: BridgeAddress, }, /// Token burned for redemption TokenBurned { transfer_id: TransferId, asset: AssetId, amount: u128, sender: BridgeAddress, }, /// Original asset unlocked AssetUnlocked { transfer_id: TransferId, recipient: BridgeAddress, }, /// Transfer completed TransferCompleted { transfer_id: TransferId }, /// Transfer failed TransferFailed { transfer_id: TransferId, reason: String, }, } #[cfg(test)] mod tests { use super::*; #[test] fn test_chain_type() { assert!(ChainType::Ethereum.is_evm()); assert!(!ChainType::Bitcoin.is_evm()); assert_eq!(ChainType::Ethereum.eth_chain_id(), Some(1)); } #[test] fn test_asset_id() { let eth = AssetId::eth(); assert_eq!(eth.symbol, "ETH"); assert_eq!(eth.decimals, 18); let wrapped = AssetId::wrapped(ð); assert!(wrapped.is_wrapped()); assert_eq!(wrapped.symbol, "sETH"); } #[test] fn test_bridge_address() { let eth_addr = BridgeAddress::from_eth([0xde; 20]); assert_eq!(eth_addr.address.len(), 20); assert!(eth_addr.as_eth().is_some()); let hex_addr = BridgeAddress::from_hex(ChainType::Ethereum, "0xdeadbeef").unwrap(); assert_eq!(hex_addr.address, vec![0xde, 0xad, 0xbe, 0xef]); } }