riglr-evm-tools 0.3.0

EVM blockchain tools for the Riglr framework
Documentation
# riglr-evm-tools

Production-grade EVM blockchain tools for riglr agents, providing comprehensive Ethereum and EVM-compatible chain interactions.

## Features

- 🔐 **Secure Transaction Management**: Thread-safe SignerContext pattern for multi-tenant key management
- 💰 **Balance Operations**: Check ETH and ERC20 token balances with ApplicationContext
- 📤 **Token Transfers**: Send ETH and ERC20 tokens with SignerContext and automatic gas estimation
- 🔄 **DeFi Integration**: Uniswap V3 support for token swaps and liquidity operations
- 🌐 **Multi-Chain Support**: Works with Ethereum, Polygon, Arbitrum, Optimism, Base, and more
- ⚡ **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
- 📖 **Clear Separation**: ApplicationContext for read operations, SignerContext for write operations

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
riglr-evm-tools = "0.1.0"
```

## Quick Start

### Using LocalEvmSigner

The `LocalEvmSigner` is the primary concrete implementation for EVM transaction signing:

```rust
use riglr_evm_tools::LocalEvmSigner;
use riglr_config::EvmNetworkConfig;
use riglr_core::{ApplicationContext, UnifiedSigner};
use std::sync::Arc;

// Create network config
let config = EvmNetworkConfig::new(
    "mainnet",
    1,
    "https://eth-mainnet.g.alchemy.com/v2/your-key".to_string()
);

// Create signer from private key
let signer = LocalEvmSigner::new(
    "0xYOUR_PRIVATE_KEY_HEX",
    config
)?;

// 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 EVM tool
use riglr_evm_tools::transaction::send_eth;
let tx_hash = send_eth("0xRECIPIENT_ADDRESS", U256::from(1_000_000_000_000_000_000u64)).await?;
```

### Read Operations with ApplicationContext

Read-only operations (like checking balances) use the ApplicationContext pattern:

```rust
use riglr_core::provider::ApplicationContext;
use riglr_evm_tools::balance::{get_eth_balance, get_erc20_balance};
use alloy::providers::{Provider, ProviderBuilder};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create application context with provider
    let app_context = ApplicationContext::from_env();
    
    // Add a Provider to the context for EVM operations
    let provider = ProviderBuilder::new()
        .on_http("https://eth.llamarpc.com".parse()?)
        .boxed();
    app_context.add_extension(Arc::new(provider) as Arc<dyn Provider>);
    
    // Check ETH balance
    let balance = get_eth_balance(
        "0x742d35Cc6634C0532925a3b844Bc9e7595f0eA4B".to_string(),
        Some(1), // Ethereum mainnet
        &app_context
    ).await?;
    
    println!("Balance: {}", balance.balance_formatted);
    Ok(())
}
```

### Write Operations with SignerContext

Write operations (like transfers) require a SignerContext with proper signing capabilities:

```rust
use riglr_core::signer::{SignerContext, UnifiedSigner};
use riglr_evm_tools::signer::LocalEvmSigner;
use riglr_evm_tools::transaction::send_eth;
use alloy::primitives::{Address, U256};
use std::sync::Arc;
use std::str::FromStr;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create configuration for the network
    let config = riglr_config::EvmNetworkConfig::new(
        "mainnet",
        1,
        "https://eth.llamarpc.com".to_string()
    );
    
    // Create a signer with private key
    let signer = Arc::new(LocalEvmSigner::new(
        std::env::var("EVM_PRIVATE_KEY")?,
        config
    )?) as Arc<dyn UnifiedSigner>;
    
    // Execute transfer within SignerContext
    let tx_hash = SignerContext::with_signer(signer, async {
        let to = Address::from_str("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5")?;
        let amount = U256::from(1_000_000_000_000_000u64); // 0.001 ETH in wei
        
        let hash = send_eth(to, amount).await?;
        Ok::<_, anyhow::Error>(hash)
    }).await?;
    
    println!("Transaction sent: {}", tx_hash);
    Ok(())
}
```

### DeFi Operations (Uniswap)

```rust
use riglr_core::signer::{SignerContext, UnifiedSigner};
use riglr_evm_tools::signer::LocalEvmSigner;
use riglr_evm_tools::swap::{get_uniswap_quote, perform_uniswap_swap};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create signer for swap operations
    let config = riglr_config::EvmNetworkConfig::new(
        "mainnet",
        1,
        "https://eth.llamarpc.com".to_string()
    );
    
    let signer = Arc::new(LocalEvmSigner::new(
        std::env::var("EVM_PRIVATE_KEY")?,
        config
    )?) as Arc<dyn UnifiedSigner>;
    
    // Execute swap within SignerContext
    SignerContext::with_signer(signer, async {
        // Get a swap quote (read operation)
        let quote = get_uniswap_quote(
            "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(), // USDC
            "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), // WETH
            "1000".to_string(),       // Amount in
            6,                        // USDC decimals
            18,                       // WETH decimals
            Some(3000),              // 0.3% fee tier
            Some(50),                // 0.5% slippage
        ).await?;
        
        println!("Expected output: {} WETH", quote.amount_out);
        
        // Execute the swap (write operation)
        let swap = perform_uniswap_swap(
            "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(), // USDC
            "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(), // WETH
            "1000".to_string(),
            6,
            quote.amount_out_minimum,
            Some(3000),
            Some(300), // 5 minute deadline
        ).await?;
        
        println!("Swap executed: {}", swap.tx_hash);
        Ok(())
    }).await?;
    
    Ok(())
}
```

## Available Tools

### Balance Tools
- `get_eth_balance` - Get ETH balance for an address
- `get_erc20_balance` - Get ERC20 token balance

### Transaction Tools
- `transfer_eth` - Transfer ETH to another address
- `transfer_erc20` - Transfer ERC20 tokens
- `get_transaction_receipt` - Get receipt for a transaction

### DeFi Tools
- `get_uniswap_quote` - Get swap quote from Uniswap V3
- `perform_uniswap_swap` - Execute a token swap on Uniswap

## Configuration

### Environment Variables

```bash
# Convention-based RPC endpoints (RPC_URL_{CHAIN_ID})
RPC_URL_1=https://eth.llamarpc.com        # Ethereum (Chain ID: 1)
RPC_URL_137=https://polygon-rpc.com       # Polygon (Chain ID: 137)
RPC_URL_42161=https://arb1.arbitrum.io/rpc # Arbitrum (Chain ID: 42161)
RPC_URL_8453=https://base.llamarpc.com    # Base (Chain ID: 8453)

