semioscan 0.10.1

Production-grade Rust library for blockchain analytics: gas calculation, price extraction, and block window calculations for EVM chains
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# Provider Setup Examples

This guide provides practical examples for setting up Alloy providers with Semioscan, covering common configurations and advanced patterns.

## Table of Contents

- [Basic Provider Setup]#basic-provider-setup
- [Rate Limiting]#rate-limiting
- [Logging and Tracing]#logging-and-tracing
- [Retry with Exponential Backoff]#retry-with-exponential-backoff
- [Combining Multiple Layers]#combining-multiple-layers
- [Provider Pooling]#provider-pooling
- [WebSocket Providers]#websocket-providers
- [Custom Filler Configuration]#custom-filler-configuration

> **Note:** RPC request/response logging is handled natively by alloy's transport layer at DEBUG/TRACE level. Enable it by setting your tracing subscriber level appropriately.

---

## Basic Provider Setup

### Simple HTTP Provider

The simplest way to create a provider:

```rust
use alloy_provider::ProviderBuilder;

let provider = ProviderBuilder::new()
    .connect_http("https://eth.llamarpc.com".parse()?);

let block = provider.get_block_number().await?;
```

### Using Semioscan Helpers

Semioscan provides convenience functions:

```rust
use semioscan::{simple_http_provider, ProviderConfig, create_http_provider};

// Quick and simple
let provider = simple_http_provider("https://eth.llamarpc.com")?;

// With configuration
let provider = create_http_provider(
    ProviderConfig::new("https://eth.llamarpc.com")
)?;
```

### Network-Specific Providers

For type-safe network-specific operations:

```rust
use alloy_network::Ethereum;
use op_alloy_network::Optimism;
use alloy_provider::ProviderBuilder;
use semioscan::create_typed_http_provider;

// Ethereum provider (explicit)
let eth_provider = ProviderBuilder::new()
    .network::<Ethereum>()
    .connect_http("https://eth.llamarpc.com".parse()?);

// Optimism provider (for Base, OP, Mode, etc.)
let op_provider = ProviderBuilder::new()
    .network::<Optimism>()
    .connect_http("https://mainnet.base.org".parse()?);

// Using Semioscan's typed helper
use semioscan::ProviderConfig;
let config = ProviderConfig::new("https://eth.llamarpc.com");
let provider = create_typed_http_provider::<Ethereum>(config)?;
```

---

## Rate Limiting

Rate limiting prevents RPC providers from rejecting requests due to rate limits.

### Using RateLimitLayer

```rust
use semioscan::RateLimitLayer;
use alloy_rpc_client::ClientBuilder;
use alloy_provider::ProviderBuilder;
use std::time::Duration;

// 10 requests per second
let layer = RateLimitLayer::per_second(10);

// Apply to client
let client = ClientBuilder::default()
    .layer(layer)
    .http("https://eth.llamarpc.com".parse()?);

let provider = ProviderBuilder::new()
    .connect_client(client);
```

### Custom Rate Limit Configurations

```rust
use semioscan::RateLimitLayer;
use std::time::Duration;

// 100 requests per minute
let layer = RateLimitLayer::new(100, Duration::from_secs(60));

// Minimum 100ms between requests
let layer = RateLimitLayer::with_min_delay(Duration::from_millis(100));

// Chain-specific rate limits
fn rate_limit_for_chain(chain: NamedChain) -> RateLimitLayer {
    match chain {
        NamedChain::Base => RateLimitLayer::per_second(4),      // Strict
        NamedChain::Optimism => RateLimitLayer::per_second(10),
        NamedChain::Mainnet => RateLimitLayer::per_second(25),
        _ => RateLimitLayer::per_second(10),                    // Default
    }
}
```

### Using ProviderConfig

```rust
use semioscan::{create_http_provider, ProviderConfig};

// Rate limiting via config
let provider = create_http_provider(
    ProviderConfig::new("https://eth.llamarpc.com")
        .with_rate_limit(10)  // 10 req/sec
)?;
```

---

## Logging and Tracing

Alloy's transport layer provides native RPC request/response logging via tracing.

### Enabling Native Logging

```rust
use tracing_subscriber::{fmt, EnvFilter};

// Enable debug logging to see RPC requests
tracing_subscriber::fmt()
    .with_env_filter("alloy_transport=debug")
    .init();

// Or for full request/response bodies, use trace level
tracing_subscriber::fmt()
    .with_env_filter("alloy_transport=trace")
    .init();
```

