SignerContext

Struct SignerContext 

Source
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

Source

pub async fn with_signer<T, F>( signer: Arc<dyn UnifiedSigner>, future: F, ) -> Result<T, SignerError>
where F: Future<Output = Result<T, SignerError>> + Send,

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 context
  • future - 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?;
Source

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 available
  • Err(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())
}
Source

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 available
  • false - 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.

Source

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 capabilities
  • Err(SignerError::UnsupportedOperation) - If the current signer doesn’t support the requested type
  • Err(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 operations
  • Err(SignerError::NoSignerContext) - If called outside a signer context
  • Err(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(())
}
Source

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 operations
  • Err(SignerError::NoSignerContext) - If called outside a signer context
  • Err(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(())
}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,