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

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;
let signer = LocalSolanaSigner::new_from_base58(
"your_base58_private_key",
"https://api.mainnet-beta.solana.com".to_string(),
)?;
let app_context = ApplicationContext::new()?;
let unified_signer: Arc<dyn UnifiedSigner> = Arc::new(signer);
app_context.set_signer(unified_signer).await?;
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>> {
let context = ApplicationContext::from_env();
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();
let result = transfer_sol(
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM".to_string(),
0.1, 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
);
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> {
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())
})?;
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, .. }) => {
eprintln!("Temporary error: {}", message);
},
Err(ToolError::Permanent { message, .. }) => {
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) => {
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_URL=https://api.mainnet-beta.solana.com
JUPITER_API_URL=https://quote-api.jup.ag
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)
get_sol_balance_with_context(address, &app_context).await?;
get_sol_balance(address, rpc_client).await?;
After (Current)
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