pub struct SignerContext;Expand description
The SignerContext provides thread-local signer management for secure multi-tenant operation.
This enables stateless tools that can access the appropriate signer without explicit passing, while maintaining strict isolation between different async tasks and users.
§Security Features
- Thread isolation: Each async task has its own isolated signer context
- No signer leakage: Contexts cannot access signers from other tasks
- Safe concurrent access: Multiple tasks can run concurrently with different signers
- Automatic cleanup: Contexts are automatically cleaned up when tasks complete
§Usage Patterns
§Basic Usage
use riglr_core::signer::SignerContext;
// Concrete signers are from tools crates:
// use riglr_solana_tools::LocalSolanaSigner;
use std::sync::Arc;
let keypair = Keypair::new();
let signer = Arc::new(LocalSolanaSigner::new(
keypair,
"https://api.devnet.solana.com".to_string()
));
// Execute code with signer context
let result = SignerContext::with_signer(signer, async {
// Inside this scope, tools can access the signer
let current = SignerContext::current().await?;
let user_id = current.user_id();
Ok(format!("Processing for user: {:?}", user_id))
}).await?;
println!("Result: {}", result);§Multi-Tenant Service Example
use riglr_core::signer::{SignerContext, UnifiedSigner};
use std::sync::Arc;
async fn handle_user_request(
user_signer: Arc<dyn UnifiedSigner>,
operation: &str
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
SignerContext::with_signer(user_signer, async {
// All operations in this scope use the user's signer
match operation {
"balance" => check_balance().await,
"transfer" => perform_transfer().await,
_ => Err(riglr_core::signer::SignerError::NoSignerContext)
}
}).await.map_err(Into::into)
}
async fn check_balance() -> Result<String, riglr_core::signer::SignerError> {
let signer = SignerContext::current().await?;
Ok(format!("Balance for user: {:?}", signer.user_id()))
}
async fn perform_transfer() -> Result<String, riglr_core::signer::SignerError> {
let signer = SignerContext::current().await?;
// Perform actual transfer using signer...
Ok("Transfer completed".to_string())
}§Error Handling
Tools should always check for signer availability:
use riglr_core::signer::SignerContext;
async fn safe_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
if !SignerContext::is_available().await {
return Err("This operation requires a signer context".into());
}
let signer = SignerContext::current().await?;
// Proceed with operation...
Ok("Operation completed".to_string())
}§Security Considerations
- Never store signers globally: Always use the context pattern
- Validate user permissions: Check that users own the addresses they’re operating on
- Audit all operations: Log all signer usage for security auditing
- Use environment-specific endpoints: Different signers for mainnet/testnet
Implementations§
Source§impl SignerContext
impl SignerContext
Sourcepub async fn with_signer<T, F>(
signer: Arc<dyn UnifiedSigner>,
future: F,
) -> Result<T, SignerError>
pub async fn with_signer<T, F>( signer: Arc<dyn UnifiedSigner>, future: F, ) -> Result<T, SignerError>
Execute a future with a unified signer context.
This is the primary method for setting up a signer context. It uses the new UnifiedSigner trait which provides better type safety and chain-specific access.
§Arguments
signer- The unified signer to make available in the contextfuture- The async code to execute with the signer context
§Examples
use riglr_core::signer::{SignerContext, UnifiedSigner};
use riglr_solana_tools::LocalSolanaSigner;
use std::sync::Arc;
let signer: Arc<dyn UnifiedSigner> = Arc::new(LocalSolanaSigner::new(
keypair,
"https://api.devnet.solana.com".to_string()
));
let result = SignerContext::with_signer(signer, async {
// Access as Solana signer
let solana = SignerContext::current_as_solana().await?;
let pubkey = solana.pubkey();
Ok(pubkey)
}).await?;Sourcepub async fn current() -> Result<Arc<dyn UnifiedSigner>, SignerError>
pub async fn current() -> Result<Arc<dyn UnifiedSigner>, SignerError>
Get the current unified signer from thread-local context.
This function retrieves the signer that was set by SignerContext::with_signer().
Returns the UnifiedSigner which can be cast to specific signer types.
§Returns
Ok(Arc<dyn UnifiedSigner>)- The current signer if availableErr(SignerError::NoSignerContext)- If called outside a signer context
§Examples
use riglr_core::signer::SignerContext;
async fn tool_that_needs_signer() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Get the current unified signer
let signer = SignerContext::current().await?;
// Check what chains it supports
if signer.supports_solana() {
let solana = SignerContext::current_as_solana().await?;
return Ok(format!("Solana pubkey: {}", solana.pubkey()));
}
if signer.supports_evm() {
let evm = SignerContext::current_as_evm().await?;
return Ok(format!("EVM address: {}", evm.address()));
}
Ok("Unknown signer type".to_string())
}Sourcepub async fn is_available() -> bool
pub async fn is_available() -> bool
Check if there is currently a signer context available.
This function returns true if the current async task is running within
a SignerContext::with_signer() scope, and false otherwise.
This is useful for tools that want to provide different behavior when called with or without a signer context, such as read-only vs. transactional operations.
§Returns
true- If a signer context is availablefalse- If no signer context is available
§Examples
use riglr_core::signer::SignerContext;
// Concrete signers from tools crates:
// use riglr_solana_tools::LocalSolanaSigner;
use std::sync::Arc;
async fn flexible_tool() -> Result<String, riglr_core::signer::SignerError> {
if SignerContext::is_available().await {
// We have a signer, can perform transactions
let signer = SignerContext::current().await?;
Ok("Performing transaction with signer".to_string())
} else {
// No signer available, provide read-only functionality
Ok("Read-only mode: no signer available".to_string())
}
}
// Test without signer context
let result1 = flexible_tool().await.unwrap();
assert_eq!(result1, "Read-only mode: no signer available");
// Test with signer context
let keypair = Keypair::new();
let signer = Arc::new(LocalSolanaSigner::new(
keypair,
"https://api.devnet.solana.com".to_string()
));
let result2 = SignerContext::with_signer(signer, async {
flexible_tool().await
}).await.unwrap();
assert_eq!(result2, "Performing transaction with signer");
§Performance
This function is very lightweight and can be called frequently without performance concerns. It simply checks if the thread-local storage contains a signer reference.
Sourcepub async fn current_as_solana() -> Result<SolanaSignerHandle, SignerError>
pub async fn current_as_solana() -> Result<SolanaSignerHandle, SignerError>
Get the current signer as a specific type.
This method allows type-safe access to chain-specific signer capabilities. Tools can require specific signer types and get compile-time guarantees.
§Type Parameters
T- The specific signer trait to cast to (e.g.,dyn SolanaSigner,dyn EvmSigner)
§Returns
Ok(&T)- Reference to the signer with the requested capabilitiesErr(SignerError::UnsupportedOperation)- If the current signer doesn’t support the requested typeErr(SignerError::NoSignerContext)- If no signer context is available
§Examples
use riglr_core::signer::{SignerContext, SolanaSigner, EvmSigner};
// Require Solana signer
async fn solana_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let signer = SignerContext::current_as::<dyn SolanaSigner>().await?;
Ok(format!("Solana pubkey: {}", signer.pubkey()))
}
// Require EVM signer
async fn evm_operation() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let signer = SignerContext::current_as::<dyn EvmSigner>().await?;
Ok(format!("EVM chain: {}", signer.chain_id()))
}Get the current signer as a Solana signer with type-safe access.
This method provides a strongly-typed handle to the current signer’s Solana
capabilities. The returned handle implements Deref to dyn SolanaSigner,
allowing direct access to all Solana-specific methods.
§Returns
Ok(SolanaSignerHandle)- A handle providing type-safe access to Solana operationsErr(SignerError::NoSignerContext)- If called outside a signer contextErr(SignerError::UnsupportedOperation)- If the current signer doesn’t support Solana
§Thread Safety
The returned handle is thread-safe and can be passed between async tasks. The underlying signer is immutable and reference-counted.
§Examples
use riglr_core::signer::SignerContext;
async fn solana_only_operation() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// This will fail if the current signer doesn't support Solana
let signer = SignerContext::current_as_solana().await?;
// Now we have type-safe access to Solana methods
let pubkey = signer.pubkey();
let client = signer.client();
// Perform Solana-specific operations
let balance = client.get_balance(&pubkey.parse()?).await?;
println!("Balance: {} SOL", balance);
Ok(())
}Sourcepub async fn current_as_evm() -> Result<EvmSignerHandle, SignerError>
pub async fn current_as_evm() -> Result<EvmSignerHandle, SignerError>
Get the current signer as an EVM signer with type-safe access.
This method provides a strongly-typed handle to the current signer’s EVM
capabilities. The returned handle implements Deref to dyn EvmSigner,
allowing direct access to all EVM-specific methods.
§Returns
Ok(EvmSignerHandle)- A handle providing type-safe access to EVM operationsErr(SignerError::NoSignerContext)- If called outside a signer contextErr(SignerError::UnsupportedOperation)- If the current signer doesn’t support EVM
§Thread Safety
The returned handle is thread-safe and can be passed between async tasks. The underlying signer is immutable and reference-counted.
§Examples
use riglr_core::signer::SignerContext;
async fn evm_only_operation() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// This will fail if the current signer doesn't support EVM
let signer = SignerContext::current_as_evm().await?;
// Now we have type-safe access to EVM methods
let address = signer.address();
let chain_id = signer.chain_id();
println!("Operating on chain {} with address {}", chain_id, address);
// Build and send a transaction
let tx = TransactionRequest::default()
.to(Address::ZERO)
.value(U256::from(1000000000000000u64)); // 0.001 ETH
let tx_hash = signer.sign_and_send_transaction(tx).await?;
println!("Transaction sent: {}", tx_hash);
Ok(())
}