CoolRouter Documentation

Build AI-powered Solana dApps with the cleanest developer experience in the ecosystem.

CoolRouter is a revolutionary Solana program that enables smart contracts to call LLMs on-chain with structured outputs. Unlike traditional approaches that require polling, CoolRouter uses a unique callback mechanism to automatically invoke your contract with the LLM response once consensus is reached through our decentralized oracle network.

60+
Active Providers
500+
Models
<4s
Response Time

How CoolRouter Works

CoolRouter bridges Web2 oracles with Web3 smart contracts through a decentralized voting mechanism that ensures response validity and consensus before delivering results to your contract.

CoolRouter Architecture Diagram showing the flow from caller contract through router to oracles and back

Web3 Side (On-Chain)

  • • Your smart contract initiates LLM requests
  • • Router contract manages the request lifecycle
  • • Automatic callback invocation - no polling needed

Web2 Side (Off-Chain)

  • • Decentralized oracle network processes requests
  • • Multiple oracles vote on responses
  • • Consensus ensures valid, tamper-proof results

Use Cases for AI × Blockchains

CoolRouter unlocks powerful AI capabilities for on-chain applications. Here are real-world use cases where combining LLMs with smart contracts creates entirely new possibilities.

Background Checks

Background checks before lending and insurance

Use AI to analyze credit histories, social profiles, and on-chain transaction patterns before approving DeFi loans or insurance policies. Smart contracts can automatically adjust interest rates or coverage based on AI-assessed risk scores.

DAO Governance

Vote on proposals and analyze them using AI

Enable DAOs to use AI for proposal analysis, risk assessment, and automated voting recommendations. LLMs can summarize complex proposals, identify conflicts with existing rules, and suggest optimal voting strategies based on DAO objectives.

NSFW and Spam Filter

On-Chain filtering system for social dApps

Build decentralized social platforms with AI-powered content moderation. Smart contracts can automatically filter inappropriate content, spam, and scams without centralized control, maintaining community standards through on-chain rules.

RWA Risk Classification

To properly analyze RWA properties upon listing

Automatically assess real-world assets (RWAs) like real estate, vehicles, or commodities before tokenization. AI evaluates market conditions, legal documents, and property characteristics to provide risk scores and suggested valuations on-chain.

NFT Forgery + Plagiarism Detection

To detect forgery of similar looking NFTs and other works

Protect creators and collectors by using AI to detect copied or forged NFT artwork. Smart contracts can automatically flag suspicious listings, verify originality, and maintain authenticity records on-chain before allowing mints or trades.

Job Matchmaking

On-Chain job match-making using AI

Create decentralized job platforms where AI matches freelancers with projects based on skills, reputation, and requirements. Smart contracts handle escrow, milestone payments, and reputation updates automatically based on AI-verified work quality.

Example: Background Check Implementation

Here's how you might implement an AI-powered background check system for DeFi lending:

use anchor_lang::prelude::*;
use coolrouter_cpi::{create_llm_request, Message};

#[program]
pub mod lending_protocol {
    use super::*;
    
    pub fn request_credit_assessment(
        ctx: Context<RequestCreditAssessment>,
        applicant: Pubkey,
        loan_amount: u64,
    ) -> Result<()> {
        let request_id = format!("credit_{}", applicant.to_string());
        
        // Gather on-chain data about the applicant
        let transaction_history = get_transaction_history(&applicant)?;
        let defi_activity = get_defi_positions(&applicant)?;
        
        // Create AI prompt with structured data
        let prompt = format!(
            "Analyze this loan application:\n             Requested Amount: {} SOL\n             Transaction History: {}\n             DeFi Positions: {}\n             \n             Provide a risk score (0-100) and recommended interest rate.",
            loan_amount as f64 / 1e9,
            transaction_history,
            defi_activity
        );
        
        let messages = vec![
            Message {
                role: "system".to_string(),
                content: "You are a DeFi credit analyst. Provide JSON:                          {\"risk_score\": <0-100>, \"interest_rate\": <percentage>}".to_string(),
            },
            Message {
                role: "user".to_string(),
                content: prompt,
            },
        ];
        
        // Request AI analysis with high consensus requirements
        create_llm_request(
            ctx.accounts.request_pda.to_account_info(),
            ctx.accounts.authority.to_account_info(),
            ctx.accounts.lending_program.to_account_info(),
            ctx.accounts.system_program.to_account_info(),
            ctx.accounts.coolrouter_program.key(),
            vec![ctx.accounts.loan_application.to_account_info()],
            request_id,
            "openai".to_string(),
            "gpt-4".to_string(),
            messages,
            5,  // Require 5 oracle votes for financial decisions
            80, // 80% consensus threshold for security
        )?;
        
        Ok(())
    }
    