### What Gets Logged

At DEBUG level:
- Request URL and span information
- HTTP response status codes

At TRACE level:
- Full response bodies

### Example Output

```
DEBUG ReqwestTransport{url=https://eth.llamarpc.com}: received response from server status=200
```

---

## Retry with Exponential Backoff

Handle transient failures gracefully with automatic retries.

### Using RetryLayer

```rust
use semioscan::{RetryLayer, RetryConfig};
use alloy_rpc_client::ClientBuilder;
use alloy_provider::ProviderBuilder;
use std::time::Duration;

// Default retry (3 attempts, 100ms base delay)
let layer = RetryLayer::new();

// Custom configuration
let layer = RetryLayer::builder()
    .max_retries(5)
    .base_delay(Duration::from_millis(200))
    .max_delay(Duration::from_secs(60))
    .build();

// Preset configurations
let aggressive = RetryLayer::aggressive();   // 5 retries, 50ms base
let conservative = RetryLayer::conservative(); // 3 retries, 500ms base

let client = ClientBuilder::default()
    .layer(layer)
    .http("https://eth.llamarpc.com".parse()?);

let provider = ProviderBuilder::new()
    .connect_client(client);
```

### Retry Behavior

The retry layer uses exponential backoff:

- Delay = min(base_delay * 2^attempt, max_delay)
- Only retries transient errors (rate limits, timeouts, connection errors)
- Does not retry permanent errors (invalid params, contract reverts)

---

## Combining Multiple Layers

Layer order matters! Outer layers wrap inner layers.

### Recommended Stack

```rust
use semioscan::{RateLimitLayer, RetryLayer};
use alloy_rpc_client::ClientBuilder;
use alloy_provider::ProviderBuilder;

// Recommended layer order (outer to inner):
// 1. Retry - retries failed requests
// 2. Rate Limit - enforces rate limits
// Note: Logging is handled natively by alloy at DEBUG/TRACE level
let client = ClientBuilder::default()
    .layer(RetryLayer::new())             // Outer: retries on failure
    .layer(RateLimitLayer::per_second(10)) // Inner: rate limits
    .http("https://eth.llamarpc.com".parse()?);

let provider = ProviderBuilder::new()
    .connect_client(client);
```

### Layer Execution Order

With the stack above, request flow is:

```
Request → Retry → RateLimit → HTTP Transport (with native logging)
Response ← Retry ← RateLimit ← HTTP Transport
```

- Retry catches failures and retries with backoff
- Rate limit ensures we don't exceed limits
- Native alloy logging happens at the transport level

---

## Provider Pooling

For multi-chain applications, use a provider pool for efficient connection reuse.

### Basic Pool Setup

```rust
use semioscan::{ProviderPool, ProviderPoolBuilder, ChainEndpoint};
use alloy_chains::NamedChain;

// Create a pool with multiple chains
let pool = ProviderPoolBuilder::new()
    .add_chain(NamedChain::Mainnet, "https://eth.llamarpc.com")
    .add_chain(NamedChain::Base, "https://mainnet.base.org")
    .add_chain(NamedChain::Optimism, "https://mainnet.optimism.io")
    .add_chain(NamedChain::Arbitrum, "https://arb1.arbitrum.io/rpc")
    .with_rate_limit(10)
    .build()?;

// Get provider for a specific chain
let provider = pool.get(NamedChain::Base).expect("Chain not configured");
let block = provider.get_block_number().await?;
```

### Static Pool Pattern

For applications that need a global pool:

```rust
use semioscan::{ProviderPool, ProviderPoolBuilder};
use alloy_chains::NamedChain;
use std::sync::LazyLock;

// Static pool initialized once on first access
static PROVIDERS: LazyLock<ProviderPool> = LazyLock::new(|| {
    ProviderPoolBuilder::new()
        .add_chain(NamedChain::Mainnet, "https://eth.llamarpc.com")
        .add_chain(NamedChain::Base, "https://mainnet.base.org")
        .add_chain(NamedChain::Optimism, "https://mainnet.optimism.io")
        .with_rate_limit(10)
        .build()
        .expect("Failed to create provider pool")
});

// Use anywhere in your application
async fn get_block_number(chain: NamedChain) -> Result<u64, Error> {
    let provider = PROVIDERS.get(chain).expect("Chain not configured");
    Ok(provider.get_block_number().await?)
}

async fn multi_chain_operation() -> Result<(), Error> {
    // Concurrent access to multiple chains
    let (eth_block, base_block) = tokio::join!(
        get_block_number(NamedChain::Mainnet),
        get_block_number(NamedChain::Base),
    );

    println!("Ethereum: {}, Base: {}", eth_block?, base_block?);
    Ok(())
}
```

