riglr_solana_tools/lib.rs
1//! # riglr-solana-tools
2//!
3//! A comprehensive suite of rig-compatible tools for interacting with the Solana blockchain.
4//!
5//! This crate provides ready-to-use tools for building Solana-native AI agents, including:
6//!
7//! - **Balance Tools**: Check SOL and SPL token balances
8//! - **Transaction Tools**: Send SOL and token transfers
9//! - **DeFi Tools**: Interact with Jupiter for swaps and quotes
10//! - **Pump.fun Tools**: Deploy, buy, and sell tokens on Pump.fun
11//! - **Network Tools**: Query blockchain state and transaction details
12//!
13//! All tools are built with the `#[tool]` macro for seamless integration with rig agents
14//! and include comprehensive error handling and retry logic.
15//!
16//! ## Features
17//!
18//! - **Production Ready**: Built-in retry logic, timeouts, and error handling
19//! - **Type Safe**: Full Rust type safety with serde and schemars integration
20//! - **Async First**: Non-blocking operations using tokio
21//! - **Composable**: Mix and match tools as needed for your agent
22//! - **Well Documented**: Every tool includes usage examples
23//!
24//! ## Quick Start
25//!
26//! ```ignore
27//! use riglr_solana_tools::balance::get_sol_balance;
28//! use riglr_core::provider::ApplicationContext;
29//! use riglr_core::{ToolWorker, ExecutionConfig, Job, idempotency::InMemoryIdempotencyStore};
30//! use riglr_config::Config;
31//! use solana_client::rpc_client::RpcClient;
32//! use std::sync::Arc;
33//!
34//! # async fn example() -> anyhow::Result<()> {
35//! // Set up ApplicationContext with Solana RPC client
36//! let config = Config::from_env();
37//! let context = ApplicationContext::from_config(&config);
38//! let solana_client = Arc::new(RpcClient::new("https://api.mainnet-beta.solana.com"));
39//! context.set_extension(solana_client);
40//!
41//! // Create and register tools with worker
42//! let worker = ToolWorker::<InMemoryIdempotencyStore>::new(
43//! ExecutionConfig::default(),
44//! context
45//! );
46//!
47//! worker.register_tool(Arc::new(get_sol_balance)).await;
48//!
49//! // Execute the tool
50//! let job = Job::new(
51//! "get_sol_balance",
52//! &serde_json::json!({"address": "So11111111111111111111111111111111111111112"}),
53//! 3
54//! )?;
55//!
56//! let result = worker.process_job(job).await?;
57//! println!("Balance result: {:?}", result);
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! ## Tool Categories
63//!
64//! - [`balance`] - Balance checking tools for SOL and SPL tokens
65//! - [`transaction`] - Transaction creation and execution tools
66//! - [`swap`] - Jupiter DEX integration for token swaps
67//! - [`pump`] - Pump.fun integration for meme token deployment and trading
68//! - [`network`] - Network state and blockchain query tools
69
70pub mod balance;
71pub mod clients;
72pub mod common;
73pub mod error;
74pub mod network;
75pub mod pump;
76pub mod signer;
77pub mod swap;
78pub mod transaction;
79pub mod utils;
80
81// Re-export commonly used tools
82pub use balance::*;
83pub use network::*;
84pub use pump::*;
85pub use signer::*;
86pub use swap::*;
87pub use transaction::*;
88
89// Re-export specific utilities to avoid ambiguous glob conflicts
90pub use utils::{
91 execute_solana_transaction, send_transaction, send_transaction_with_retry, validate_address,
92 TransactionConfig, TransactionSubmissionResult,
93};
94// Note: generate_mint_keypair and create_token_with_mint_keypair are not re-exported
95// at the top level to avoid conflicts. Use utils::generate_mint_keypair directly.
96
97// Re-export error types
98pub use error::SolanaToolError;
99
100// Re-export signer types for convenience
101pub use riglr_core::{signer::UnifiedSigner, SignerContext};
102
103/// Current version of riglr-solana-tools
104pub const VERSION: &str = env!("CARGO_PKG_VERSION");
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_version_is_valid() {
112 // VERSION should be a valid semantic version string
113 assert!(!VERSION.is_empty(), "VERSION should not be empty");
114 assert!(
115 VERSION.contains('.'),
116 "VERSION should contain dots (semantic versioning)"
117 );
118 // Verify it's a proper version format (at least X.Y.Z)
119 let parts: Vec<&str> = VERSION.split('.').collect();
120 assert!(
121 parts.len() >= 3,
122 "VERSION should have at least 3 parts (major.minor.patch)"
123 );
124 // Each part should be numeric (for basic semantic versioning)
125 for (i, part) in parts.iter().take(3).enumerate() {
126 // Remove any pre-release or build metadata for the first 3 parts
127 let clean_part = part.split('-').next().unwrap().split('+').next().unwrap();
128 assert!(
129 clean_part.chars().all(|c| c.is_ascii_digit()),
130 "VERSION part {} should be numeric, got: {}",
131 i,
132 clean_part
133 );
134 }
135 }
136
137 #[test]
138 fn test_version_constant_accessible() {
139 // Test that VERSION constant can be accessed and assigned
140 let version_copy = VERSION;
141 assert_eq!(version_copy, VERSION);
142 }
143
144 #[test]
145 fn test_module_declarations_exist() {
146 // Verify that all declared modules can be referenced
147 // This test ensures the module declarations are valid and the modules exist
148
149 // Test that we can access module paths (compilation test)
150 let _balance_module = std::any::type_name::<balance::BalanceResult>();
151 let _error_module = std::any::type_name::<error::SolanaToolError>();
152 // Note: Tool-generated Args types are in different namespace after macro changes
153 let _signer_module = std::any::type_name::<signer::LocalSolanaSigner>();
154
155 // These should compile without errors if modules exist
156 }
157
158 #[test]
159 fn test_re_exported_types_accessible() {
160 // Test that re-exported types from balance module are accessible
161 // Note: Tool-generated Args types are in different namespace after macro changes
162 // We can still verify that the tool functions themselves are accessible
163 let _balance_result = std::any::type_name::<balance::BalanceResult>();
164
165 // Test that re-exported types from transaction module are accessible
166 let _transaction_result = std::any::type_name::<utils::TransactionSubmissionResult>();
167 let _transaction_config = std::any::type_name::<utils::TransactionConfig>();
168
169 // Test that re-exported types from signer module are accessible
170 let _local_solana_signer = std::any::type_name::<signer::LocalSolanaSigner>();
171 }
172
173 #[test]
174 fn test_utils_re_exports_accessible() {
175 // Test that specific utils re-exports are accessible by type name
176 let _transaction_config = std::any::type_name::<TransactionConfig>();
177 let _transaction_result = std::any::type_name::<TransactionSubmissionResult>();
178 }
179
180 #[test]
181 fn test_error_re_exports() {
182 // Test that error re-exports work
183 let _solana_tool_error = std::any::type_name::<SolanaToolError>();
184 }
185
186 #[test]
187 fn test_riglr_core_re_exports() {
188 // Test that riglr-core re-exports are accessible
189 let _unified_signer = std::any::type_name::<dyn UnifiedSigner>();
190 let _signer_context = std::any::type_name::<SignerContext>();
191 }
192
193 #[test]
194 fn test_version_matches_cargo_pkg_version() {
195 // VERSION should match the CARGO_PKG_VERSION environment variable
196 // This is a compile-time guarantee, but we test the behavior
197 let version = VERSION;
198
199 // Basic validation that it looks like a version
200 assert!(
201 !version.is_empty() && version.contains('.'),
202 "VERSION should be a non-empty version string with dots"
203 );
204
205 // Test that VERSION is a static string
206 let version_ref: &'static str = VERSION;
207 assert_eq!(version_ref, VERSION);
208 }
209
210 #[test]
211 fn test_crate_documentation_constants() {
212 // Test that the crate has the expected structure based on documentation
213 // This validates that the public API matches what's documented
214
215 // These should be accessible as documented in the crate docs
216 let _version: &'static str = VERSION;
217
218 // Verify the main re-exported modules exist by checking their types
219 let _balance_exists = std::any::type_name::<balance::BalanceResult>();
220 // Note: Tool-generated Args types are in different namespace after macro changes
221 let _signer_exists = std::any::type_name::<signer::LocalSolanaSigner>();
222 let _error_exists = std::any::type_name::<error::SolanaToolError>();
223 }
224}