Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
199 commits
Select commit Hold shift + click to select a range
ad163eb
init test
LeoPatOZ Sep 18, 2025
3844e13
refactor reorg test
LeoPatOZ Sep 18, 2025
f824f74
add more basic block scanner test
LeoPatOZ Sep 18, 2025
42a0a40
update test
LeoPatOZ Sep 22, 2025
768bf8e
Merge branch 'main' into requirement-tests
LeoPatOZ Sep 23, 2025
8e41902
update naming
LeoPatOZ Sep 23, 2025
be28102
update
LeoPatOZ Sep 23, 2025
c7efa8d
format
LeoPatOZ Sep 23, 2025
d474f9d
Merge branch 'main' into requirement-tests
LeoPatOZ Sep 23, 2025
87c3916
update tests for range inclusive
LeoPatOZ Sep 23, 2025
9f23779
rename
LeoPatOZ Sep 23, 2025
da8a308
better logging
LeoPatOZ Sep 23, 2025
0fa7850
refactor to have seperatation in modes
LeoPatOZ Sep 23, 2025
2b7eafd
unify live and historical
LeoPatOZ Sep 23, 2025
f17042a
comments explaining
LeoPatOZ Sep 23, 2025
1f0e5ab
better cuttoff tests
LeoPatOZ Sep 23, 2025
e81d6c6
update comment
LeoPatOZ Sep 23, 2025
ac45a6f
fmt
LeoPatOZ Sep 23, 2025
54a8a4a
fmt
LeoPatOZ Sep 23, 2025
82931b9
Merge branch 'refactor-block-range-scanner' into reorg-live-mode
LeoPatOZ Sep 23, 2025
f62d79f
update to use saturating add
LeoPatOZ Sep 23, 2025
aec2a11
reorg detection start
LeoPatOZ Sep 23, 2025
5d488c0
Merge branch 'refactor-block-range-scanner' into reorg-live-mode
LeoPatOZ Sep 23, 2025
f8a8037
update comment about reorg
LeoPatOZ Sep 23, 2025
ce8e5c1
replace expects
LeoPatOZ Sep 23, 2025
9be45a6
format
LeoPatOZ Sep 23, 2025
3a547e0
Update src/block_range_scanner.rs
LeoPatOZ Sep 24, 2025
de9a07b
add check to historical
LeoPatOZ Sep 24, 2025
33c50fb
move EOF check to sync historical data function
LeoPatOZ Sep 24, 2025
2c4c281
fmt
LeoPatOZ Sep 24, 2025
aa98709
refactor to have commands
LeoPatOZ Sep 24, 2025
3649cba
helper function for the subsriber logic
LeoPatOZ Sep 24, 2025
cb419ea
Merge branch 'requirement-tests' into refactor-block-range-scanner
LeoPatOZ Sep 24, 2025
6ba0b24
comment
LeoPatOZ Sep 24, 2025
dd6cc1e
removed optional block hash and num
LeoPatOZ Sep 24, 2025
0324247
comment about unwrap
LeoPatOZ Sep 24, 2025
218dbc5
send error
LeoPatOZ Sep 24, 2025
22a4d25
update batch end fetching error message
0xNeshi Sep 24, 2025
9fc91e7
refactor + print warning if channel closed when sending error
0xNeshi Sep 24, 2025
2bb4690
updated test cases
LeoPatOZ Sep 24, 2025
07b24e1
Merge branch 'main' into refactor-block-range-scanner
LeoPatOZ Sep 24, 2025
8877d98
format
LeoPatOZ Sep 24, 2025
77a820a
Merge branch 'refactor-block-range-scanner' into reorg-live-mode
LeoPatOZ Sep 24, 2025
ced78ce
improve reorg logic
LeoPatOZ Sep 24, 2025
33f89e8
comment out for now
LeoPatOZ Sep 24, 2025
367d5b3
update reorg test
LeoPatOZ Sep 25, 2025
5d003c6
up
LeoPatOZ Sep 25, 2025
352c2bd
Merge branch 'main' into reorg-live-mode
LeoPatOZ Sep 25, 2025
94697c8
checkpoint commit - INCLUDES REORG LOGIC
LeoPatOZ Sep 25, 2025
e260c81
update scanner
LeoPatOZ Sep 25, 2025
f28fd7f
final checks
LeoPatOZ Sep 25, 2025
9e75d44
format
LeoPatOZ Sep 25, 2025
f4b7384
Update tests/live_mode/reorg.rs
LeoPatOZ Sep 26, 2025
a1a530d
Update tests/live_mode/reorg.rs
LeoPatOZ Sep 26, 2025
427cb19
Update tests/live_mode/reorg.rs
LeoPatOZ Sep 26, 2025
d496f6c
update test
LeoPatOZ Sep 26, 2025
2547f73
Merge branch 'main' into reorg-live-mode
LeoPatOZ Sep 26, 2025
0a9d525
remove callback
LeoPatOZ Sep 26, 2025
58ef2a3
update range
LeoPatOZ Sep 26, 2025
98dd7df
revert change to curr
LeoPatOZ Sep 26, 2025
f150bdf
remove print
LeoPatOZ Sep 26, 2025
f5bac5b
reorg test
LeoPatOZ Sep 26, 2025
b012367
add more tests
LeoPatOZ Sep 26, 2025
3a3e572
format
LeoPatOZ Sep 26, 2025
c9de225
ignored rewind depth for now
LeoPatOZ Sep 26, 2025
8af35e8
Merge branch 'main' into reorg-live-mode
LeoPatOZ Sep 26, 2025
b7a675e
remove unused import
LeoPatOZ Sep 26, 2025
27da4d8
cleaner reorg logic
LeoPatOZ Sep 27, 2025
c7850e2
block confirmation
LeoPatOZ Sep 28, 2025
c5e17f1
testing
LeoPatOZ Sep 29, 2025
561455e
merge
LeoPatOZ Sep 29, 2025
63b6504
trying to test
LeoPatOZ Sep 30, 2025
8cfe689
Merge branch 'main' into add-block-confirmations
LeoPatOZ Sep 30, 2025
058eb46
confrimation test
LeoPatOZ Sep 30, 2025
cb9ce85
Merge branch 'main' into add-block-confirmations
LeoPatOZ Sep 30, 2025
f0a481e
add comment
LeoPatOZ Sep 30, 2025
46f2163
better setup fns
LeoPatOZ Oct 1, 2025
cca5f33
fix test
LeoPatOZ Oct 1, 2025
f0d02cb
revert
LeoPatOZ Oct 1, 2025
8929f8b
remove print fix roerg
LeoPatOZ Oct 1, 2025
a33731c
fix test setup
LeoPatOZ Oct 2, 2025
7886af7
clippy
LeoPatOZ Oct 2, 2025
fcc534c
fix assert
LeoPatOZ Oct 2, 2025
658ff93
remove rewind
LeoPatOZ Oct 2, 2025
21cc925
Merge branch 'main' into add-block-confirmations
LeoPatOZ Oct 2, 2025
1f640c4
increase reorg depth..
LeoPatOZ Oct 2, 2025
b6ee2de
Merge branch 'main' into add-block-confirmations
LeoPatOZ Oct 2, 2025
d164191
start latest from latest confirmed tip
LeoPatOZ Oct 2, 2025
5d14a0b
add test
LeoPatOZ Oct 2, 2025
59a07d3
fix test
LeoPatOZ Oct 3, 2025
111e76f
better comments
LeoPatOZ Oct 3, 2025
3185170
refactor tests
LeoPatOZ Oct 3, 2025
a37a942
chore: fix conflicts
0xNeshi Oct 3, 2025
d01ea9d
remove config
LeoPatOZ Oct 3, 2025
8b3217c
update naming to stream
LeoPatOZ Oct 3, 2025
5072e58
update comment
LeoPatOZ Oct 3, 2025
088112d
update read me and doc test
LeoPatOZ Oct 3, 2025
47594b7
format
LeoPatOZ Oct 3, 2025
c52976b
doc test
LeoPatOZ Oct 3, 2025
c75a85b
update Client --> EventScannerClient
LeoPatOZ Oct 3, 2025
231c9d9
rename
LeoPatOZ Oct 6, 2025
057d73d
add back max read
LeoPatOZ Oct 6, 2025
9ee4210
update logic of max blocks reads
LeoPatOZ Oct 6, 2025
61ecdae
added test
LeoPatOZ Oct 6, 2025
a5015f1
rename and add max reads to global
LeoPatOZ Oct 6, 2025
a1867c5
Merge branch 'main' into remove-config-params
LeoPatOZ Oct 6, 2025
b22c42b
fix test
LeoPatOZ Oct 6, 2025
e715d1e
up
LeoPatOZ Oct 6, 2025
4e4f0d2
rename back to client
LeoPatOZ Oct 6, 2025
de593fa
format
LeoPatOZ Oct 6, 2025
5b35e54
readme
LeoPatOZ Oct 6, 2025
b81bc45
Merge branch 'main' into remove-config-params
0xNeshi Oct 7, 2025
fa7ca59
trying something
LeoPatOZ Oct 7, 2025
73f8195
add event filter and default
LeoPatOZ Oct 9, 2025
93c22c7
base config
LeoPatOZ Oct 9, 2025
ba0199a
move everything
LeoPatOZ Oct 9, 2025
d09b9a2
trying to fix sync mode
LeoPatOZ Oct 9, 2025
ae39776
wrapper
LeoPatOZ Oct 9, 2025
e6b8693
update with dummy scanner
LeoPatOZ Oct 9, 2025
e28c381
remove client
LeoPatOZ Oct 9, 2025
06c58eb
repeat for all modes
LeoPatOZ Oct 9, 2025
7402974
fix imports
LeoPatOZ Oct 9, 2025
810afef
remove option confirmation
LeoPatOZ Oct 10, 2025
fdd73e3
fix compilation and lint
LeoPatOZ Oct 10, 2025
4440855
remoe old EventScanner
LeoPatOZ Oct 10, 2025
421b585
rename to live
LeoPatOZ Oct 10, 2025
b4f5633
fix test and example
LeoPatOZ Oct 10, 2025
f852288
rename
LeoPatOZ Oct 10, 2025
77beb97
fix reorg test
LeoPatOZ Oct 10, 2025
5ab0b17
remove event filter from scanner
LeoPatOZ Oct 10, 2025
c6941d0
add latest
LeoPatOZ Oct 10, 2025
fffd6c8
add test to each scanner
LeoPatOZ Oct 10, 2025
d9ac24e
remove nested folder
LeoPatOZ Oct 10, 2025
996d993
move errors to own file
LeoPatOZ Oct 10, 2025
d00e7f1
doc
LeoPatOZ Oct 10, 2025
c217206
Merge branch 'main' into remove-config-params
LeoPatOZ Oct 10, 2025
3b7d918
update readme
LeoPatOZ Oct 10, 2025
0e00593
format
LeoPatOZ Oct 10, 2025
d28c351
client --> scanner
LeoPatOZ Oct 14, 2025
5ca4cd2
use block read limit
LeoPatOZ Oct 14, 2025
4a5a6f9
connect_provider to connect
LeoPatOZ Oct 14, 2025
872a842
foramt
LeoPatOZ Oct 14, 2025
e366272
stream to start
LeoPatOZ Oct 14, 2025
6f19dab
client to scanner
LeoPatOZ Oct 14, 2025
5cd7a4f
format
LeoPatOZ Oct 14, 2025
5bba20c
Merge branch 'main' into remove-config-params
LeoPatOZ Oct 20, 2025
69d336c
fix: merge errors
LeoPatOZ Oct 20, 2025
2b7f504
fix: integrate latest into scanner api refactor
LeoPatOZ Oct 20, 2025
972ffd3
chore: remove all client
LeoPatOZ Oct 20, 2025
d9c14d6
chore: move impl to different file
LeoPatOZ Oct 20, 2025
7f941c6
chore: rename param
LeoPatOZ Oct 20, 2025
25c3998
Merge branch 'main' into remove-config-params
LeoPatOZ Oct 20, 2025
461b93d
feat: add sync_scanning example
LeoPatOZ Oct 21, 2025
6a51aa2
chore: add comment about solc version and update bytecode
LeoPatOZ Oct 21, 2025
2b5453f
fix: format
LeoPatOZ Oct 21, 2025
9149a7c
chore: rename max read to max block range
LeoPatOZ Oct 21, 2025
ea22632
chore: rename to max block range + u64
LeoPatOZ Oct 22, 2025
b501e14
chore: drop with_ prefix
LeoPatOZ Oct 22, 2025
fd2e78d
ref: rename event_lib to event_scanner
LeoPatOZ Oct 22, 2025
423d17a
ref: remove result in connect
LeoPatOZ Oct 22, 2025
134b9d7
ref: remove result in connect + rename historic config to builder
LeoPatOZ Oct 22, 2025
3d18a16
ref: rename rest of modes + fix result type
LeoPatOZ Oct 22, 2025
ee9066d
ref: remove clone
LeoPatOZ Oct 22, 2025
39983cc
ref: remove panic docs
LeoPatOZ Oct 22, 2025
7913717
ref: remove await in sync
LeoPatOZ Oct 22, 2025
b83d7a1
ref: remove with_ prefix on filter
LeoPatOZ Oct 22, 2025
ee198df
fix: doc test
LeoPatOZ Oct 22, 2025
4cef782
ref: remove task aborting
LeoPatOZ Oct 22, 2025
0dbcdbf
fix: format
LeoPatOZ Oct 22, 2025
eda2e18
ref: move message to only file
LeoPatOZ Oct 22, 2025
6c7b169
ref: make event listener only available to crate
LeoPatOZ Oct 22, 2025
1687d99
ref: update comment
LeoPatOZ Oct 22, 2025
5dc0e88
fix: format
LeoPatOZ Oct 22, 2025
4b55342
feat: remove client - make each client handle own calling
LeoPatOZ Oct 22, 2025
77181bd
ref: remove to_live
LeoPatOZ Oct 22, 2025
9c6ed4c
ref: run --> start
LeoPatOZ Oct 22, 2025
5789994
ref: remove base builder
LeoPatOZ Oct 22, 2025
7c66fe0
ref: move consumer to common
LeoPatOZ Oct 22, 2025
2109b1b
feat: add better comments
LeoPatOZ Oct 22, 2025
50517ff
feat: add back comments to scanner start functions
LeoPatOZ Oct 22, 2025
f31428c
ref: delete scanner.rs
LeoPatOZ Oct 22, 2025
e8c1117
test: better tesing of builder pattern + additional test for streaming
LeoPatOZ Oct 22, 2025
fe4e6a4
ref: rename create event stream to subscribe
LeoPatOZ Oct 23, 2025
4cf401b
feat: add must use to new fn
LeoPatOZ Oct 23, 2025
c3f33c0
ref: remove comment in builder struct def
LeoPatOZ Oct 23, 2025
accf180
ref: better comment for connect methods
LeoPatOZ Oct 23, 2025
b37b4bf
fix: format
LeoPatOZ Oct 23, 2025
52ea719
ref: super --> crate
LeoPatOZ Oct 23, 2025
18f1de4
ref: rename test to make it clearer
LeoPatOZ Oct 23, 2025
35d651f
ref: remove clone
LeoPatOZ Oct 23, 2025
ddaa921
ref: rename to message
LeoPatOZ Oct 23, 2025
2498939
feat: shared error enum accross both scanners
LeoPatOZ Oct 23, 2025
581f670
fix: doc test
LeoPatOZ Oct 23, 2025
f73088c
ref: unexpand
LeoPatOZ Oct 23, 2025
a103255
ref: redundant map
LeoPatOZ Oct 23, 2025
d548947
ref: remove builder from public API
LeoPatOZ Oct 23, 2025
8c6365c
Merge branch 'main' into remove-config-params
LeoPatOZ Oct 23, 2025
276d0b7
feat: remove leftover with_ prefix
0xNeshi Oct 23, 2025
6d16f55
docs: fix README
0xNeshi Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
members = [
".",
"examples/historical_scanning",
"examples/simple_counter",
"examples/live_scanning",
"examples/latest_events_scanning",
"examples/sync_scanning"
]
resolver = "2"

