loop-agent-sdk 0.1.0

Trustless agent SDK for Loop Protocol — intent-based execution on Solana.
Documentation
//! Loop Protocol Constants
//! 
//! Program IDs, token mints, and protocol constants.
//! Shared between all Loop SDKs.

use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

// ============================================================================
// NETWORK CONFIGURATION
// ============================================================================

/// Network environment
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Network {
    Devnet,
    Mainnet,
}

impl Default for Network {
    fn default() -> Self {
        Network::Mainnet
    }
}

// ============================================================================
// PROGRAM IDS
// ============================================================================

/// Program IDs for each network
pub struct ProgramIds {
    pub vault: Pubkey,
    pub cred: Pubkey,
    pub oxo: Pubkey,
    pub vtp: Pubkey,
    pub avp: Pubkey,
    pub shopping: Pubkey,
}

impl ProgramIds {
    /// Get program IDs for the specified network
    pub fn for_network(network: Network) -> Self {
        match network {
            Network::Devnet => Self::devnet(),
            Network::Mainnet => Self::mainnet(),
        }
    }
    
    /// Devnet program IDs (March 16, 2026)
    pub fn devnet() -> Self {
        Self {
            vault: Pubkey::from_str("9gKRCrUpHv9CRHwYMmm2zP5bN1bUKgyi7BxuS5jifX4x").unwrap(),
            cred: Pubkey::from_str("JCzhFrDvipRr5CYtWoV3szpaJ3RP59gbAn4zCi5CZcv8").unwrap(),
            oxo: Pubkey::from_str("AZZQdfcmejPjebNnneqTeVbkbVY8nyZuomuChbLqwhHj").unwrap(),
            vtp: Pubkey::from_str("3mP7L31af6MV6FnqWG6E78JELNuizWDwBK3rC3g3WjSK").unwrap(),
            avp: Pubkey::from_str("FE3ZJBqVcqP6ar2pnndMghgNb3pi4mrjhVoAS7x4BVCA").unwrap(),
            shopping: Pubkey::from_str("FSqRkH7nkGHP3VpHwFE667PVAfLKfSGaPMgTrXpJZJoJ").unwrap(),
        }
    }
    
    /// Mainnet program IDs (March 18, 2026)
    pub fn mainnet() -> Self {
        Self {
            vault: Pubkey::from_str("J8HhLeRv5iQaSyYQBXJoDwDKbw4V8uA84WN93YrVSWQT").unwrap(),
            cred: Pubkey::from_str("HYQJwCJ5wH9o4sb9sVPyvSSeY9DtsznZGy2AfpiBaBaG").unwrap(),
            // OXO not yet deployed to mainnet
            oxo: Pubkey::from_str("AZZQdfcmejPjebNnneqTeVbkbVY8nyZuomuChbLqwhHj").unwrap(),
            // VTP not yet deployed to mainnet
            vtp: Pubkey::from_str("3mP7L31af6MV6FnqWG6E78JELNuizWDwBK3rC3g3WjSK").unwrap(),
            // AVP not yet deployed to mainnet
            avp: Pubkey::from_str("FE3ZJBqVcqP6ar2pnndMghgNb3pi4mrjhVoAS7x4BVCA").unwrap(),
            shopping: Pubkey::from_str("HiewKEBy6YVn3Xi5xdhyrsfPr3KjKg6Jy8PXemyeteXJ").unwrap(),
        }
    }
}

// ============================================================================
// TOKEN MINTS
// ============================================================================

/// Token mints for each network
pub struct TokenMints {
    pub cred: Pubkey,
    pub oxo: Pubkey,
    pub usdc: Pubkey,
}

impl TokenMints {
    /// Get token mints for the specified network
    pub fn for_network(network: Network) -> Self {
        match network {
            Network::Devnet => Self::devnet(),
            Network::Mainnet => Self::mainnet(),
        }
    }
    
    /// Devnet token mints
    pub fn devnet() -> Self {
        Self {
            cred: Pubkey::from_str("DDfGosZop37FA97MqCrXA6fKMu8kTwduQ1JsdYJFoNBP").unwrap(),
            oxo: Pubkey::from_str("A8UkSCWuFvgBjeqWzZVowyCyUgW3JcyPB8VscPycd9hd").unwrap(),
            usdc: Pubkey::from_str("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU").unwrap(),
        }
    }
    
    /// Mainnet token mints
    pub fn mainnet() -> Self {
        Self {
            cred: Pubkey::from_str("9GQMCAK3MpZF1hEbwqA9d4mRGtippGV9hyr8fxmz6eA").unwrap(),
            // OXO not yet deployed to mainnet
            oxo: Pubkey::from_str("A8UkSCWuFvgBjeqWzZVowyCyUgW3JcyPB8VscPycd9hd").unwrap(),
            usdc: Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap(),
        }
    }
}

// ============================================================================
// STATE ACCOUNTS (Mainnet)
// ============================================================================

/// Mainnet state accounts
pub struct MainnetState {
    pub cred_config: Pubkey,
    pub shopping_state: Pubkey,
    pub reserve_vault: Pubkey,
    pub treasury: Pubkey,
    pub staker_pool: Pubkey,
}

