onemoney-protocol 0.18.0

Official Rust SDK for OneMoney Protocol - L1 blockchain network client
Documentation
//! Bridge-related API operations.

use om_primitives_types::transaction::payload::{TokenBridgeAndMintPayload, TokenBurnAndBridgePayload};
use om_rest_types::{
    requests::{TokenBridgeAndMintRequest, TokenBurnAndBridgeRequest},
    responses::TransactionResponse,
};

use crate::{
    client::{
        Client,
        config::{
            api_path,
            endpoints::bridge::{BRIDGE_AND_MINT, BURN_AND_BRIDGE},
        },
    },
    crypto::sign_transaction_payload,
    error::Result,
};

impl Client {
    /// Bridge and mint tokens from another chain.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token bridge and mint parameters
    /// * `private_key` - Private key for signing the transaction
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn bridge_and_mint(
        &self,
        payload: TokenBridgeAndMintPayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&payload, private_key)?;
        let request = TokenBridgeAndMintRequest {
            data: payload,
            signature,
        };

        self.post(&api_path(BRIDGE_AND_MINT), &request).await
    }

    /// Burn and bridge tokens to another chain.
    ///
    /// # Arguments
    ///
    /// * `payload` - Token burn and bridge parameters
    /// * `private_key` - Private key for signing the transaction
    ///
    /// # Returns
    ///
    /// The transaction result.
    pub async fn burn_and_bridge(
        &self,
        payload: TokenBurnAndBridgePayload,
        private_key: &str,
    ) -> Result<TransactionResponse> {
        let signature = sign_transaction_payload(&payload, private_key)?;
        let request = TokenBurnAndBridgeRequest {
            data: payload,
            signature,
        };

        self.post(&api_path(BURN_AND_BRIDGE), &request).await
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use alloy_primitives::{Address, Bytes, U256};

    use super::*;
    use crate::{
        NamedChain,
        client::config::endpoints::bridge::{BRIDGE_AND_MINT, BURN_AND_BRIDGE},
    };

    #[test]
    fn test_bridge_and_mint_api_path_construction() {
        let path = api_path(BRIDGE_AND_MINT);
        assert_eq!(path, "/v1/tokens/bridge_and_mint");
    }

    #[test]
    fn test_burn_and_bridge_api_path_construction() {
        let path = api_path(BURN_AND_BRIDGE);
        assert_eq!(path, "/v1/tokens/burn_and_bridge");
    }

    #[test]
    fn test_token_bridge_and_mint_payload_structure() {
        let recipient =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenBridgeAndMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 1,
            recipient,
            value: U256::from(5000000000000000000u64),
            token,
            source_chain_id: 1,
            source_tx_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
            bridge_metadata: "".to_string(),
        };

        assert_eq!(payload.chain_id, NamedChain::TESTNET_CHAIN_ID);
        assert_eq!(payload.nonce, 1);
        assert_eq!(payload.recipient, recipient);
        assert_eq!(payload.value, U256::from(5000000000000000000u64));
        assert_eq!(payload.token, token);
        assert_eq!(payload.source_chain_id, 1);
    }

    #[test]
    fn test_token_burn_and_bridge_payload_structure() {
        let sender =
            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");

        let payload = TokenBurnAndBridgePayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            sender,
            value: U256::from(3000000000000000000u64),
            token,
            destination_chain_id: 1,
            destination_address: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string(),
            escrow_fee: U256::from(1000000u64),
            bridge_metadata: "".to_string(),
            bridge_param: Bytes::default(),
        };

        assert_eq!(payload.chain_id, NamedChain::TESTNET_CHAIN_ID);
        assert_eq!(payload.nonce, 5);
        assert_eq!(payload.sender, sender);
        assert_eq!(payload.value, U256::from(3000000000000000000u64));
        assert_eq!(payload.token, token);
        assert_eq!(payload.destination_chain_id, 1);
    }

    #[test]
    fn test_bridge_and_mint_payload_alloy_rlp_encoding() {
        use alloy_rlp::Encodable as AlloyEncodable;

        let payload = TokenBridgeAndMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 1,
            recipient: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0")
                .expect("Test data should be valid"),
            value: U256::from(5000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            source_chain_id: 1,
            source_tx_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
            bridge_metadata: "".to_string(),
        };

        let mut encoded = Vec::new();
        payload.encode(&mut encoded);
        assert!(!encoded.is_empty());
        assert!(encoded.len() > 100);
    }

    #[test]
    fn test_burn_and_bridge_payload_alloy_rlp_encoding() {
        use alloy_rlp::Encodable as AlloyEncodable;

        let payload = TokenBurnAndBridgePayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 5,
            sender: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid"),
            value: U256::from(3000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            destination_chain_id: 1,
            destination_address: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string(),
            escrow_fee: U256::from(1000000u64),
            bridge_metadata: "".to_string(),
            bridge_param: Bytes::default(),
        };

        let mut encoded = Vec::new();
        payload.encode(&mut encoded);
        assert!(!encoded.is_empty());
        assert!(encoded.len() > 100);
    }

    #[test]
    fn test_bridge_payloads_different_encodings() {
        use alloy_rlp::Encodable as AlloyEncodable;

        let payload1 = TokenBridgeAndMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 1,
            recipient: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0")
                .expect("Test data should be valid"),
            value: U256::from(5000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            source_chain_id: 1,
            source_tx_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
            bridge_metadata: "".to_string(),
        };

        let payload2 = TokenBridgeAndMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 2,
            recipient: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0")
                .expect("Test data should be valid"),
            value: U256::from(5000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            source_chain_id: 1,
            source_tx_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
            bridge_metadata: "".to_string(),
        };

        let mut encoded1 = Vec::new();
        payload1.encode(&mut encoded1);

        let mut encoded2 = Vec::new();
        payload2.encode(&mut encoded2);

        assert_ne!(encoded1, encoded2);
    }

    #[test]
    fn test_bridge_payload_encoding_deterministic() {
        use alloy_rlp::Encodable as AlloyEncodable;

        let payload = TokenBurnAndBridgePayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 1,
            sender: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid"),
            value: U256::from(1000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            destination_chain_id: 1,
            destination_address: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string(),
            escrow_fee: U256::from(1000000u64),
            bridge_metadata: "".to_string(),
            bridge_param: Bytes::default(),
        };

        let mut encoded1 = Vec::new();
        payload.encode(&mut encoded1);

        let mut encoded2 = Vec::new();
        payload.encode(&mut encoded2);

        assert_eq!(encoded1, encoded2);
    }

    #[test]
    fn test_bridge_request_structure() {
        use om_rest_types::common::RestSignature;

        let payload = TokenBridgeAndMintPayload {
            chain_id: NamedChain::TESTNET_CHAIN_ID,
            nonce: 1,
            recipient: Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0")
                .expect("Test data should be valid"),
            value: U256::from(5000000000000000000u64),
            token: Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid"),
            source_chain_id: 1,
            source_tx_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(),
            bridge_metadata: "".to_string(),
        };

        let signature = RestSignature::new(
            U256::from_str("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
                .expect("Test data should be valid"),
            U256::from_str("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
                .expect("Test data should be valid"),
            27,
        );

        let request = TokenBridgeAndMintRequest {
            data: payload.clone(),
            signature: signature.clone(),
        };

        assert_eq!(request.data.chain_id, NamedChain::TESTNET_CHAIN_ID);
        assert_eq!(request.signature.v, 27);
    }
}