riglr_core/signer.rs
1//! Blockchain signer implementations and context management for secure transactions.
2//!
3//! This module provides the SignerContext pattern for thread-safe transaction signing,
4//! along with unified signer abstractions for both EVM and Solana chains.
5//!
6//! # Architecture
7//!
8//! The module implements two key patterns:
9//! - **SignerContext**: Thread-local storage for secure transaction signing
10//! - **Configuration-driven signers**: Type-safe network configuration from riglr-config
11//!
12//! # Usage
13//!
14//! ```ignore
15//! use riglr_config::Config;
16//! use riglr_core::signer::SignerContext;
17//! // Import concrete signers from tools crates
18//! use riglr_solana_tools::LocalSolanaSigner;
19//! use riglr_evm_tools::LocalEvmSigner;
20//! use std::sync::Arc;
21//!
22//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
23//! // Load configuration from environment
24//! let config = Config::from_env()?;
25//!
26//! // Create signers with proper network configuration
27//! // NOTE: Concrete signers are in tools crates
28//! let signer = Arc::new(LocalSolanaSigner::new(
29//! keypair,
30//! config.providers.solana.network_config()
31//! )?);
32//!
33//! // Execute transactional operations within SignerContext
34//! SignerContext::with_signer(signer, async {
35//! // Tools can now access the signer via SignerContext::current()
36//! // for operations that require transaction signing
37//! transfer_sol("recipient", 1.0).await?;
38//! Ok(())
39//! }).await?;
40//! # Ok(())
41//! # }
42//! ```
43
44use std::sync::Arc;
45use tokio::task_local;
46
47pub mod error;
48pub mod granular_traits;
49pub mod traits;
50
51pub use error::SignerError;
52pub use granular_traits::{
53 Chain, EvmSigner, MultiChainSigner, SignerBase, SolanaSigner, UnifiedSigner,
54};
55pub use traits::{EvmClient, SolanaClient};
56
57// Thread-local storage for current signer context
58// This provides secure isolation between different async tasks/requests
59task_local! {
60 static CURRENT_UNIFIED_SIGNER: Arc<dyn UnifiedSigner>;
61}
62
63/// The SignerContext provides thread-local signer management for secure multi-tenant operation.
64///
65/// This enables stateless tools that can access the appropriate signer without explicit passing,
66/// while maintaining strict isolation between different async tasks and users.
67///
68/// ## Security Features
69///
70/// - **Thread isolation**: Each async task has its own isolated signer context
71/// - **No signer leakage**: Contexts cannot access signers from other tasks
72/// - **Safe concurrent access**: Multiple tasks can run concurrently with different signers
73/// - **Automatic cleanup**: Contexts are automatically cleaned up when tasks complete
74///
75/// ## Usage Patterns
76///
77/// ### Basic Usage
78///
79/// ```ignore
80/// use riglr_core::signer::SignerContext;
81/// // Concrete signers are from tools crates:
82/// // use riglr_solana_tools::LocalSolanaSigner;
83/// use std::sync::Arc;
84///
85/// # async fn example() -> Result<(), riglr_core::signer::SignerError> {
86/// let keypair = Keypair::new();
87/// let signer = Arc::new(LocalSolanaSigner::new(
88/// keypair,
89/// "https://api.devnet.solana.com".to_string()
90/// ));
91///
92/// // Execute code with signer context
93/// let result = SignerContext::with_signer(signer, async {
94/// // Inside this scope, tools can access the signer
95/// let current = SignerContext::current().await?;
96/// let user_id = current.user_id();
97/// Ok(format!("Processing for user: {:?}", user_id))
98/// }).await?;
99///
100/// println!("Result: {}", result);
101/// # Ok(())
102/// # }
103/// ```
104///
105/// ### Multi-Tenant Service Example
106///
107/// ```ignore
108/// use riglr_core::signer::{SignerContext, UnifiedSigner};
109/// use std::sync::Arc;
110///
111/// async fn handle_user_request(
112/// user_signer: Arc<dyn UnifiedSigner>,
113/// operation: &str
114/// ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
115/// SignerContext::with_signer(user_signer, async {
116/// // All operations in this scope use the user's signer
117/// match operation {
118/// "balance" => check_balance().await,
119/// "transfer" => perform_transfer().await,
120/// _ => Err(riglr_core::signer::SignerError::NoSignerContext)
121/// }
122/// }).await.map_err(Into::into)
123/// }
124///
125/// async fn check_balance() -> Result<String, riglr_core::signer::SignerError> {
126/// let signer = SignerContext::current().await?;
127/// Ok(format!("Balance for user: {:?}", signer.user_id()))
128/// }
129///
130/// async fn perform_transfer() -> Result<String, riglr_core::signer::SignerError> {
131/// let signer = SignerContext::current().await?;
132/// // Perform actual transfer using signer...
133/// Ok("Transfer completed".to_string())
134/// }
135/// ```
136///
137/// ### Error Handling
138///
139/// Tools should always check for signer availability:
140///
141/// ```rust
142/// use riglr_core::signer::SignerContext;
143///
144/// async fn safe_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
145/// if !SignerContext::is_available().await {
146/// return Err("This operation requires a signer context".into());
147/// }
148///
149/// let signer = SignerContext::current().await?;
150/// // Proceed with operation...
151/// Ok("Operation completed".to_string())
152/// }
153/// ```
154///
155/// ## Security Considerations
156///
157/// - **Never store signers globally**: Always use the context pattern
158/// - **Validate user permissions**: Check that users own the addresses they're operating on
159/// - **Audit all operations**: Log all signer usage for security auditing
160/// - **Use environment-specific endpoints**: Different signers for mainnet/testnet
161pub struct SignerContext;
162
163/// A handle to the current Solana signer, providing type-safe access.
164///
165/// This handle guarantees that the underlying signer supports Solana operations.
166/// It implements `Deref` to `dyn SolanaSigner`, allowing direct access to all
167/// Solana-specific methods.
168///
169/// # Thread Safety
170///
171/// The handle is `Send + Sync` and can be passed between threads safely.
172/// The underlying signer is reference-counted and immutable.
173///
174/// # Examples
175///
176/// ```ignore
177/// use riglr_core::signer::SignerContext;
178///
179/// async fn solana_transfer() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
180/// // Get a type-safe handle to the Solana signer
181/// let signer = SignerContext::current_as_solana().await?;
182///
183/// // Access Solana-specific methods directly
184/// let pubkey = signer.pubkey();
185/// let signature = signer.sign_message(b"Hello").await?;
186///
187/// Ok(())
188/// }
189/// ```
190pub struct SolanaSignerHandle(Arc<dyn UnifiedSigner>);
191
192impl std::ops::Deref for SolanaSignerHandle {
193 type Target = dyn SolanaSigner;
194
195 fn deref(&self) -> &Self::Target {
196 self.0
197 .as_solana()
198 .expect("Signer must support Solana. This is a bug in SignerContext.")
199 }
200}
201
202/// A handle to the current EVM signer, providing type-safe access.
203///
204/// This handle guarantees that the underlying signer supports EVM operations.
205/// It implements `Deref` to `dyn EvmSigner`, allowing direct access to all
206/// EVM-specific methods.
207///
208/// # Thread Safety
209///
210/// The handle is `Send + Sync` and can be passed between threads safely.
211/// The underlying signer is reference-counted and immutable.
212///
213/// # Examples
214///
215/// ```ignore
216/// use riglr_core::signer::SignerContext;
217///
218/// async fn evm_transfer() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
219/// // Get a type-safe handle to the EVM signer
220/// let signer = SignerContext::current_as_evm().await?;
221///
222/// // Access EVM-specific methods directly
223/// let address = signer.address();
224/// let chain_id = signer.chain_id();
225///
226/// // Sign and send a transaction
227/// let tx = TransactionRequest::default();
228/// let tx_hash = signer.sign_and_send_transaction(tx).await?;
229///
230/// Ok(())
231/// }
232/// ```
233pub struct EvmSignerHandle(Arc<dyn UnifiedSigner>);
234
235impl std::ops::Deref for EvmSignerHandle {
236 type Target = dyn EvmSigner;
237
238 fn deref(&self) -> &Self::Target {
239 self.0
240 .as_evm()
241 .expect("Signer must support EVM. This is a bug in SignerContext.")
242 }
243}
244
245impl SignerContext {
246 /// Execute a future with a unified signer context.
247 ///
248 /// This is the primary method for setting up a signer context. It uses the new
249 /// UnifiedSigner trait which provides better type safety and chain-specific access.
250 ///
251 /// # Arguments
252 /// * `signer` - The unified signer to make available in the context
253 /// * `future` - The async code to execute with the signer context
254 ///
255 /// # Examples
256 /// ```ignore
257 /// use riglr_core::signer::{SignerContext, UnifiedSigner};
258 /// use riglr_solana_tools::LocalSolanaSigner;
259 /// use std::sync::Arc;
260 ///
261 /// let signer: Arc<dyn UnifiedSigner> = Arc::new(LocalSolanaSigner::new(
262 /// keypair,
263 /// "https://api.devnet.solana.com".to_string()
264 /// ));
265 ///
266 /// let result = SignerContext::with_signer(signer, async {
267 /// // Access as Solana signer
268 /// let solana = SignerContext::current_as_solana().await?;
269 /// let pubkey = solana.pubkey();
270 /// Ok(pubkey)
271 /// }).await?;
272 /// ```
273 pub async fn with_signer<T, F>(
274 signer: Arc<dyn UnifiedSigner>,
275 future: F,
276 ) -> Result<T, SignerError>
277 where
278 F: std::future::Future<Output = Result<T, SignerError>> + Send,
279 {
280 CURRENT_UNIFIED_SIGNER.scope(signer, future).await
281 }
282
283 /// Get the current unified signer from thread-local context.
284 ///
285 /// This function retrieves the signer that was set by [`SignerContext::with_signer()`].
286 /// Returns the UnifiedSigner which can be cast to specific signer types.
287 ///
288 /// # Returns
289 /// * `Ok(Arc<dyn UnifiedSigner>)` - The current signer if available
290 /// * `Err(SignerError::NoSignerContext)` - If called outside a signer context
291 ///
292 /// # Examples
293 ///
294 /// ```ignore
295 /// use riglr_core::signer::SignerContext;
296 ///
297 /// async fn tool_that_needs_signer() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
298 /// // Get the current unified signer
299 /// let signer = SignerContext::current().await?;
300 ///
301 /// // Check what chains it supports
302 /// if signer.supports_solana() {
303 /// let solana = SignerContext::current_as_solana().await?;
304 /// return Ok(format!("Solana pubkey: {}", solana.pubkey()));
305 /// }
306 ///
307 /// if signer.supports_evm() {
308 /// let evm = SignerContext::current_as_evm().await?;
309 /// return Ok(format!("EVM address: {}", evm.address()));
310 /// }
311 ///
312 /// Ok("Unknown signer type".to_string())
313 /// }
314 /// ```
315 pub async fn current() -> Result<Arc<dyn UnifiedSigner>, SignerError> {
316 CURRENT_UNIFIED_SIGNER
317 .try_with(|signer| signer.clone())
318 .map_err(|_| SignerError::NoSignerContext)
319 }
320
321 /// Check if there is currently a signer context available.
322 ///
323 /// This function returns `true` if the current async task is running within
324 /// a [`SignerContext::with_signer()`] scope, and `false` otherwise.
325 ///
326 /// This is useful for tools that want to provide different behavior when called
327 /// with or without a signer context, such as read-only vs. transactional operations.
328 ///
329 /// # Returns
330 /// * `true` - If a signer context is available
331 /// * `false` - If no signer context is available
332 ///
333 /// # Examples
334 ///
335 /// ```ignore
336 /// use riglr_core::signer::SignerContext;
337 /// // Concrete signers from tools crates:
338 /// // use riglr_solana_tools::LocalSolanaSigner;
339 /// use std::sync::Arc;
340 ///
341 /// async fn flexible_tool() -> Result<String, riglr_core::signer::SignerError> {
342 /// if SignerContext::is_available().await {
343 /// // We have a signer, can perform transactions
344 /// let signer = SignerContext::current().await?;
345 /// Ok("Performing transaction with signer".to_string())
346 /// } else {
347 /// // No signer available, provide read-only functionality
348 /// Ok("Read-only mode: no signer available".to_string())
349 /// }
350 /// }
351 ///
352 /// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
353 /// // Test without signer context
354 /// let result1 = flexible_tool().await.unwrap();
355 /// assert_eq!(result1, "Read-only mode: no signer available");
356 ///
357 /// // Test with signer context
358 /// let keypair = Keypair::new();
359 /// let signer = Arc::new(LocalSolanaSigner::new(
360 /// keypair,
361 /// "https://api.devnet.solana.com".to_string()
362 /// ));
363 ///
364 /// let result2 = SignerContext::with_signer(signer, async {
365 /// flexible_tool().await
366 /// }).await.unwrap();
367 /// assert_eq!(result2, "Performing transaction with signer");
368 ///
369 /// # Ok(())
370 /// # }
371 /// ```
372 ///
373 /// # Performance
374 ///
375 /// This function is very lightweight and can be called frequently without
376 /// performance concerns. It simply checks if the thread-local storage
377 /// contains a signer reference.
378 pub async fn is_available() -> bool {
379 CURRENT_UNIFIED_SIGNER.try_with(|_| ()).is_ok()
380 }
381
382 /// Get the current signer as a specific type.
383 ///
384 /// This method allows type-safe access to chain-specific signer capabilities.
385 /// Tools can require specific signer types and get compile-time guarantees.
386 ///
387 /// # Type Parameters
388 /// * `T` - The specific signer trait to cast to (e.g., `dyn SolanaSigner`, `dyn EvmSigner`)
389 ///
390 /// # Returns
391 /// * `Ok(&T)` - Reference to the signer with the requested capabilities
392 /// * `Err(SignerError::UnsupportedOperation)` - If the current signer doesn't support the requested type
393 /// * `Err(SignerError::NoSignerContext)` - If no signer context is available
394 ///
395 /// # Examples
396 /// ```ignore
397 /// use riglr_core::signer::{SignerContext, SolanaSigner, EvmSigner};
398 ///
399 /// // Require Solana signer
400 /// async fn solana_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
401 /// let signer = SignerContext::current_as::<dyn SolanaSigner>().await?;
402 /// Ok(format!("Solana pubkey: {}", signer.pubkey()))
403 /// }
404 ///
405 /// // Require EVM signer
406 /// async fn evm_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
407 /// let signer = SignerContext::current_as::<dyn EvmSigner>().await?;
408 /// Ok(format!("EVM chain: {}", signer.chain_id()))
409 /// }
410 /// ```
411 ///
412 /// Get the current signer as a Solana signer with type-safe access.
413 ///
414 /// This method provides a strongly-typed handle to the current signer's Solana
415 /// capabilities. The returned handle implements `Deref` to `dyn SolanaSigner`,
416 /// allowing direct access to all Solana-specific methods.
417 ///
418 /// # Returns
419 /// * `Ok(SolanaSignerHandle)` - A handle providing type-safe access to Solana operations
420 /// * `Err(SignerError::NoSignerContext)` - If called outside a signer context
421 /// * `Err(SignerError::UnsupportedOperation)` - If the current signer doesn't support Solana
422 ///
423 /// # Thread Safety
424 ///
425 /// The returned handle is thread-safe and can be passed between async tasks.
426 /// The underlying signer is immutable and reference-counted.
427 ///
428 /// # Examples
429 ///
430 /// ```ignore
431 /// use riglr_core::signer::SignerContext;
432 ///
433 /// async fn solana_only_operation() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
434 /// // This will fail if the current signer doesn't support Solana
435 /// let signer = SignerContext::current_as_solana().await?;
436 ///
437 /// // Now we have type-safe access to Solana methods
438 /// let pubkey = signer.pubkey();
439 /// let client = signer.client();
440 ///
441 /// // Perform Solana-specific operations
442 /// let balance = client.get_balance(&pubkey.parse()?).await?;
443 /// println!("Balance: {} SOL", balance);
444 ///
445 /// Ok(())
446 /// }
447 /// ```
448 pub async fn current_as_solana() -> Result<SolanaSignerHandle, SignerError> {
449 let unified = CURRENT_UNIFIED_SIGNER
450 .try_with(|s| s.clone())
451 .map_err(|_| SignerError::NoSignerContext)?;
452
453 // Check if the signer supports Solana
454 if !unified.supports_solana() {
455 return Err(SignerError::UnsupportedOperation(
456 "Current signer does not support Solana operations".to_string(),
457 ));
458 }
459
460 Ok(SolanaSignerHandle(unified))
461 }
462
463 /// Get the current signer as an EVM signer with type-safe access.
464 ///
465 /// This method provides a strongly-typed handle to the current signer's EVM
466 /// capabilities. The returned handle implements `Deref` to `dyn EvmSigner`,
467 /// allowing direct access to all EVM-specific methods.
468 ///
469 /// # Returns
470 /// * `Ok(EvmSignerHandle)` - A handle providing type-safe access to EVM operations
471 /// * `Err(SignerError::NoSignerContext)` - If called outside a signer context
472 /// * `Err(SignerError::UnsupportedOperation)` - If the current signer doesn't support EVM
473 ///
474 /// # Thread Safety
475 ///
476 /// The returned handle is thread-safe and can be passed between async tasks.
477 /// The underlying signer is immutable and reference-counted.
478 ///
479 /// # Examples
480 ///
481 /// ```ignore
482 /// use riglr_core::signer::SignerContext;
483 ///
484 /// async fn evm_only_operation() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
485 /// // This will fail if the current signer doesn't support EVM
486 /// let signer = SignerContext::current_as_evm().await?;
487 ///
488 /// // Now we have type-safe access to EVM methods
489 /// let address = signer.address();
490 /// let chain_id = signer.chain_id();
491 ///
492 /// println!("Operating on chain {} with address {}", chain_id, address);
493 ///
494 /// // Build and send a transaction
495 /// let tx = TransactionRequest::default()
496 /// .to(Address::ZERO)
497 /// .value(U256::from(1000000000000000u64)); // 0.001 ETH
498 ///
499 /// let tx_hash = signer.sign_and_send_transaction(tx).await?;
500 /// println!("Transaction sent: {}", tx_hash);
501 ///
502 /// Ok(())
503 /// }
504 /// ```
505 pub async fn current_as_evm() -> Result<EvmSignerHandle, SignerError> {
506 let unified = CURRENT_UNIFIED_SIGNER
507 .try_with(|s| s.clone())
508 .map_err(|_| SignerError::NoSignerContext)?;
509
510 // Check if the signer supports EVM
511 if !unified.supports_evm() {
512 return Err(SignerError::UnsupportedOperation(
513 "Current signer does not support EVM operations".to_string(),
514 ));
515 }
516
517 Ok(EvmSignerHandle(unified))
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 // Tests removed as they depend on blockchain SDKs
524 // The concrete implementations and their tests are now in the tools crates
525 // (riglr-solana-tools and riglr-evm-tools)
526 //
527 // Tests for SignerContext and UnifiedSigner functionality have been moved
528 // to integration tests in the respective tools crates where concrete
529 // implementations are available.
530}