altius-tx-sdk 0.2.3

SDK for signing and sending Altius USD multi-token transactions
Documentation
//! Utility functions for the Altius SDK.

use alloy_primitives::{Address, B256, keccak256};
use alloy_rlp::Encodable;

// ============================================================================
// Fee Token Address Computation
// ============================================================================

/// Fee token prefix (10 bytes): 0xA1700000...
const FEE_TOKEN_PREFIX_BYTES: [u8; 10] = [0xA1, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

/// Compute deterministic fee token address.
///
/// Address = prefix(10B) || keccak256(creator, salt)[:10]
///
/// Returns the computed address and the lower 8 bytes as u64 for index checking.
pub fn compute_fee_token_address(creator: Address, salt: B256) -> (Address, u64) {
    let mut buf = Vec::new();
    creator.encode(&mut buf);
    salt.encode(&mut buf);

    let hash = keccak256(&buf);

    // Extract lower 8 bytes for index calculation
    let mut lower_bytes = [0u8; 8];
    lower_bytes.copy_from_slice(&hash[..8]);
    let lower_u64 = u64::from_be_bytes(lower_bytes);

    // Address = prefix(10B) + hash[:10](10B) = 20 bytes
    let mut address_bytes = [0u8; 20];
    address_bytes[..10].copy_from_slice(&FEE_TOKEN_PREFIX_BYTES);
    address_bytes[10..].copy_from_slice(&hash[..10]);

    (Address::from(address_bytes), lower_u64)
}

/// Check if address is in reserved range (index < 256)
pub fn is_reserved_address(creator: Address, salt: B256) -> bool {
    let (_, index) = compute_fee_token_address(creator, salt);
    index < 256
}

/// Compute keccak256 hash
pub fn compute_keccak256(data: &[u8]) -> B256 {
    keccak256(data)
}

/// Pad a hex string to specified bytes
pub fn pad_hex(hex: &str, bytes: usize) -> String {
    let clean = hex.trim_start_matches("0x");
    let target_len = bytes * 2;
    if clean.len() >= target_len {
        return format!("0x{}", &clean[..target_len]);
    }
    // Pad with zeros
    let mut result = String::new();
    for _ in 0..(target_len - clean.len()) {
        result.push('0');
    }
    result.push_str(clean);
    format!("0x{}", result)
}

/// Convert number to padded hex
pub fn num_to_hex(num: u64, bytes: usize) -> String {
    let hex_str = format!("{:x}", num);
    let target_len = bytes * 2;
    if hex_str.len() >= target_len {
        return format!("0x{}", &hex_str[..target_len]);
    }
    let mut result = String::new();
    for _ in 0..(target_len - hex_str.len()) {
        result.push('0');
    }
    result.push_str(&hex_str);
    format!("0x{}", result)
}

// ============================================================================
// Fee Token Validation
// ============================================================================

/// Check if address has fee token prefix (0xa170...)
pub fn is_fee_token_prefix(addr: Address) -> bool {
    let bytes = addr.as_slice();
    bytes.len() >= 2 && bytes[0] == 0xA1 && bytes[1] == 0x70
}

/// Check if address is valid fee token (has prefix and not zero)
pub fn is_valid_fee_token_address(addr: Address) -> bool {
    is_fee_token_prefix(addr) && !addr.is_zero()
}

/// Check if index is reserved (index < 256)
pub fn is_index_reserved(index: u64) -> bool {
    index < 256
}