impl MainnetState {
    pub fn get() -> Self {
        Self {
            cred_config: Pubkey::from_str("FzwRshrm2Pu8ygMPqHwbjb7yWBiQ2pQc3Lgk8XJRpvNM").unwrap(),
            shopping_state: Pubkey::from_str("92nXnNG5gJFDEqXE8Lyrqr3nDZiaD2atBCoRSNY5JD3n").unwrap(),
            reserve_vault: Pubkey::from_str("Ga2PRtFu3TRsTjN1QxdVpvtjVP7kb1rAkZse9MXWXjPh").unwrap(),
            treasury: Pubkey::from_str("CTRnn7vC1EtL1YDLU3x3iidEcCWRmZupEXHKQfL17Fxa").unwrap(),
            staker_pool: Pubkey::from_str("zEJf7Vy7ZDjvoMTBndAvkvxRpACUzCboQg2dP8H8R6k").unwrap(),
        }
    }
}

// ============================================================================
// PROTOCOL CONSTANTS
// ============================================================================

/// Token decimals
pub const CRED_DECIMALS: u8 = 6;
pub const OXO_DECIMALS: u8 = 6;
pub const LAMPORTS_PER_CRED: u64 = 1_000_000;
pub const LAMPORTS_PER_OXO: u64 = 1_000_000;

/// Distribution policy (enforced by smart contract)
pub const USER_SHARE_BPS: u16 = 8000;      // 80%
pub const TREASURY_SHARE_BPS: u16 = 1400;  // 14%
pub const STAKER_SHARE_BPS: u16 = 600;     // 6%

/// Staking APY tiers (basis points, 100 = 1%)
pub const STAKING_APY: [(u16, u16); 5] = [
    (365, 1500),  // 15% APY for 365+ days
    (180, 1200),  // 12% APY for 180-364 days
    (90, 800),    // 8% APY for 90-179 days
    (30, 500),    // 5% APY for 30-89 days
    (7, 300),     // 3% APY for 7-29 days
];

/// Individual staking APY constants (basis points)
pub const STAKE_30D_APY_BPS: u16 = 800;   // 8%
pub const STAKE_90D_APY_BPS: u16 = 1200;  // 12%
pub const STAKE_180D_APY_BPS: u16 = 1600; // 16%
pub const STAKE_365D_APY_BPS: u16 = 2000; // 20%

/// Staking duration limits
pub const MIN_STAKE_DAYS: u16 = 7;
pub const MAX_STAKE_DAYS: u16 = 730;  // 2 years

/// Vault constants
pub const EXTRACTION_FEE_BPS: u16 = 500;  // 5%

/// OXO constants
pub const OXO_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;  // 1B with 6 decimals
pub const MIN_LOCK_SECONDS: i64 = 15_552_000;   // 6 months
pub const MAX_LOCK_SECONDS: i64 = 126_144_000;  // 4 years
pub const GRADUATION_THRESHOLD: u64 = 25_000_000_000;  // 25,000 OXO
pub const AGENT_CREATION_FEE: u64 = 500_000_000;  // 500 OXO

/// VTP constants
pub const TRANSFER_FEE_BPS: u16 = 10;   // 0.1%
pub const ESCROW_FEE_BPS: u16 = 25;     // 0.25%
pub const MAX_ARBITERS: u8 = 5;
pub const MAX_CONDITIONS: u8 = 10;

/// AVP constants
pub const MIN_SERVICE_AGENT_STAKE: u64 = 500_000_000;  // 500 OXO
pub const MAX_CAPABILITIES: u8 = 20;
pub const MAX_METADATA_LEN: usize = 200;

/// Session key defaults
pub const SESSION_KEY_TTL_HOURS: u32 = 24;
pub const SESSION_KEY_MAX_ACTIONS: u32 = 1000;

/// State reload target
pub const CONTEXT_RELOAD_TARGET_MS: u32 = 100;

// ============================================================================
// ENUMS (matching TypeScript SDK)
// ============================================================================

/// Type of value capture
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CaptureType {
    Shopping = 0,
    Data = 1,
    Presence = 2,
    Attention = 3,
    Referral = 4,
}

/// Agent permission levels for vault access
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PermissionLevel {
    None = 0,
    Read = 1,
    Capture = 2,
    Guided = 3,
    Autonomous = 4,
}

/// Escrow status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum EscrowStatus {
    Active = 0,
    Released = 1,
    Cancelled = 2,
    Disputed = 3,
}

/// Agent type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AgentType {
    Personal = 0,
    Service = 1,
}

/// Agent status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AgentStatus {
    Active = 0,
    Suspended = 1,
    Revoked = 2,
}

// ============================================================================
// RPC ENDPOINTS
// ============================================================================

/// Default RPC endpoints
pub const DEVNET_RPC: &str = "https://api.devnet.solana.com";
pub const MAINNET_RPC: &str = "https://api.mainnet-beta.solana.com";

/// Get RPC endpoint for network
pub fn rpc_endpoint(network: Network) -> &'static str {
    match network {
        Network::Devnet => DEVNET_RPC,
        Network::Mainnet => MAINNET_RPC,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn program_ids_are_valid() {
        let devnet = ProgramIds::devnet();
        let mainnet = ProgramIds::mainnet();
        
        // All should be valid pubkeys
        assert_ne!(devnet.vault, Pubkey::default());
        assert_ne!(mainnet.vault, Pubkey::default());
    }
    
    #[test]
    fn distribution_adds_to_100() {
        assert_eq!(
            USER_SHARE_BPS + TREASURY_SHARE_BPS + STAKER_SHARE_BPS,
            10000
        );
    }
    
    #[test]
    fn staking_apy_increases_with_duration() {
        for i in 1..STAKING_APY.len() {
            assert!(STAKING_APY[i-1].1 > STAKING_APY[i].1);
        }
    }
}