Skip to content

Commit 7898c5d

Browse files
authored
feat(bin): RPC subcommand for starknet_getEvents (#354)
1 parent be3e5ab commit 7898c5d

File tree

2 files changed

+138
-2
lines changed

2 files changed

+138
-2
lines changed

bin/katana/src/cli/rpc/client.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use katana_primitives::block::{BlockIdOrTag, ConfirmedBlockIdOrTag};
77
use katana_primitives::class::ClassHash;
88
use katana_primitives::contract::{ContractAddress, StorageKey};
99
use katana_primitives::transaction::TxHash;
10+
use katana_rpc_types::event::EventFilterWithPage;
1011
use katana_rpc_types::FunctionCall;
1112
use serde_json::value::RawValue;
1213
use serde_json::Value;
@@ -88,6 +89,7 @@ const BLOCK_HASH_AND_NUMBER: &str = "starknet_blockHashAndNumber";
8889
const CHAIN_ID: &str = "starknet_chainId";
8990
const SYNCING: &str = "starknet_syncing";
9091
const GET_NONCE: &str = "starknet_getNonce";
92+
const GET_EVENTS: &str = "starknet_getEvents";
9193
const TRACE_TRANSACTION: &str = "starknet_traceTransaction";
9294
const TRACE_BLOCK_TRANSACTIONS: &str = "starknet_traceBlockTransactions";
9395

@@ -195,6 +197,10 @@ impl Client {
195197
self.send_request(GET_NONCE, rpc_params!(block_id, contract_address)).await
196198
}
197199

200+
pub async fn get_events(&self, filter: EventFilterWithPage) -> Result<Value> {
201+
self.send_request(GET_EVENTS, rpc_params!(filter)).await
202+
}
203+
198204
// Trace API methods
199205

200206
pub async fn trace_transaction(&self, transaction_hash: TxHash) -> Result<Value> {

bin/katana/src/cli/rpc/starknet.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use katana_primitives::contract::StorageKey;
66
use katana_primitives::execution::{EntryPointSelector, FunctionCall};
77
use katana_primitives::transaction::TxHash;
88
use katana_primitives::{ContractAddress, Felt};
9+
use katana_rpc_types::event::{EventFilter, EventFilterWithPage, ResultPageRequest};
910

1011
use super::client::Client;
1112

@@ -79,6 +80,10 @@ pub enum StarknetCommands {
7980
#[command(name = "nonce")]
8081
GetNonce(GetNonceArgs),
8182

83+
/// Get events matching filter criteria
84+
#[command(name = "events")]
85+
GetEvents(GetEventsArgs),
86+
8287
/// Get transaction execution trace
8388
#[command(name = "trace")]
8489
TraceTransaction(TxHashArgs),
@@ -220,8 +225,30 @@ pub struct CallArgs {
220225
#[derive(Debug, Args)]
221226
#[cfg_attr(test, derive(PartialEq, Eq))]
222227
pub struct GetEventsArgs {
223-
/// Event filter JSON
224-
filter: String,
228+
/// From block (number, hash, 'latest', or 'pending')
229+
#[arg(long)]
230+
from: Option<BlockIdArg>,
231+
232+
/// To block (number, hash, 'latest', or 'pending')
233+
#[arg(long)]
234+
to: Option<BlockIdArg>,
235+
236+
/// Contract address to filter events from
237+
#[arg(long)]
238+
address: Option<ContractAddress>,
239+
240+
/// Event keys filter. Each key group is comma-separated, groups are space-separated.
241+
/// Example: --keys 0x1 0x2,0x3 represents [[0x1], [0x2, 0x3]]
242+
#[arg(long, num_args = 0..)]
243+
keys: Vec<String>,
244+
245+
/// Continuation token from previous query for pagination
246+
#[arg(long)]
247+
continuation_token: Option<String>,
248+
249+
/// Number of events to return per page
250+
#[arg(long, default_value = "10")]
251+
chunk_size: u64,
225252
}
226253

227254
#[derive(Debug, Args)]
@@ -395,6 +422,29 @@ impl StarknetCommands {
395422
println!("{}", colored_json::to_colored_json_auto(&result)?);
396423
}
397424

425+
StarknetCommands::GetEvents(args) => {
426+
// Parse keys if provided
427+
let keys: Option<Vec<Vec<Felt>>> =
428+
if !args.keys.is_empty() { Some(parse_event_keys(&args.keys)?) } else { None };
429+
430+
let event_filter = EventFilter {
431+
keys,
432+
address: args.address,
433+
to_block: args.to.map(|b| b.0),
434+
from_block: args.from.map(|b| b.0),
435+
};
436+
437+
let result_page_request = ResultPageRequest {
438+
chunk_size: args.chunk_size,
439+
continuation_token: args.continuation_token,
440+
};
441+
442+
let filter = EventFilterWithPage { event_filter, result_page_request };
443+
444+
let result = client.get_events(filter).await?;
445+
println!("{}", colored_json::to_colored_json_auto(&result)?);
446+
}
447+
398448
StarknetCommands::TraceTransaction(args) => {
399449
let tx_hash = args.tx_hash;
400450
let result = client.trace_transaction(tx_hash).await?;
@@ -475,6 +525,26 @@ impl Default for ConfirmedBlockIdArg {
475525
}
476526
}
477527

528+
/// Parses event keys from CLI arguments.
529+
///
530+
/// Format: Each argument is a comma-separated list of felts.
531+
/// Example: ["0x1", "0x2,0x3", "0x4"] => [[0x1], [0x2, 0x3], [0x4]]
532+
fn parse_event_keys(keys: &[String]) -> Result<Vec<Vec<Felt>>> {
533+
keys.iter()
534+
.enumerate()
535+
.map(|(i, group)| {
536+
group
537+
.split(',')
538+
.map(|s| {
539+
s.trim()
540+
.parse::<Felt>()
541+
.with_context(|| format!("invalid felt in key group {}: '{}'", i, s))
542+
})
543+
.collect::<Result<Vec<Felt>>>()
544+
})
545+
.collect()
546+
}
547+
478548
#[cfg(test)]
479549
mod tests {
480550
use std::str::FromStr;
@@ -551,4 +621,64 @@ mod tests {
551621
let default = ConfirmedBlockIdArg::default();
552622
assert_matches!(default.0, ConfirmedBlockIdOrTag::Latest);
553623
}
624+
625+
use clap::Parser;
626+
627+
use super::{parse_event_keys, GetEventsArgs};
628+
629+
#[derive(Debug, Parser)]
630+
struct TestCli {
631+
#[command(flatten)]
632+
args: GetEventsArgs,
633+
}
634+
635+
#[test]
636+
fn get_events_args_single_keys() {
637+
let args = TestCli::try_parse_from(["test", "--keys", "0x1", "0x2", "0x3"]).unwrap();
638+
639+
let keys = parse_event_keys(&args.args.keys).unwrap();
640+
assert_eq!(keys.len(), 3);
641+
assert_eq!(keys[0], vec![felt!("0x1")]);
642+
assert_eq!(keys[1], vec![felt!("0x2")]);
643+
assert_eq!(keys[2], vec![felt!("0x3")]);
644+
}
645+
646+
#[test]
647+
fn get_events_args_multiple_keys() {
648+
let args =
649+
TestCli::try_parse_from(["test", "--keys", "0x9", "0x1,0x2,0x3", "0x4,0x5"]).unwrap();
650+
651+
let keys = parse_event_keys(&args.args.keys).unwrap();
652+
assert_eq!(keys.len(), 3);
653+
assert_eq!(keys[0], vec![felt!("0x9")]);
654+
assert_eq!(keys[1], vec![felt!("0x1"), felt!("0x2"), felt!("0x3")]);
655+
assert_eq!(keys[2], vec![felt!("0x4"), felt!("0x5")]);
656+
}
657+
658+
#[test]
659+
fn get_events_args_keys_with_whitespace() {
660+
let args = TestCli::try_parse_from(["test", "--keys", "0x1, 0x2 , 0x3"]).unwrap();
661+
662+
let keys = parse_event_keys(&args.args.keys).unwrap();
663+
assert_eq!(keys.len(), 1);
664+
assert_eq!(keys[0], vec![felt!("0x1"), felt!("0x2"), felt!("0x3")]);
665+
}
666+
667+
#[test]
668+
fn get_events_args_invalid_felt() {
669+
let args = TestCli::try_parse_from(["test", "--keys", "0x1", "invalid"]).unwrap();
670+
671+
let result = parse_event_keys(&args.args.keys);
672+
assert!(result.is_err());
673+
assert!(result.unwrap_err().to_string().contains("invalid felt in key group"));
674+
}
675+
676+
#[test]
677+
fn get_events_args_invalid_hex() {
678+
let args = TestCli::try_parse_from(["test", "--keys", "0x1,0xGGG"]).unwrap();
679+
680+
let result = parse_event_keys(&args.args.keys);
681+
assert!(result.is_err());
682+
assert!(result.unwrap_err().to_string().contains("invalid felt in key group 0"));
683+
}
554684
}

0 commit comments

Comments
 (0)