mx-core 0.1.0

Core utilities for MultiversX Rust services.
Documentation
//! Bech32 address encoding and decoding utilities.

use crate::error::CoreError;
use prost::bytes::Bytes;

/// Decodes a bech32-encoded address into raw bytes.
///
/// # Arguments
/// * `hrp_addr` - A bech32-encoded address string (e.g., "erd1...")
///
/// # Returns
/// The raw 32-byte public key, or an error if decoding fails
///
/// # Errors
/// Returns an error if:
/// - The address is not valid bech32
/// - The decoded data is not exactly 32 bytes
pub fn decode_bech32(hrp_addr: &str) -> Result<Bytes, CoreError> {
    let (_hrp, raw) =
        bech32::decode(hrp_addr).map_err(|e| CoreError::InvalidBech32(e.to_string()))?;

    if raw.len() != 32 {
        return Err(CoreError::InvalidAddressLength(raw.len()));
    }

    Ok(Bytes::from(raw))
}

/// Decodes an optional bech32 address, returning empty Bytes if None or empty.
///
/// # Arguments
/// * `addr` - Optional bech32-encoded address string
///
/// # Returns
/// The raw bytes, or empty Bytes if input is None/empty
pub fn decode_optional_bech32(addr: Option<&str>) -> Result<Bytes, CoreError> {
    match addr {
        Some(a) if !a.trim().is_empty() => decode_bech32(a),
        _ => Ok(Bytes::new()),
    }
}

/// Encodes raw bytes as a bech32 address with the given HRP.
///
/// # Arguments
/// * `hrp` - Human-readable prefix (e.g., "erd" for `MultiversX` mainnet)
/// * `data` - Raw bytes to encode (typically 32-byte public key)
///
/// # Returns
/// The bech32-encoded address string
///
/// # Errors
/// Returns an error if encoding fails
pub fn encode_bech32(hrp: &str, data: &[u8]) -> Result<String, CoreError> {
    bech32::encode::<bech32::Bech32>(
        bech32::Hrp::parse(hrp).map_err(|e| CoreError::InvalidHrp(e.to_string()))?,
        data,
    )
    .map_err(|e| CoreError::Bech32Encode(e.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_decode_bech32() {
        let addr = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th";
        let result = decode_bech32(addr);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 32);
    }

    #[test]
    fn test_decode_bech32_invalid() {
        let result = decode_bech32("invalid");
        assert!(result.is_err());
    }

    #[test]
    fn test_encode_decode_roundtrip() {
        let original = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th";
        let decoded = decode_bech32(original).unwrap();
        let encoded = encode_bech32("erd", &decoded).unwrap();
        assert_eq!(original, encoded);
    }

    #[test]
    fn test_decode_optional_bech32_none() {
        let result = decode_optional_bech32(None);
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    #[test]
    fn test_decode_optional_bech32_empty() {
        let result = decode_optional_bech32(Some(""));
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }
}