    // Callback receives AI assessment
    pub fn llm_callback(
        ctx: Context<LLMCallback>,
        request_id: String,
        response: Vec<u8>,
    ) -> Result<()> {
        let loan_application = &mut ctx.accounts.loan_application;
        
        // Parse AI response
        let response_str = String::from_utf8(response)
            .map_err(|_| ErrorCode::InvalidResponse)?;
        let assessment: CreditAssessment = serde_json::from_str(&response_str)
            .map_err(|_| ErrorCode::InvalidJsonResponse)?;
        
        // Update loan application with AI-determined terms
        loan_application.risk_score = assessment.risk_score;
        loan_application.approved_interest_rate = assessment.interest_rate;
        loan_application.status = if assessment.risk_score < 60 {
            LoanStatus::Approved
        } else {
            LoanStatus::RequiresManualReview
        };
        
        msg!("Credit assessment complete: Risk={}, Rate={}%", 
             assessment.risk_score, assessment.interest_rate);
        
        Ok(())
    }
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct CreditAssessment {
    pub risk_score: u8,
    pub interest_rate: u16, // Basis points
}

Example: DAO Proposal Analysis

Automate governance by having AI analyze and summarize proposals:

pub fn analyze_proposal(
    ctx: Context<AnalyzeProposal>,
    proposal_id: u64,
    proposal_text: String,
) -> Result<()> {
    let request_id = format!("dao_proposal_{}", proposal_id);
    
    // Get DAO context
    let dao_rules = get_dao_constitution(&ctx.accounts.dao_state)?;
    let past_proposals = get_recent_proposals(&ctx.accounts.dao_state, 10)?;
    
    let messages = vec![
        Message {
            role: "system".to_string(),
            content: format!(
                "You are a DAO governance analyst. DAO Rules: {}\n                 Recent Context: {}\n                 Provide analysis in JSON: {{\"summary\": string,                  \"conflicts\": [string], \"recommendation\": string,                  \"priority\": \"low|medium|high\"}}",
                dao_rules,
                past_proposals
            ),
        },
        Message {
            role: "user".to_string(),
            content: format!("Analyze this proposal:\n{}", proposal_text),
        },
    ];
    
    create_llm_request(
        ctx.accounts.request_pda.to_account_info(),
        ctx.accounts.authority.to_account_info(),
        ctx.accounts.dao_program.to_account_info(),
        ctx.accounts.system_program.to_account_info(),
        ctx.accounts.coolrouter_program.key(),
        vec![ctx.accounts.proposal_state.to_account_info()],
        request_id,
        "anthropic".to_string(),
        "claude-3-opus".to_string(),
        messages,
        3,
        66,
    )?;
    
    Ok(())
}

pub fn llm_callback(
    ctx: Context<DAOCallback>,
    request_id: String,
    response: Vec<u8>,
) -> Result<()> {
    let proposal = &mut ctx.accounts.proposal_state;
    
    let analysis: ProposalAnalysis = serde_json::from_slice(&response)
        .map_err(|_| ErrorCode::InvalidResponse)?;
    
    // Store AI analysis on-chain
    proposal.ai_summary = analysis.summary;
    proposal.potential_conflicts = analysis.conflicts;
    proposal.ai_recommendation = analysis.recommendation;
    proposal.priority_level = analysis.priority;
    proposal.analysis_complete = true;
    
    // Emit event for DAO members
    emit!(ProposalAnalyzed {
        proposal_id: proposal.id,
        priority: analysis.priority,
        has_conflicts: !analysis.conflicts.is_empty(),
    });
    
    Ok(())
}

Example: Content Moderation

Build safe social dApps with AI-powered content filtering:

pub fn moderate_content(
    ctx: Context<ModerateContent>,
    post_id: String,
    content: String,
) -> Result<()> {
    let request_id = format!("moderate_{}", post_id);
    
    let messages = vec![
        Message {
            role: "system".to_string(),
            content: "You are a content moderator. Classify content as:                      {\"safe\": bool, \"categories\": [string],                      \"confidence\": <0-100>}. Categories: spam, nsfw,                      hate_speech, scam, violence, none.".to_string(),
        },
        Message {
            role: "user".to_string(),
            content: format!("Moderate this post:\n{}", content),
        },
    ];
    
    create_llm_request(
        ctx.accounts.request_pda.to_account_info(),
        ctx.accounts.authority.to_account_info(),
        ctx.accounts.social_program.to_account_info(),
        ctx.accounts.system_program.to_account_info(),
        ctx.accounts.coolrouter_program.key(),
        vec![ctx.accounts.post_state.to_account_info()],
        request_id,
        "openai".to_string(),
        "gpt-4".to_string(),
        messages,
        4,  // Multiple oracles for accuracy
        75, // High consensus for moderation
    )?;
    
    Ok(())
}

pub fn llm_callback(
    ctx: Context<ModerationCallback>,
    request_id: String,
    response: Vec<u8>,
) -> Result<()> {
    let post = &mut ctx.accounts.post_state;
    
    let moderation: ModerationResult = serde_json::from_slice(&response)?;
    
    post.is_safe = moderation.safe;
    post.moderation_categories = moderation.categories;
    post.confidence_score = moderation.confidence;
    
    // Automatically hide unsafe content
    if !moderation.safe && moderation.confidence > 80 {
        post.visibility = Visibility::Hidden;
        emit!(ContentHidden {
            post_id: post.id.clone(),
            reason: moderation.categories[0].clone(),
        });
    }
    
    Ok(())
}

Why CoolRouter for These Use Cases?

