riglr-solana-tools

Crates.ioriglr-solana-tools
lib.rsriglr-solana-tools
version0.3.0
created_at2025-09-10 19:48:30.833489+00
updated_at2025-09-10 19:48:30.833489+00
descriptionA suite of rig-compatible tools for interacting with the Solana blockchain
homepagehttps://github.com/riglr/riglr
repositoryhttps://github.com/riglr/riglr
max_upload_size
id1832951
size721,682
J.Warp (jwarp-riglr)

documentation

https://docs.rs/riglr-solana-tools

README

riglr-solana-tools

Production-grade Solana blockchain tools for riglr agents, providing comprehensive Solana network interactions.

Crates.io Documentation

Quick Start

Using LocalSolanaSigner

The LocalSolanaSigner is the primary concrete implementation for Solana transaction signing:

use riglr_solana_tools::LocalSolanaSigner;
use riglr_core::{ApplicationContext, UnifiedSigner};
use std::sync::Arc;

// Create a signer from a base58 private key
let signer = LocalSolanaSigner::new_from_base58(
    "your_base58_private_key",
    "https://api.mainnet-beta.solana.com".to_string(),
)?;

// Set up application context
let app_context = ApplicationContext::new()?;
let unified_signer: Arc<dyn UnifiedSigner> = Arc::new(signer);
app_context.set_signer(unified_signer).await?;

// Now you can use any Solana tool
use riglr_solana_tools::transaction::transfer_sol;
let signature = transfer_sol("recipient_address", 1_000_000_000).await?;

Features

  • 🔐 Secure Transaction Management: Thread-safe ApplicationContext with automatic client injection
  • 💰 Balance Operations: Check SOL and SPL token balances with clean #[tool] functions
  • 📤 Token Transfers: Send SOL and SPL tokens with automatic signer context management
  • 🔄 DeFi Integration: Jupiter aggregator for token swaps and liquidity operations
  • 🚀 Pump.fun Integration: Deploy and trade meme tokens on Solana's latest platform
  • High Performance: Async/await with connection pooling and retry logic
  • 🛡️ Error Handling: Type-safe ToolError for distinguishing retriable and permanent failures
  • 🔒 Multi-Tenant Safe: Automatic isolation between concurrent user requests
  • 📖 Clean Architecture: Single #[tool] functions with automatic context injection

Architecture

riglr-solana-tools uses the modern ApplicationContext pattern from riglr-core with automatic dependency injection:

Clean Tool Functions

All tools use the same clean pattern with automatic context injection:

use riglr_core::provider::ApplicationContext;
use riglr_solana_tools::balance::get_sol_balance;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Setup ApplicationContext with Solana client
    let context = ApplicationContext::from_env();
    
    // Tools automatically use context.solana_client()
    let balance = get_sol_balance(
        "So11111111111111111111111111111111111111112".to_string(),
        &context,
    ).await?;
    
    println!("Balance: {} SOL", balance.sol);
    Ok(())
}

Transaction Operations

Transaction tools work with SignerContext automatically detected from ApplicationContext:

use riglr_core::provider::ApplicationContext;
use riglr_solana_tools::transaction::transfer_sol;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let context = ApplicationContext::from_env();
    
    // Tool automatically uses signer context for transactions
    let result = transfer_sol(
        "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM".to_string(),
        0.1, // 0.1 SOL
        Some("Payment".to_string()),
        None,
        &context,
    ).await?;
    
    println!("Transaction signature: {}", result.signature);
    Ok(())
}

Tool Integration

All tools follow the #[tool] macro pattern and integrate seamlessly with ToolWorker:

use riglr_core::{ToolWorker, ExecutionConfig, Job, JobResult};
use riglr_core::provider::ApplicationContext;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let context = ApplicationContext::from_env();
    
    let worker = ToolWorker::new(
        ExecutionConfig::default(),
        context
    );

    // Execute tools via job system
    let job = Job::new("get_sol_balance", &json!({
        "address": "So11111111111111111111111111111111111111112"
    }), 3)?;

    let result: JobResult = worker.process_job(job).await?;
    println!("Job result: {:?}", result);
    Ok(())
}

