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.
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.

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.
- 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
| Parameter | Description |
|---|---|
request_pda | PDA account for storing the request in CoolRouter |
authority | Signer account that pays for the request |
caller_program | Your program's ID (used for callback) |
callback_accounts | Accounts to pass to your callback function (max 32) |
request_id | Unique identifier for this request (max 60 bytes) |
provider | LLM provider (e.g., "openai", "anthropic") |
model_id | Model name (e.g., "gpt-4", "claude-3-opus") |
messages | Array of messages with roles and content (max 50) |
min_votes | Minimum oracle votes required (must be > 0) |
approval_threshold | Vote 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 instanceadd_callback_account(account)- Adds a single callback accountadd_callback_accounts(accounts)- Adds multiple callback accountscreate_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
- 1Request CreationYour contract calls create_request via CPI, specifying the LLM provider, model, and voting parameters.
- 2Oracle VotingMultiple oracles process the request off-chain and submit vote hashes of their responses.
- 3ConsensusOnce min_votes is reached and approval_threshold is met, voting is complete and a winning hash is selected.
- 4FulfillmentAn 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
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.
approval_threshold
Percentage (1-100) of votes that must agree on the same response hash. Higher thresholds provide stronger consensus but require more agreement.
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. Verify voting is completed and a winning hash exists
- 2. Hash the provided response and compare to winning hash
- 3. Verify callback program matches the original caller
- 4. Verify callback accounts match the original request
- 5. Invoke the callback with the validated response
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(())
}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
| Value | Security | Use Case |
|---|---|---|
| 1-2 | Low | Development, testing, non-critical applications |
| 3-5 | Good | Production apps, most use cases |
| 6-10 | High | High-value transactions, critical decisions |
approval_threshold
| Percentage | Consensus | Description |
|---|---|---|
| 51% | Simple Majority | Fastest but less robust |
| 66% | Supermajority | Recommended for most applications |
| 80-90% | Strong Consensus | Maximum 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 accountsVotingClosed- Attempted to vote after voting completedOracleAlreadyVoted- Oracle tried to vote twiceVotingNotCompleted- Tried to fulfill before consensusResponseHashMismatch- Fulfillment response doesn't match winning hashCallbackProgramMismatch- 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
| Error | Description |
|---|---|
| TooManyAccounts | Exceeded 32 callback accounts |
| AccountMismatch | Callback account mismatch during fulfillment |
| VotingClosed | Voting is closed or request already fulfilled |
| CallbackProgramMismatch | Callback program does not match original caller |
| AccountCountMismatch | Number of accounts doesn't match request |
| ProviderTooLong | Provider name exceeds 64 characters |
| ModelIdTooLong | Model ID exceeds 64 characters |
| TooManyMessages | Exceeded 50 messages in request |
| InvalidMinVotes | min_votes must be greater than 0 |
| InvalidApprovalThreshold | approval_threshold must be between 1-100 |
| OracleAlreadyVoted | Oracle has already submitted a vote |
| TooManyVotes | Exceeded 32 oracle votes |
| VotingNotCompleted | Tried to fulfill before consensus reached |
| NoWinningHash | No winning hash available |
| ResponseHashMismatch | Response 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
| Constant | Value | Description |
|---|---|---|
| MAX_CALLBACK_ACCOUNTS | 32 | Maximum callback accounts per request |
| MAX_ORACLES | 32 | Maximum oracle votes per request |
| MAX_REQUEST_ID_LEN | 60 bytes | Maximum length for request IDs |
| MAX_PROVIDER_LEN | 64 chars | Maximum length for provider names |
| MAX_MODEL_ID_LEN | 64 chars | Maximum length for model IDs |
| MAX_MESSAGES | 50 | Maximum messages in a request |
Ready to Build?
Start integrating CoolRouter into your Solana dApp today