  • Structured Outputs: Get JSON responses that map directly to your Rust structs for easy parsing and validation
  • Decentralized Validation: Oracle voting ensures AI responses are consistent and tamper-proof
  • Automatic Callbacks: No polling - your contract is invoked as soon as consensus is reached
  • High Security: Adjustable voting parameters let you require strong consensus for financial or critical decisions
  • Provider Flexibility: Switch between 60+ providers and 500+ models to find the best performance/cost balance

Installation

Add the CoolRouter CPI library to your Cargo.toml:

[dependencies]
coolrouter-cpi = "0.1.0"
anchor-lang = "0.29.0"

Quick Start

Here's a minimal example to get you started with CoolRouter:

use anchor_lang::prelude::*;
use coolrouter_cpi::{create_llm_request, Message};

#[program]
pub mod my_program {
    use super::*;

    pub fn ask_ai(
        ctx: Context<AskAI>,
        request_id: String,
        prompt: String,
    ) -> Result<()> {
        let messages = vec![Message {
            role: "user".to_string(),
            content: prompt,
        }];
        
        create_llm_request(
            ctx.accounts.request_pda.to_account_info(),
            ctx.accounts.authority.to_account_info(),
            ctx.accounts.my_program.to_account_info(),
            ctx.accounts.system_program.to_account_info(),
            ctx.accounts.coolrouter_program.key(),
            vec![ctx.accounts.response_storage.to_account_info()],
            request_id,
            "openai".to_string(),
            "gpt-4".to_string(),
            messages,
            3,  // min_votes
            66, // approval_threshold (66%)
        )?;
        
        Ok(())
    }
    
    // CoolRouter will call this function automatically
    pub fn llm_callback(
        ctx: Context<LLMCallback>,
        request_id: String,
        response: Vec<u8>,
    ) -> Result<()> {
        // Handle the LLM response
        msg!("Received response: {:?}", response);
        Ok(())
    }
}

CPI Library Overview

The coolrouter-cpi crate provides a type-safe interface for making Cross-Program Invocation (CPI) calls to the CoolRouter program. It abstracts away the complexity of account management, instruction serialization, and program invocation.

Key Benefits:
  • Type-safe API with compile-time guarantees
  • No manual account management or PDA derivation
  • Automatic callback invocation (no polling required)
  • Builder pattern for complex scenarios

Core Functions

create_llm_request

The main function for creating LLM requests. This is a convenience wrapper around the builder pattern.

pub fn create_llm_request<'info>(
    request_pda: AccountInfo<'info>,
    authority: AccountInfo<'info>,
    caller_program: AccountInfo<'info>,
    system_program: AccountInfo<'info>,
    coolrouter_program: Pubkey,
    callback_accounts: Vec<AccountInfo<'info>>,
    request_id: String,
    provider: String,
    model_id: String,
    messages: Vec<Message>,
    min_votes: u8,
    approval_threshold: u8,
) -> Result<()>