# Add any EVM chain by setting RPC_URL_{CHAIN_ID}
# Example: RPC_URL_10=https://optimism.io  # Optimism (Chain ID: 10)

# Private key for transactions (required for signing)
EVM_PRIVATE_KEY=0x...
```

### Extensible Chain Support

The EVM tools now support adding new chains without code changes. Simply set an environment variable following the `RPC_URL_{CHAIN_ID}` convention:

```rust
// Any chain with RPC_URL_{ID} set is automatically supported
let supported_chains = riglr_evm_tools::util::get_supported_chains();
println!("Supported chains: {:?}", supported_chains);

// Check if a specific chain is supported
if riglr_evm_tools::util::is_chain_supported(10) {
    println!("Optimism is configured!");
}
```

### Supported Networks

- Ethereum Mainnet
- Polygon
- Arbitrum One
- Optimism
- Base
- BNB Smart Chain
- Avalanche C-Chain
- Any EVM-compatible chain with custom RPC

## Architecture Patterns

### ApplicationContext for Read Operations

Read-only operations use ApplicationContext with a Provider extension:

```rust
use riglr_core::provider::ApplicationContext;
use alloy::providers::Provider;

// Setup once in your application
let app_context = ApplicationContext::from_env();
let provider = /* create provider */;
app_context.add_extension(Arc::new(provider) as Arc<dyn Provider>);

// Tools use the provider from context
let balance = get_eth_balance(address, chain_id, &app_context).await?;
```

### SignerContext for Write Operations

Write operations require SignerContext with proper signing capabilities:

```rust
use riglr_core::signer::{SignerContext, UnifiedSigner};

// Create signer with private key
let signer = Arc::new(LocalEvmSigner::new(key, config)?) as Arc<dyn UnifiedSigner>;

// Execute within context
SignerContext::with_signer(signer, async {
    // Operations here have access to signing capabilities
    let tx_hash = send_eth(to, amount).await?;
    Ok(tx_hash)
}).await?;
```

### Key Benefits

1. **Clear Separation**: Read vs write operations are clearly distinguished
2. **Thread Safety**: Each async task has its own isolated context
3. **Multi-Tenant Support**: Multiple users can execute operations concurrently
4. **Type Safety**: Operations are type-checked at compile time
5. **Automatic Context Propagation**: Tools automatically access the current context

### Type-Safe Chain Access

```rust
use riglr_core::signer::SignerContext;

// Get EVM-specific signer (fails if current signer doesn't support EVM)
let evm_signer = SignerContext::current_as_evm().await?;
let address = evm_signer.address();
let chain_id = evm_signer.chain_id();

// Check capabilities before operations
let signer = SignerContext::current().await?;
if signer.supports_evm() {
    // Perform EVM operations
}
```

## Error Handling

All tools use the `ToolError` pattern to distinguish between retriable and permanent failures:

```rust
use riglr_core::ToolError;
use riglr_evm_tools::balance::get_eth_balance;

match get_eth_balance(address, chain_id, &app_context).await {
    Ok(balance) => println!("Success: {}", balance.balance_formatted),
    Err(e) => match e {
        ToolError::Permanent { message, .. } => {
            // Invalid input, don't retry
            println!("Permanent error: {}", message);
        }
        ToolError::Retriable { message, .. } => {
            // Network issues, can retry
            println!("Temporary error: {}", message);
        }
        _ => println!("Other error: {:?}", e),
    }
}
```

## Integration with rig

All tools are compatible with the rig framework:

```rust
use rig::agents::Agent;
use riglr_evm_tools::get_eth_balance_tool;

let agent = Agent::builder()
    .preamble("You are an EVM blockchain analyst.")
    .tool(get_eth_balance_tool())
    .build();
```

## Safety and Security

- Private keys are managed per-client instance, not globally
- All addresses are validated before use
- Automatic nonce management prevents transaction conflicts
- Gas estimation with configurable limits
- Slippage protection for DeFi operations
- Thread-safe client instances can be cloned and shared

## License

MIT

## Contributing

Contributions are welcome! Please read our contributing guidelines and submit PRs to our GitHub repository.

## Support

For issues and questions, please open an issue on GitHub or reach out to the maintainers.