### Lazy Provider Addition

Add chains on-demand:

```rust
use semioscan::{ProviderPool, ProviderPoolBuilder, ProviderConfig};
use alloy_chains::NamedChain;

let pool = ProviderPoolBuilder::new().build()?;

// Add chains lazily when first accessed
async fn get_or_add_provider(
    pool: &ProviderPool,
    chain: NamedChain,
    rpc_url: &str,
) -> Result<&PooledProvider, Error> {
    pool.get_or_add(chain, || {
        ProviderConfig::new(rpc_url).with_rate_limit(10)
    })
}
```

---

## WebSocket Providers

For real-time subscriptions, use WebSocket providers.

### Basic WebSocket Setup

```rust
use alloy_provider::{ProviderBuilder, WsConnect};

let ws = WsConnect::new("wss://eth-mainnet.ws.alchemyapi.io/v2/YOUR_KEY");
let provider = ProviderBuilder::new()
    .connect_ws(ws)
    .await?;
```

### Using Semioscan's WebSocket Helper

```rust
use semioscan::{create_ws_provider, ProviderConfig};

let provider = create_ws_provider(
    ProviderConfig::new("wss://eth-mainnet.ws.alchemyapi.io/v2/YOUR_KEY")
).await?;
```

### Real-Time Event Streaming

```rust
use semioscan::events::realtime::RealtimeEventScanner;
use alloy_provider::{ProviderBuilder, WsConnect};
use alloy_rpc_types::Filter;
use futures::StreamExt;

// Create WebSocket provider
let ws = WsConnect::new("wss://eth.example.com/ws");
let provider = ProviderBuilder::new().connect_ws(ws).await?;

// Create scanner
let scanner = RealtimeEventScanner::new(provider);

// Subscribe to blocks
let mut blocks = scanner.subscribe_blocks().await?;
while let Some(header) = blocks.next().await {
    println!("New block: #{}", header.number);
}

// Subscribe to logs
let filter = Filter::new()
    .address(token_address)
    .event_signature(Transfer::SIGNATURE_HASH);

let mut logs = scanner.subscribe_logs(filter).await?;
while let Some(log) = logs.next().await {
    println!("Transfer event: {:?}", log);
}
```

---

## Custom Filler Configuration

Fillers automatically populate transaction fields.

### Recommended Fillers

```rust
use alloy_provider::ProviderBuilder;

// Default: includes nonce, gas, and chain ID fillers
let provider = ProviderBuilder::new()
    .with_recommended_fillers()
    .connect_http("https://eth.llamarpc.com".parse()?);
```

### With Wallet

```rust
use alloy_provider::ProviderBuilder;
use alloy_signer_local::PrivateKeySigner;

let signer: PrivateKeySigner = "your-private-key".parse()?;

let provider = ProviderBuilder::new()
    .with_recommended_fillers()
    .wallet(signer)
    .connect_http("https://eth.llamarpc.com".parse()?);

// Transactions are now automatically signed
let tx = TransactionRequest::default()
    .with_to(recipient)
    .with_value(U256::from(1_000_000_000_000_000_000u64));

let receipt = provider.send_transaction(tx).await?.get_receipt().await?;
```

### Read-Only Provider (No Fillers)

For read-only operations, skip fillers for better performance:

```rust
use alloy_provider::ProviderBuilder;

// No fillers - for read-only operations
let provider = ProviderBuilder::new()
    .connect_http("https://eth.llamarpc.com".parse()?);

// Can read but not send transactions
let block = provider.get_block_number().await?;
```

---

## Complete Examples

### Production-Ready Ethereum Provider