Parameters

ParameterDescription
request_pdaPDA account for storing the request in CoolRouter
authoritySigner account that pays for the request
caller_programYour program's ID (used for callback)
callback_accountsAccounts to pass to your callback function (max 32)
request_idUnique identifier for this request (max 60 bytes)
providerLLM provider (e.g., "openai", "anthropic")
model_idModel name (e.g., "gpt-4", "claude-3-opus")
messagesArray of messages with roles and content (max 50)
min_votesMinimum oracle votes required (must be > 0)
approval_thresholdVote percentage needed for consensus (1-100)

Builder Pattern

For more control over the request creation, use the builder pattern:

use coolrouter_cpi::CoolRouterCPI;

// Create the builder
let cpi = CoolRouterCPI::new(
    request_pda,
    authority,
    caller_program,
    system_program,
    coolrouter_program_id,
);

// Add callback accounts
let cpi = cpi
    .add_callback_account(account1)
    .add_callback_account(account2)
    .add_callback_accounts(vec![account3, account4]);

// Create the request
cpi.create_request(
    request_id,
    provider,
    model_id,
    messages,
    min_votes,
    approval_threshold,
)?;

Builder Methods

  • new(...) - Creates a new builder instance
  • add_callback_account(account) - Adds a single callback account
  • add_callback_accounts(accounts) - Adds multiple callback accounts
  • create_request(...) - Finalizes and submits the request

Message Structure

Messages follow the standard LLM format with roles and content:

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct Message {
    pub role: String,    // "system", "user", or "assistant"
    pub content: String, // The message content
}

Example Usage

let messages = vec![
    Message {
        role: "system".to_string(),
        content: "You are a helpful assistant for Solana developers.".to_string(),
    },
    Message {
        role: "user".to_string(),
        content: "How do I optimize my transaction for lower fees?".to_string(),
    },
];

CoolRouter Contract Overview

The CoolRouter contract is the core Solana program that manages LLM requests, coordinates oracle voting, and executes callbacks. Understanding how it works will help you build more robust consumer contracts.

Request Lifecycle

  1. 1
    Request CreationYour contract calls create_request via CPI, specifying the LLM provider, model, and voting parameters.
  2. 2
    Oracle VotingMultiple oracles process the request off-chain and submit vote hashes of their responses.
  3. 3
    ConsensusOnce min_votes is reached and approval_threshold is met, voting is complete and a winning hash is selected.
  4. 4
    FulfillmentAn oracle fulfills the request by providing the full response, which CoolRouter validates and forwards to your callback.

Creating Requests

The create_request instruction initializes a new LLM request:

pub fn create_request<'info>(
    ctx: Context<'_, '_, '_, 'info, CreateRequest<'info>>,
    request_id: String,
    provider: String,
    model_id: String,
    messages: Vec<Message>,
    min_votes: u8,
    approval_threshold: u8,
) -> Result<()>

Request Validation

CoolRouter performs several validations:

  • Provider name must be ≤ 64 characters
  • Model ID must be ≤ 64 characters
  • Messages array must contain ≤ 50 messages
  • Callback accounts must be ≤ 32 accounts
  • min_votes must be greater than 0
  • approval_threshold must be between 1 and 100
Important: The request_id is used as a seed for the PDA. Make sure it's unique for each request to avoid account conflicts.

Oracle Voting System

CoolRouter uses a decentralized oracle network to ensure response validity. Oracles vote by submitting hashes of their LLM responses, and consensus is reached when enough oracles agree.

Voting Parameters

min_votes

Minimum number of oracle votes required before consensus can be reached. Higher values increase security but may increase latency.

Recommended: 3-5 for production

approval_threshold

Percentage (1-100) of votes that must agree on the same response hash. Higher thresholds provide stronger consensus but require more agreement.

Recommended: 66-80 for production

Voting Process

pub fn submit_vote(
    ctx: Context<SubmitVote>,
    response_hash: [u8; 32],
) -> Result<()>

Oracles call this instruction to submit their vote. The contract counts votes and automatically transitions to VotingCompleted status when consensus is reached.

Fulfilling Requests

After voting is complete, an oracle fulfills the request by providing the full response. CoolRouter validates that the response matches the winning hash and then invokes your callback.