Expand Down
136 changes: 87 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
Event Scanner is a Rust library for streaming EVM-based smart contract events. It is built on top of the [`alloy`](https://github.com/alloy-rs/alloy) ecosystem and focuses on in-memory scanning without a backing database. Applications provide event filters; the scanner takes care of fetching historical ranges, bridging into live streaming mode, all whilst delivering the events as streams of data.

---


## Table of Contents

Expand Down Expand Up @@ -60,27 +59,29 @@ event-scanner = "0.3.0-alpha"
Create an event stream for the given event filters registered with the `EventScanner`:

```rust
use alloy::{eips::BlockNumberOrTag, network::Ethereum, sol_types::SolEvent};
use event_scanner::{EventFilter, EventScanner, EventScannerError, EventScannerMessage};
use alloy::{network::Ethereum, sol_types::SolEvent};
use event_scanner::{EventFilter, EventScanner, EventScannerMessage};
use tokio_stream::StreamExt;

use crate::MyContract;

async fn run_scanner(
ws_url: alloy::transports::http::reqwest::Url,
contract: alloy::primitives::Address,
) -> Result<(), EventScannerError> {
let mut client = EventScanner::new().connect_ws::<Ethereum>(ws_url).await?;
) -> Result<(), Box<dyn std::error::Error>> {
// Configure scanner with custom batch size (optional)
let mut scanner = EventScanner::live()
.block_read_limit(500) // Process up to 500 blocks per batch
.connect_ws::<Ethereum>(ws_url).await?;

let filter = EventFilter::new()
.with_contract_address(contract)
.with_event(MyContract::SomeEvent::SIGNATURE);
.contract_address(contract)
.event(MyContract::SomeEvent::SIGNATURE);

let mut stream = client.create_event_stream(filter);
let mut stream = scanner.subscribe(filter);

tokio::spawn(async move {
client.start_scanner(BlockNumberOrTag::Earliest, Some(BlockNumberOrTag::Latest)).await
});
// Start the scanner
tokio::spawn(async move { scanner.stream().await });

while let Some(EventScannerMessage::Data(logs)) = stream.next().await {
println!("Fetched logs: {logs:?}");
Expand All @@ -96,13 +97,43 @@ async fn run_scanner(

### Building a Scanner

`EventScanner` supports:
`EventScanner` provides mode-specific constructors and a builder pattern to configure settings before connecting:

- `with_blocks_read_per_epoch` - how many blocks are read at a time in a single batch (taken into consideration when fetching historical blocks)
- `with_reorg_rewind_depth` - how many blocks to rewind when a reorg is detected (NOTE ⚠️: still WIP)
- `with_block_confirmations` - how many confirmations to wait for before considering a block final
```rust
// Live streaming mode
let scanner = EventScanner::live()
.block_read_limit(500) // Optional: set max blocks per read (default: 1000)
.connect_ws::<Ethereum>(ws_url).await?;

// Historical scanning mode
let scanner = EventScanner::historic()
.block_read_limit(500)
.connect_ws::<Ethereum>(ws_url).await?;

// Sync mode (historical + live)
let scanner = EventScanner::sync()
.block_read_limit(500)
.connect_ws::<Ethereum>(ws_url).await?;

// Latest mode (recent blocks only)
let scanner = EventScanner::latest()
.count(100)
.block_read_limit(500)
.connect_ws::<Ethereum>(ws_url).await?;
```

**Available Modes:**
- `EventScanner::live()` – Streams new blocks as they arrive
- `EventScanner::historic()` – Processes historical block ranges
- `EventScanner::sync()` – Processes historical data then transitions to live streaming
- `EventScanner::latest()` – Processes a specific number of events then optionally switches to live scanning mode

Once configured, connect using either `connect_ws::<Ethereum>(ws_url)` or `connect_ipc::<Ethereum>(path)`. This will `connect` the `EventScanner` and allow you to create event streams and start scanning in various [modes](#scanning-modes).
**Global Configuration Options:**
- `block_read_limit(usize)` – Sets the maximum number of blocks to process per read operation. This prevents RPC provider errors from overly large block range queries.
- Connect with `connect_ws::<Ethereum>(url)`, `connect_ipc::<Ethereum>(path)`, or `connect(provider)`.

**Starting the Scanner:**
Invoking `scanner.start()` starts the scanner in the specified mode.

### Defining Event Filters

Expand All @@ -111,70 +142,74 @@ Create an `EventFilter` for each event stream you wish to process. The filter sp
```rust
// Track a SPECIFIC event from a SPECIFIC contract
let specific_filter = EventFilter::new()
.with_contract_address(*counter_contract.address())
.with_event(Counter::CountIncreased::SIGNATURE);
.contract_address(*counter_contract.address())
.event(Counter::CountIncreased::SIGNATURE);

// Track a multiple events from a SPECIFIC contract
let specific_filter = EventFilter::new()
.with_contract_address(*counter_contract.address())
.with_event(Counter::CountIncreased::SIGNATURE)
.with_event(Counter::CountDecreased::SIGNATURE);
.contract_address(*counter_contract.address())
.event(Counter::CountIncreased::SIGNATURE)
.event(Counter::CountDecreased::SIGNATURE);

// Track a SPECIFIC event from a ALL contracts
let specific_filter = EventFilter::new()
.with_event(Counter::CountIncreased::SIGNATURE);
.event(Counter::CountIncreased::SIGNATURE);

// Track ALL events from a SPECIFIC contracts
let all_contract_events_filter = EventFilter::new()
.with_contract_address(*counter_contract.address())
.with_contract_address(*other_counter_contract.address());
.contract_address(*counter_contract.address())
.contract_address(*other_counter_contract.address());

// Track ALL events from ALL contracts in the block range
let all_events_filter = EventFilter::new();
```

Register multiple filters by invoking `create_event_stream` repeatedly.
Register multiple filters by invoking `subscribe` repeatedly.

The flexibility provided by `EventFilter` allows you to build sophisticated event monitoring systems that can track events at different granularities depending on your application's needs.

### Scanning Modes

- **Live mode** - `start_scanner(BlockNumberOrTag::Latest, None)` subscribes to new blocks only. On detecting a reorg, the scanner emits `ScannerStatus::ReorgDetected` and recalculates the confirmed window, streaming logs from the corrected confirmed block range.
- **Historical mode** - `start_scanner(BlockNumberOrTag::Number(start), Some(BlockNumberOrTag::Number(end)))`, scanner fetches events from a historical block range. Currently no reorg logic has been implemented (NOTE ⚠️: still WIP). In the case that the end block > finalized block and you need reorg resistance, we recommend to use sync mode.
- **Historical → Live** - `start_scanner(BlockNumberOrTag::Number(start), None)` replays from `start` to current head, then streams future blocks. Reorgs are handled as per the particular mode phase the scanner is in (historical or live).
- **Live mode** – `EventScanner::live()` creates a scanner that subscribes to new blocks as they arrive.
- **Historical mode** – `EventScanner::historic()` creates a scanner for processing historical block ranges.
- **Sync mode** – `EventScanner::sync()` creates a scanner that processes historical data then automatically transitions to live streaming.
- **Latest mode** – `EventScanner::latest()` creates a scanner that processes a set number of events.

For now modes are deduced from the `start` and `end` parameters. In the future, we might add explicit commands to select the mode.
**Configuration Tips:**
- Set `block_read_limit` based on your RPC provider's limits (e.g., Alchemy, Infura may limit queries to 2000 blocks)
- For live mode, if the WebSocket subscription lags significantly (e.g., >2000 blocks), ranges are automatically capped to prevent RPC errors
- Each mode has its own configuration options for start block, end block, confirmations, etc. where it makes sense
- The modes come with sensible defaults for example not specify a start block for historic mode automatically sets the start block to the earliest one

See the integration tests under `tests/live_mode`, `tests/historic_mode`, and `tests/historic_to_live` for concrete examples.
See integration tests under `tests/live_mode`, `tests/historic_mode`, and `tests/historic_to_live` for concrete examples.

### Scanning Latest Events

`scan_latest` collects the most recent matching events for each registered stream.
Scanner mode that collects a specified number of the most recent matching events for each registered stream.

- It does not enter live mode; it scans a block range and then returns.
- Each registered stream receives at most `count` logs in a single message, chronologically ordered.

Basic usage:

```rust
use alloy::{eips::BlockNumberOrTag, network::Ethereum};
use event_scanner::{EventFilter, event_scanner::{EventScanner, EventScannerMessage}};
use alloy::{network::Ethereum, primitives::Address, transports::http::reqwest::Url};
use event_scanner::{EventFilter, EventScanner, Message};
use tokio_stream::StreamExt;

async fn latest_example(ws_url: alloy::transports::http::reqwest::Url, addr: alloy::primitives::Address) -> eyre::Result<()> {
let mut client = EventScanner::new().connect_ws::<Ethereum>(ws_url).await?;
async fn latest_events(ws_url: Url, addr: Address) -> anyhow::Result<()> {
let mut scanner = EventScanner::latest().count(10).connect_ws::<Ethereum>(ws_url).await?;

let filter = EventFilter::new().contract_address(addr);

let filter = EventFilter::new().with_contract_address(addr);
let mut stream = client.create_event_stream(filter);
let mut stream = scanner.subscribe(filter);

// Collect the latest 10 events across Earliest..=Latest
client.scan_latest(10).await?;
scanner.start().await?;

// Expect a single message with up to 10 logs, then the stream ends
while let Some(msg) = stream.next().await {
if let EventScannerMessage::Data(logs) = msg {
println!("Latest logs: {}", logs.len());
}
while let Some(Message::Data(logs)) = stream.next().await {
println!("Latest logs: {}", logs.len());
}

Ok(())
Expand All @@ -185,30 +220,33 @@ Restricting to a specific block range:

```rust
// Collect the latest 5 events between blocks [1_000_000, 1_100_000]
client
.scan_latest_in_range(5, BlockNumberOrTag::Number(1_000_000), BlockNumberOrTag::Number(1_100_000))
let mut scanner = EventScanner::latest()
.count(5)
.from_block(1_000_000)
.to_block(1_100_000)
.connect_ws::<Ethereum>(ws_url).await?;
.await?;
```

The scanner periodically checks the tip to detect reorgs. On reorg, the scanner emits `ScannerStatus::ReorgDetected`, resets to the updated tip, and restarts the scan. Final delivery to log listeners is in chronological order.

Notes:

- Ensure you create streams via `create_event_stream()` before calling `scan_latest*` so listeners are registered.
- Ensure you create streams via `subscribe()` before calling `start` so listeners are registered.
<!-- TODO: uncomment once implemented - The function returns after delivering the messages; to continuously stream new blocks, use `scan_latest_then_live`. -->

---

## Examples

- `examples/simple_counter` – minimal live-mode scanner
- `examples/historical_scanning` – demonstrates replaying from genesis (block 0) before continuing streaming latest blocks
- `examples/latest_events_scanning` – demonstrates scanning the latest events
- `examples/live_scanning` – minimal live-mode scanner using `EventScanner::live()`
- `examples/historical_scanning` – demonstrates replaying historical data using `EventScanner::historic()`
- `examples/latest_events_scanning` – demonstrates scanning the latest events using `EventScanner::latest()`

Run an example with:

```bash
RUST_LOG=info cargo run -p simple_counter
RUST_LOG=info cargo run -p live_scanning
# or
RUST_LOG=info cargo run -p historical_scanning
```
Expand Down
34 changes: 16 additions & 18 deletions examples/historical_scanning/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
use std::time::Duration;

use alloy::{
eips::BlockNumberOrTag, network::Ethereum, providers::ProviderBuilder, sol, sol_types::SolEvent,
};
use alloy::{network::Ethereum, providers::ProviderBuilder, sol, sol_types::SolEvent};
use alloy_node_bindings::Anvil;
use event_scanner::{
EventFilter,
event_scanner::{EventScanner, EventScannerMessage},
};

use event_scanner::{EventFilter, EventScanner, Message};
use tokio::time::sleep;
use tokio_stream::StreamExt;
use tracing::{error, info};
use tracing_subscriber::EnvFilter;

sol! {
#[allow(missing_docs)]
#[sol(rpc, bytecode="608080604052346015576101b0908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c90816306661abd1461016157508063a87d942c14610145578063d732d955146100ad5763e8927fbc14610048575f80fd5b346100a9575f3660031901126100a9575f5460018101809111610095576020817f7ca2ca9527391044455246730762df008a6b47bbdb5d37a890ef78394535c040925f55604051908152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b346100a9575f3660031901126100a9575f548015610100575f198101908111610095576020817f53a71f16f53e57416424d0d18ccbd98504d42a6f98fe47b09772d8f357c620ce925f55604051908152a1005b60405162461bcd60e51b815260206004820152601860248201527f436f756e742063616e6e6f74206265206e6567617469766500000000000000006044820152606490fd5b346100a9575f3660031901126100a95760205f54604051908152f35b346100a9575f3660031901126100a9576020905f548152f3fea2646970667358221220b846b706f79f5ae1fc4a4238319e723a092f47ce4051404186424739164ab02264736f6c634300081e0033")]
contract Counter {
// Built directly with solc 0.8.30+commit.73712a01.Darwin.appleclang
#[sol(rpc,
bytecode="608080604052346015576101b0908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c90816306661abd1461016157508063a87d942c14610145578063d732d955146100ad5763e8927fbc14610048575f80fd5b346100a9575f3660031901126100a9575f5460018101809111610095576020817f7ca2ca9527391044455246730762df008a6b47bbdb5d37a890ef78394535c040925f55604051908152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b346100a9575f3660031901126100a9575f548015610100575f198101908111610095576020817f53a71f16f53e57416424d0d18ccbd98504d42a6f98fe47b09772d8f357c620ce925f55604051908152a1005b60405162461bcd60e51b815260206004820152601860248201527f436f756e742063616e6e6f74206265206e6567617469766500000000000000006044820152606490fd5b346100a9575f3660031901126100a95760205f54604051908152f35b346100a9575f3660031901126100a9576020905f548152f3fea2646970667358221220471585b420a1ad0093820ff10129ec863f6df4bec186546249391fbc3cdbaa7c64736f6c634300081e0033"
)] contract Counter {
uint256 public count;

event CountIncreased(uint256 newCount);
Expand All @@ -39,6 +35,7 @@ contract Counter {
}
}
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).try_init();
Expand All @@ -52,29 +49,30 @@ async fn main() -> anyhow::Result<()> {
let contract_address = counter_contract.address();

let increase_filter = EventFilter::new()
.with_contract_address(*contract_address)
.with_event(Counter::CountIncreased::SIGNATURE);
.contract_address(*contract_address)
.event(Counter::CountIncreased::SIGNATURE);

let _ = counter_contract.increase().send().await?.get_receipt().await?;

let mut client = EventScanner::new().connect_ws::<Ethereum>(anvil.ws_endpoint_url()).await?;
let mut scanner =
EventScanner::historic().connect_ws::<Ethereum>(anvil.ws_endpoint_url()).await?;

let mut stream = client.create_event_stream(increase_filter);
let mut stream = scanner.subscribe(increase_filter);

sleep(Duration::from_secs(10)).await;
client.start_scanner(BlockNumberOrTag::Number(0), None).await.expect("failed to start scanner");
scanner.start().await.expect("failed to start scanner");

while let Some(message) = stream.next().await {
match message {
EventScannerMessage::Data(logs) => {
Message::Data(logs) => {
for log in logs {
info!("Callback successfully executed with event {:?}", log.inner.data);
}
}
EventScannerMessage::Error(e) => {
Message::Error(e) => {
error!("Received error: {}", e);
}
EventScannerMessage::Status(info) => {
Message::Status(info) => {
info!("Received info: {:?}", info);
}
}
Expand Down
Loading