synor/crates/synor-bridge/src/lib.rs
Gulshan Yadav 45ccbcba03 feat(bridge): add synor-bridge crate for cross-chain interoperability
Phase 14: Interoperability & Privacy enhancements

New synor-bridge crate with Ethereum lock-mint bridge:
- Bridge trait for generic cross-chain implementations
- Vault management with daily limits and pause controls
- Transfer lifecycle (pending → confirmed → minted)
- Multi-relayer signature verification
- Wrapped token minting (ETH → sETH, ERC20 → sERC20)
- Burn-unlock flow for redemption

Also fixes synor-ibc lib.rs exports and adds rand dependency.

21 tests passing for synor-bridge.
2026-01-20 01:38:37 +05:30

436 lines
15 KiB
Rust

//! 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<u64> {
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<String>, symbol: impl Into<String>, 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<u8>,
}
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<Self, BridgeError> {
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<T> = std::result::Result<T, BridgeError>;
/// 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<TransferId>;
/// Verify a lock proof
async fn verify_lock(&self, transfer_id: &TransferId) -> BridgeResult<bool>;
/// 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<TransferId>;
/// Unlock original tokens
async fn unlock(&self, transfer_id: &TransferId) -> BridgeResult<()>;
/// Get transfer status
async fn get_transfer_status(&self, transfer_id: &TransferId) -> BridgeResult<TransferStatus>;
}
/// 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(&eth);
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]);
}
}