pub fn fulfill_request<'info>(
    ctx: Context<'_, '_, '_, 'info, FulfillRequest<'info>>,
    response: Vec<u8>,
) -> Result<()>

Validation Steps

  1. 1. Verify voting is completed and a winning hash exists
  2. 2. Hash the provided response and compare to winning hash
  3. 3. Verify callback program matches the original caller
  4. 4. Verify callback accounts match the original request
  5. 5. Invoke the callback with the validated response
Security: The hash validation ensures that the oracle cannot provide a different response than what was voted on, making the system tamper-proof.

Consumer Contract Setup

Here's a complete example of a consumer contract with proper state management:

use anchor_lang::prelude::*;
use coolrouter_cpi::{create_llm_request, Message};

declare_id!("YourProgramIDHere");

const MAX_REQUEST_ID_LEN: usize = 60;
const MAX_RESPONSE_LEN: usize = 2000;

const ACCOUNT_SPACE: usize = 8 + (4 + MAX_REQUEST_ID_LEN) 
    + (4 + MAX_RESPONSE_LEN) + 1 + 32;

#[program]
pub mod my_consumer {
    use super::*;
    
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let state = &mut ctx.accounts.state;
        state.authority = ctx.accounts.authority.key();
        Ok(())
    }
}

Making Requests

Implement a request instruction that calls CoolRouter:

pub fn request_llm_response(
    ctx: Context<RequestLLMResponse>,
    request_id: String,
    prompt: String,
    min_votes: u8,
    approval_threshold: u8,
) -> Result<()> {
    let consumer_state = &mut ctx.accounts.consumer_state;
    
    // Validate inputs
    require!(
        request_id.len() <= MAX_REQUEST_ID_LEN,
        ErrorCode::RequestIdTooLong
    );
    require!(min_votes > 0, ErrorCode::InvalidMinVotes);
    require!(
        approval_threshold > 0 && approval_threshold <= 100,
        ErrorCode::InvalidApprovalThreshold
    );
    
    // Initialize state
    consumer_state.request_id = request_id.clone();
    consumer_state.response = Vec::new();
    consumer_state.has_response = false;
    consumer_state.authority = ctx.accounts.authority.key();
    
    // Prepare messages
    let messages = vec![Message {
        role: "user".to_string(),
        content: prompt,
    }];
    
    // Specify callback accounts
    let callback_accounts = vec![
        ctx.accounts.consumer_state.to_account_info(),
    ];
    
    // Call CoolRouter
    create_llm_request(
        ctx.accounts.request_pda.to_account_info(),
        ctx.accounts.authority.to_account_info(),
        ctx.accounts.consumer_program.to_account_info(),
        ctx.accounts.system_program.to_account_info(),
        ctx.accounts.coolrouter_program.key(),
        callback_accounts,
        request_id,
        "openai".to_string(),
        "gpt-4".to_string(),
        messages,
        min_votes,
        approval_threshold,
    )?;
    
    msg!("LLM request created");
    Ok(())
}

Handling Callbacks

The callback function is where CoolRouter delivers the validated LLM response. This function MUST be named llm_callback and must accept the request_id and response parameters:

pub fn llm_callback(
    ctx: Context<LLMCallback>,
    request_id: String,
    response: Vec<u8>,
) -> Result<()> {
    let consumer_state = &mut ctx.accounts.consumer_state;
    
    // Verify this is the expected request
    require!(
        consumer_state.request_id == request_id,
        ErrorCode::RequestIdMismatch
    );
    
    // Validate response size
    require!(
        response.len() <= MAX_RESPONSE_LEN,
        ErrorCode::ResponseTooLarge
    );
    
    // Store the response
    consumer_state.response = response.clone();
    consumer_state.has_response = true;
    
    // Emit event
    emit!(ResponseReceived {
        request_id,
        response_preview: String::from_utf8_lossy(&response[..100.min(response.len())]).to_string(),
    });
    
    msg!("LLM response received and stored");
    Ok(())
}
No Polling Required: Unlike traditional oracle solutions, CoolRouter automatically invokes your callback when the response is ready. You don't need to implement any polling mechanism or manual account checking.

State Management

Design your consumer state to track request status and store responses:

#[account]
pub struct ConsumerState {
    pub request_id: String,     // The unique request identifier
    pub response: Vec<u8>,      // The LLM response bytes
    pub has_response: bool,     // Whether response has been received
    pub authority: Pubkey,      // Owner of this request
}