```rust
use semioscan::{RateLimitLayer, RetryLayer};
use alloy_rpc_client::ClientBuilder;
use alloy_provider::ProviderBuilder;
use alloy_network::Ethereum;

fn create_production_provider(rpc_url: &str) -> Result<impl Provider<Ethereum>, Error> {
    // Note: Logging is handled natively by alloy at DEBUG/TRACE level
    let client = ClientBuilder::default()
        .layer(RetryLayer::builder()
            .max_retries(5)
            .base_delay(Duration::from_millis(100))
            .build())
        .layer(RateLimitLayer::per_second(20))
        .http(rpc_url.parse()?);

    Ok(ProviderBuilder::new()
        .network::<Ethereum>()
        .connect_client(client))
}
```

### Multi-Chain Analytics Application

```rust
use semioscan::{
    GasCostCalculator, network_type_for_chain, NetworkType,
    ProviderPool, ProviderPoolBuilder,
    EthereumReceiptAdapter, OptimismReceiptAdapter,
};
use alloy_chains::NamedChain;
use std::sync::LazyLock;

static POOL: LazyLock<ProviderPool> = LazyLock::new(|| {
    ProviderPoolBuilder::new()
        .add_chain(NamedChain::Mainnet, "https://eth.llamarpc.com")
        .add_chain(NamedChain::Base, "https://mainnet.base.org")
        .add_chain(NamedChain::Optimism, "https://mainnet.optimism.io")
        .with_rate_limit(10)
        .build()
        .expect("Failed to create pool")
});

async fn analyze_gas_costs(
    chain: NamedChain,
    from: Address,
    to: Address,
    start_block: u64,
    end_block: u64,
) -> Result<GasCostResult, Error> {
    let provider = POOL.get(chain).expect("Chain not configured");

    match network_type_for_chain(chain) {
        NetworkType::Ethereum => {
            // Clone to get owned provider for calculator
            let calculator = GasCostCalculator::new(provider.clone());
            calculator.calculate_gas_cost_for_transfers_between_blocks(
                chain, from, to, start_block, end_block
            ).await
        }
        NetworkType::Optimism => {
            let calculator = GasCostCalculator::new(provider.clone());
            calculator.calculate_gas_cost_for_transfers_between_blocks(
                chain, from, to, start_block, end_block
            ).await
        }
    }
}
```

### Batch RPC Operations

```rust
use semioscan::{batch_fetch_balances, batch_fetch_eth_balances, BalanceQuery};
use alloy_provider::ProviderBuilder;
use alloy_provider::layers::CallBatchLayer;
use std::time::Duration;

// Provider with automatic call batching
let provider = ProviderBuilder::new()
    .layer(CallBatchLayer::new().wait(Duration::from_millis(10)))
    .connect_http("https://eth.llamarpc.com".parse()?);

// Batch fetch multiple token balances
let queries: Vec<BalanceQuery> = vec![
    (usdc_address, alice),
    (usdc_address, bob),
    (weth_address, alice),
    (weth_address, bob),
];

let results = batch_fetch_balances(&provider, &queries).await;

for (query, result) in queries.iter().zip(results.iter()) {
    match result {
        Ok(balance) => println!("{:?}: {}", query, balance),
        Err(e) => println!("{:?}: Error - {}", query, e),
    }
}

// Batch fetch ETH balances
let addresses = vec![alice, bob, charlie];
let eth_results = batch_fetch_eth_balances(&provider, &addresses).await;
```

---

## Environment Variables

Common patterns for RPC URL configuration:

```rust
use std::env;

fn get_rpc_url(chain: NamedChain) -> String {
    match chain {
        NamedChain::Mainnet => {
            env::var("ETHEREUM_RPC_URL")
                .unwrap_or_else(|_| "https://eth.llamarpc.com".to_string())
        }
        NamedChain::Base => {
            env::var("BASE_RPC_URL")
                .unwrap_or_else(|_| "https://mainnet.base.org".to_string())
        }
        NamedChain::Optimism => {
            env::var("OPTIMISM_RPC_URL")
                .unwrap_or_else(|_| "https://mainnet.optimism.io".to_string())
        }
        _ => panic!("Unsupported chain: {:?}", chain),
    }
}
```

### Using dotenvy

```rust
use dotenvy::dotenv;

fn main() -> Result<(), Error> {
    // Load .env file (optional - doesn't fail if missing)
    dotenv().ok();

    let rpc_url = std::env::var("ETHEREUM_RPC_URL")?;
    // ...
}
```

---

*Related documentation:*

- [Network Selection Guide]./NETWORK_SELECTION.md
- [Alloy Base Prompt]./alloy/base-prompt.md
- [Improvements Tracking]./IMPROVEMENTS.md