riglr-solana-tools 0.3.0

A suite of rig-compatible tools for interacting with the Solana blockchain
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# riglr-solana-tools

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

[![Crates.io](https://img.shields.io/crates/v/riglr-solana-tools.svg)](https://crates.io/crates/riglr-solana-tools)
[![Documentation](https://docs.rs/riglr-solana-tools/badge.svg)](https://docs.rs/riglr-solana-tools)

## Quick Start

### Using LocalSolanaSigner

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

```rust
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:

```rust
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:

```rust
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:

```rust
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:

```rust
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
```rust
#[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
```rust
#[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
```rust
#[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
```rust
#[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
```rust
#[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

```rust
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:

```rust
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:

```bash
# 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](../riglr-config) for complete configuration options.

## Migration from Legacy Patterns

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

### Before (Legacy)
```rust
// OLD - dual API pattern
get_sol_balance_with_context(address, &app_context).await?;
get_sol_balance(address, rpc_client).await?;
```

### After (Current)  
```rust
// 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