Tool Implementation Pattern

All tools follow a consistent pattern with ApplicationContext automatic injection:

use riglr_core::provider::ApplicationContext;
use riglr_core::ToolError;
use riglr_macros::tool;
use std::sync::Arc;

#[tool]
pub async fn my_solana_tool(
    param: String,
    context: &ApplicationContext,
) -> Result<MyResult, ToolError> {
    // Get Solana RPC client from context extensions
    let rpc_client = context
        .get_extension::<Arc<solana_client::rpc_client::RpcClient>>()
        .ok_or_else(|| {
            ToolError::permanent_string("Solana RpcClient not found in context".to_string())
        })?;
    
    // Use the client for operations
    let result = rpc_client.get_account(&pubkey)
        .map_err(|e| ToolError::retriable_string(format!("Network error: {}", e)))?;
    
    Ok(MyResult { data: result })
}

Key Points:

  • All tools use context: &ApplicationContext as the last parameter
  • Solana RPC clients are injected via context.get_extension()
  • Transaction tools automatically access SignerContext when needed
  • Error handling distinguishes between retriable and permanent failures

Available Tools

All tools use the new clean signature pattern:

Balance Operations

#[tool]
pub async fn get_sol_balance(
    address: String,
    context: &ApplicationContext,
) -> Result<BalanceResult, ToolError>

#[tool] 
pub async fn get_spl_token_balance(
    owner_address: String,
    mint_address: String,
    context: &ApplicationContext,
) -> Result<TokenBalanceResult, ToolError>

#[tool]
pub async fn get_multiple_balances(
    addresses: Vec<String>,
    context: &ApplicationContext,
) -> Result<Vec<BalanceResult>, ToolError>

Transaction Operations

#[tool]
pub async fn transfer_sol(
    to_address: String,
    amount_sol: f64,
    memo: Option<String>,
    priority_fee: Option<u64>,
    context: &ApplicationContext,
) -> Result<TransactionResult, ToolError>

#[tool]
pub async fn transfer_spl_token(
    mint_address: String,
    to_address: String,
    amount: u64,
    memo: Option<String>,
    context: &ApplicationContext,
) -> Result<TransactionResult, ToolError>

Trading Operations

#[tool]
pub async fn get_jupiter_quote(
    input_mint: String,
    output_mint: String,
    amount: u64,
    slippage_bps: u16,
    only_direct_routes: bool,
    jupiter_api_url: Option<String>,
    context: &ApplicationContext,
) -> Result<SwapQuote, ToolError>

#[tool]
pub async fn perform_jupiter_swap(
    input_mint: String,
    output_mint: String,
    amount: u64,
    slippage_bps: u16,
    jupiter_api_url: Option<String>,
    use_versioned_transaction: bool,
    context: &ApplicationContext,
) -> Result<SwapResult, ToolError>

#[tool]
pub async fn get_token_price(
    base_mint: String,
    quote_mint: String,
    context: &ApplicationContext,
) -> Result<PriceResult, ToolError>

Pump.fun Operations

#[tool]
pub async fn deploy_pump_token(
    name: String,
    symbol: String,
    description: String,
    image_url: Option<String>,
    initial_buy_sol: Option<f64>,
    context: &ApplicationContext,
) -> Result<PumpTokenResult, ToolError>

#[tool]
pub async fn buy_pump_token(
    token_mint: String,
    sol_amount: f64,
    slippage_bps: u16,
    max_sol_cost: Option<f64>,
    context: &ApplicationContext,
) -> Result<PumpTradeResult, ToolError>

#[tool]
pub async fn sell_pump_token(
    token_mint: String,
    token_amount: u64,
    slippage_bps: u16,
    min_sol_output: Option<f64>,
    context: &ApplicationContext,
) -> Result<PumpTradeResult, ToolError>

