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}