#[derive(Accounts)]
#[instruction(request_id: String)]
pub struct RequestLLMResponse<'info> {
    #[account(
        init,
        payer = authority,
        space = ACCOUNT_SPACE,
        seeds = [b"consumer_state", authority.key().as_ref(), request_id.as_bytes()],
        bump
    )]
    pub consumer_state: Account<'info, ConsumerState>,
    
    #[account(mut)]
    pub authority: Signer<'info>,
    
    /// CHECK: PDA for the request in CoolRouter
    #[account(mut)]
    pub request_pda: AccountInfo<'info>,
    
    /// CHECK: This program's ID
    pub consumer_program: AccountInfo<'info>,
    
    /// CHECK: The CoolRouter program
    pub coolrouter_program: AccountInfo<'info>,
    
    pub system_program: Program<'info, System>,
}

Reading Responses

Implement a getter function for users to retrieve their responses:

pub fn get_response(ctx: Context<GetResponse>) -> Result<Vec<u8>> {
    let consumer_state = &ctx.accounts.consumer_state;
    
    // Verify authority
    require_keys_eq!(
        consumer_state.authority,
        ctx.accounts.authority.key(),
        ErrorCode::Unauthorized
    );
    
    // Check if response exists
    require!(consumer_state.has_response, ErrorCode::NoResponse);
    
    Ok(consumer_state.response.clone())
}

Voting Parameters Guide

Choosing the right voting parameters is crucial for balancing security, cost, and latency.

min_votes

ValueSecurityUse Case
1-2LowDevelopment, testing, non-critical applications
3-5GoodProduction apps, most use cases
6-10HighHigh-value transactions, critical decisions

approval_threshold

PercentageConsensusDescription
51%Simple MajorityFastest but less robust
66%SupermajorityRecommended for most applications
80-90%Strong ConsensusMaximum security, slower convergence

Callback Accounts

Callback accounts are the accounts that CoolRouter will pass to your llm_callback instruction. These typically include your state accounts that need to be updated with the response.

Limits & Constraints

  • Maximum 32 callback accounts per request
  • Accounts must be passed in the same order to fulfill_request
  • Writable flags must match between request creation and fulfillment
  • All accounts must exist and be valid at fulfillment time

Example: Multiple Callback Accounts

let callback_accounts = vec![
    ctx.accounts.consumer_state.to_account_info(),
    ctx.accounts.user_profile.to_account_info(),
    ctx.accounts.analytics.to_account_info(),
];

create_llm_request(
    // ... other params
    callback_accounts,
    // ... other params
)?;

Your callback will receive all these accounts and can update them as needed:

#[derive(Accounts)]
pub struct LLMCallback<'info> {
    #[account(mut)]
    pub consumer_state: Account<'info, ConsumerState>,
    
    #[account(mut)]
    pub user_profile: Account<'info, UserProfile>,
    
    #[account(mut)]
    pub analytics: Account<'info, Analytics>,
}

Error Handling

Implement robust error handling in your consumer contract:

#[error_code]
pub enum ErrorCode {
    #[msg("Request ID does not match")]
    RequestIdMismatch,
    
    #[msg("No response available yet")]
    NoResponse,
    
    #[msg("Request ID exceeds 60 bytes")]
    RequestIdTooLong,
    
    #[msg("Response exceeds 2000 bytes")]
    ResponseTooLarge,
    
    #[msg("Unauthorized: caller is not the request authority")]
    Unauthorized,
    
    #[msg("Minimum votes must be greater than 0")]
    InvalidMinVotes,
    
    #[msg("Approval threshold must be between 1 and 100")]
    InvalidApprovalThreshold,
}

CoolRouter Errors

Familiarize yourself with errors from the CoolRouter contract:

  • TooManyAccounts - Exceeded 32 callback accounts
  • VotingClosed - Attempted to vote after voting completed
  • OracleAlreadyVoted - Oracle tried to vote twice
  • VotingNotCompleted - Tried to fulfill before consensus
  • ResponseHashMismatch - Fulfillment response doesn't match winning hash
  • CallbackProgramMismatch - Wrong program trying to fulfill

Best Practices

1. Use Unique Request IDs

Generate unique request IDs to avoid PDA collisions:

let clock = Clock::get()?;
let request_id = format!(
    "req_{}_{}",
    ctx.accounts.authority.key(),
    clock.unix_timestamp
);

2. Validate Response Size

Set appropriate size limits based on your use case:

const MAX_RESPONSE_LEN: usize = 2000; // Adjust as needed

require!(
    response.len() <= MAX_RESPONSE_LEN,
    ErrorCode::ResponseTooLarge
);

3. Emit Events

Emit events for off-chain tracking and analytics:

#[event]
pub struct ResponseReceived {
    pub request_id: String,
    pub response_preview: String,
}

emit!(ResponseReceived {
    request_id,
    response_preview: preview_string,
});

4. Handle Binary Responses

Responses are Vec<u8> - handle both text and binary:

let response_str = String::from_utf8(response.clone())
    .unwrap_or_else(|_| {
        format!("[Binary data: {} bytes]", response.len())
    });

5. Use Production Parameters

For production, use secure voting parameters:

const PRODUCTION_MIN_VOTES: u8 = 3;
const PRODUCTION_THRESHOLD: u8 = 66; // 66%

create_llm_request(
    // ... other params
    PRODUCTION_MIN_VOTES,
    PRODUCTION_THRESHOLD,
)?;

Types & Structs

Message

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct Message {
    pub role: String,
    pub content: String,
}

LLMRequest

#[account]
pub struct LLMRequest {
    pub id: String,
    pub caller_program: Pubkey,
    pub provider: String,
    pub model_id: String,
    pub callback_accounts: Vec<Pubkey>,
    pub callback_writable: Vec<bool>,
    pub status: RequestStatus,
    pub created_at: i64,
    pub min_votes: u8,
    pub approval_threshold: u8,
    pub votes: Vec<OracleVote>,
    pub winning_hash: Option<[u8; 32]>,
    pub total_votes_cast: u8,
}

RequestStatus

#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum RequestStatus {
    Pending,           // Voting in progress
    VotingCompleted,   // Consensus reached, awaiting fulfillment
    Fulfilled,         // Response delivered to callback
}

OracleVote

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct OracleVote {
    pub oracle: Pubkey,
    pub response_hash: [u8; 32],
}

Error Codes

CoolRouter Errors

ErrorDescription
TooManyAccountsExceeded 32 callback accounts
AccountMismatchCallback account mismatch during fulfillment
VotingClosedVoting is closed or request already fulfilled
CallbackProgramMismatchCallback program does not match original caller
AccountCountMismatchNumber of accounts doesn't match request
ProviderTooLongProvider name exceeds 64 characters
ModelIdTooLongModel ID exceeds 64 characters
TooManyMessagesExceeded 50 messages in request
InvalidMinVotesmin_votes must be greater than 0
InvalidApprovalThresholdapproval_threshold must be between 1-100
OracleAlreadyVotedOracle has already submitted a vote
TooManyVotesExceeded 32 oracle votes
VotingNotCompletedTried to fulfill before consensus reached
NoWinningHashNo winning hash available
ResponseHashMismatchResponse hash doesn't match winning hash

Events

CoolRouter emits the following events for off-chain tracking:

RequestCreated

#[event]
pub struct RequestCreated {
    pub request_id: String,
    pub caller_program: Pubkey,
    pub provider: String,
    pub model_id: String,
    pub messages: Vec<Message>,
    pub min_votes: u8,
    pub approval_threshold: u8,
}

VotingCompleted

#[event]
pub struct VotingCompleted {
    pub request_id: String,
    pub winning_hash: [u8; 32],
    pub vote_count: u8,
    pub total_votes: u8,
}

RequestFulfilled

#[event]
pub struct RequestFulfilled {
    pub request_id: String,
    pub response_length: u64,
}

Constants & Limits

ConstantValueDescription
MAX_CALLBACK_ACCOUNTS32Maximum callback accounts per request
MAX_ORACLES32Maximum oracle votes per request
MAX_REQUEST_ID_LEN60 bytesMaximum length for request IDs
MAX_PROVIDER_LEN64 charsMaximum length for provider names
MAX_MODEL_ID_LEN64 charsMaximum length for model IDs
MAX_MESSAGES50Maximum messages in a request
Tip: These limits are designed to keep transactions within Solana's compute budget while providing ample flexibility for most use cases. Plan your state account sizes accordingly.

Ready to Build?

Start integrating CoolRouter into your Solana dApp today