#[tool]
pub async fn get_pump_token_info(
    token_mint: String,
    context: &ApplicationContext,
) -> Result<PumpTokenInfo, ToolError>

Network Operations

#[tool]
pub async fn get_block_height(
    context: &ApplicationContext,
) -> Result<u64, ToolError>

#[tool]
pub async fn get_transaction_status(
    signature: String,
    context: &ApplicationContext,
) -> Result<TransactionStatusResult, ToolError>

Error Handling

All tools use structured error handling with retry classification and preserve the original error context for downcasting:

Basic Error Handling

use riglr_core::{ToolError, provider::ApplicationContext};
use riglr_solana_tools::balance::get_sol_balance;

async fn handle_balance_check(context: &ApplicationContext, address: String) {
    match get_sol_balance(address, context).await {
        Ok(balance) => println!("Balance: {} SOL", balance.sol),
        Err(ToolError::Retriable { message, .. }) => {
            // Network errors - safe to retry
            eprintln!("Temporary error: {}", message);
        },
        Err(ToolError::Permanent { message, .. }) => {
            // Invalid input or permanent failures  
            eprintln!("Permanent error: {}", message);
        },
    }
}

Structured Error Context Preservation

riglr-solana-tools preserves the original SolanaToolError as the source when converting to ToolError, enabling downcasting for detailed error handling:

use riglr_core::ToolError;
use riglr_solana_tools::error::SolanaToolError;
use riglr_solana_tools::balance::get_sol_balance;

async fn handle_with_downcasting(context: &ApplicationContext, address: String) {
    match get_sol_balance(address, context).await {
        Ok(balance) => println!("Balance: {} SOL", balance.sol),
        Err(tool_error) => {
            // Access the structured error context via downcasting
            if let Some(source) = tool_error.source() {
                if let Some(solana_error) = source.downcast_ref::<SolanaToolError>() {
                    match solana_error {
                        SolanaToolError::InvalidAddress(addr) => {
                            eprintln!("Invalid Solana address format: {}", addr);
                        },
                        SolanaToolError::InsufficientBalance { required, available } => {
                            eprintln!("Need {} SOL but only have {} SOL", required, available);
                        },
                        SolanaToolError::TransactionFailed(msg) => {
                            eprintln!("Transaction failed: {}", msg);
                        },
                        _ => eprintln!("Solana error: {}", solana_error),
                    }
                }
            }
        },
    }
}

This pattern enables:

  • Retry Logic: Use ToolError's classification for retry decisions
  • Detailed Diagnostics: Downcast to SolanaToolError for specific error details
  • Backwards Compatibility: Code using basic error handling continues to work
  • Type Safety: Compile-time guarantees for error handling

Architectural Notes

Conversions Module

The common/conversions module provides type conversion utilities to work around a version mismatch between our Solana SDK (v3.x) and SPL libraries (which bundle v2.x). This is a temporary workaround that will be removed once SPL libraries are updated to use solana-sdk v3.x directly.

The module handles conversions between incompatible but equivalent types, primarily for Pubkey types that exist in both SDK versions. See the module documentation for implementation details.

Configuration

Configure Solana endpoints and API keys via environment variables or riglr-config:

# Solana RPC
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com

# Jupiter API (optional)
JUPITER_API_URL=https://quote-api.jup.ag

# Pump.fun API (optional)  
PUMP_API_URL=https://pumpapi.fun/api

ApplicationContext automatically loads these configurations and injects the appropriate clients into tools.

See riglr-config for complete configuration options.

Migration from Legacy Patterns

If you're upgrading from the dual-API pattern:

Before (Legacy)

// OLD - dual API pattern
get_sol_balance_with_context(address, &app_context).await?;
get_sol_balance(address, rpc_client).await?;

After (Current)

// NEW - clean single API
get_sol_balance(address, &context).await?;

The new pattern:

  • ✅ Single function per tool
  • ✅ Automatic context injection
  • ✅ Cleaner signatures
  • ✅ Better error handling
  • ✅ Consistent across all tools
Commit count: 209

cargo fmt