Skip to main content

stellar_rpc_client/
lib.rs

1use http::{uri::Authority, Uri};
2use itertools::Itertools;
3use jsonrpsee_core::params::ObjectParams;
4use jsonrpsee_core::{self, client::ClientT};
5use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder};
6use serde_aux::prelude::{
7    deserialize_default_from_null, deserialize_number_from_string,
8    deserialize_option_number_from_string,
9};
10use serde_with::{serde_as, DisplayFromStr};
11use stellar_xdr::curr::{
12    self as xdr, AccountEntry, AccountId, ContractDataEntry, ContractEvent, ContractId,
13    DiagnosticEvent, Error as XdrError, Hash, LedgerCloseMeta, LedgerEntryData, LedgerFootprint,
14    LedgerHeaderHistoryEntry, LedgerKey, LedgerKeyAccount, Limited, Limits, PublicKey, ReadXdr,
15    ScContractInstance, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData,
16    TransactionEnvelope, TransactionEvent, TransactionMetaV3, TransactionResult, Uint256, VecM,
17    WriteXdr,
18};
19
20use std::{
21    f64::consts::E,
22    fmt::Display,
23    str::FromStr,
24    sync::Arc,
25    time::{Duration, Instant},
26};
27
28use termcolor::{Color, ColorChoice, StandardStream, WriteColor};
29use termcolor_output::colored;
30use tokio::time::sleep;
31
32const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
33
34pub type LogEvents = fn(
35    footprint: &LedgerFootprint,
36    auth: &[VecM<SorobanAuthorizationEntry>],
37    events: &[DiagnosticEvent],
38) -> ();
39
40pub type LogResources = fn(resources: &SorobanResources) -> ();
41
42#[derive(thiserror::Error, Debug)]
43#[allow(deprecated)] // Can be removed once Error enum doesn't have any code marked deprecated inside
44pub enum Error {
45    #[error(transparent)]
46    InvalidAddress(#[from] stellar_strkey::DecodeError),
47    #[error("invalid response from server")]
48    InvalidResponse,
49    #[error("provided network passphrase {expected:?} does not match the server: {server:?}")]
50    InvalidNetworkPassphrase { expected: String, server: String },
51    #[error("xdr processing error: {0}")]
52    Xdr(#[from] XdrError),
53    #[error("invalid rpc url: {0}")]
54    InvalidRpcUrl(http::uri::InvalidUri),
55    #[error("invalid rpc url: {0}")]
56    InvalidRpcUrlFromUriParts(http::uri::InvalidUriParts),
57    #[error("invalid friendbot url: {0}")]
58    InvalidUrl(String),
59    #[error(transparent)]
60    JsonRpc(#[from] jsonrpsee_core::ClientError),
61    #[error("json decoding error: {0}")]
62    Serde(#[from] serde_json::Error),
63    #[error("transaction failed: {0}")]
64    TransactionFailed(String),
65    #[error("transaction submission failed: {0}")]
66    TransactionSubmissionFailed(String),
67    #[error("expected transaction status: {0}")]
68    UnexpectedTransactionStatus(String),
69    #[error("transaction submission timeout")]
70    TransactionSubmissionTimeout,
71    #[error("transaction simulation failed: {0}")]
72    TransactionSimulationFailed(String),
73    #[error("{0} not found: {1}")]
74    NotFound(String, String),
75    #[error("Missing result in successful response")]
76    MissingResult,
77    #[error("Failed to read Error response from server")]
78    MissingError,
79    #[error("Missing signing key for account {address}")]
80    MissingSignerForAddress { address: String },
81    #[error("cursor is not valid")]
82    InvalidCursor,
83    #[error("unexpected ({length}) simulate transaction result length")]
84    UnexpectedSimulateTransactionResultSize { length: usize },
85    #[error("unexpected ({count}) number of operations")]
86    UnexpectedOperationCount { count: usize },
87    #[error("Transaction contains unsupported operation type")]
88    UnsupportedOperationType,
89    #[error("unexpected contract code data type: {0:?}")]
90    UnexpectedContractCodeDataType(LedgerEntryData),
91    #[error("unexpected contract instance type: {0:?}")]
92    UnexpectedContractInstance(xdr::ScVal),
93    #[error("unexpected contract code got token {0:?}")]
94    #[deprecated(note = "To be removed in future versions")]
95    UnexpectedToken(ContractDataEntry),
96    #[error("Fee was too large {0}")]
97    LargeFee(u64),
98    #[error("Cannot authorize raw transactions")]
99    CannotAuthorizeRawTransaction,
100    #[error("Missing result for tnx")]
101    MissingOp,
102}
103
104#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
105pub struct SendTransactionResponse {
106    pub hash: String,
107    pub status: String,
108    #[serde(
109        rename = "errorResultXdr",
110        skip_serializing_if = "Option::is_none",
111        default
112    )]
113    pub error_result_xdr: Option<String>,
114    #[serde(rename = "latestLedger")]
115    pub latest_ledger: u32,
116    #[serde(
117        rename = "latestLedgerCloseTime",
118        deserialize_with = "deserialize_number_from_string"
119    )]
120    pub latest_ledger_close_time: u32,
121}
122
123#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
124// TODO: add ledger info and application order
125pub struct GetTransactionResponseRaw {
126    pub status: String,
127
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub ledger: Option<u32>,
130
131    #[serde(
132        rename = "applicationOrder",
133        skip_serializing_if = "Option::is_none",
134        default
135    )]
136    pub application_order: Option<u32>,
137
138    #[serde(rename = "feeBump", skip_serializing_if = "Option::is_none", default)]
139    pub fee_bump: Option<bool>,
140
141    #[serde(
142        rename = "envelopeXdr",
143        skip_serializing_if = "Option::is_none",
144        default
145    )]
146    pub envelope_xdr: Option<String>,
147
148    #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)]
149    pub result_xdr: Option<String>,
150
151    #[serde(
152        rename = "resultMetaXdr",
153        skip_serializing_if = "Option::is_none",
154        default
155    )]
156    pub result_meta_xdr: Option<String>,
157
158    #[serde(rename = "txHash", skip_serializing_if = "Option::is_none", default)]
159    pub tx_hash: Option<String>,
160
161    #[serde(
162        rename = "createdAt",
163        deserialize_with = "deserialize_option_i64_from_string_or_number",
164        skip_serializing_if = "Option::is_none",
165        default
166    )]
167    pub created_at: Option<i64>,
168
169    #[serde(rename = "events", skip_serializing_if = "Option::is_none", default)]
170    pub events: Option<GetTransactionEventsRaw>,
171}
172
173#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Default)]
174pub struct GetTransactionEventsRaw {
175    #[serde(
176        rename = "contractEventsXdr",
177        skip_serializing_if = "Option::is_none",
178        default
179    )]
180    pub contract_events_xdr: Option<Vec<Vec<String>>>,
181
182    #[serde(
183        rename = "diagnosticEventsXdr",
184        skip_serializing_if = "Option::is_none",
185        default
186    )]
187    pub diagnostic_events_xdr: Option<Vec<String>>,
188
189    #[serde(
190        rename = "transactionEventsXdr",
191        skip_serializing_if = "Option::is_none",
192        default
193    )]
194    pub transaction_events_xdr: Option<Vec<String>>,
195}
196
197#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
198pub struct GetTransactionEvents {
199    pub contract_events: Vec<Vec<ContractEvent>>,
200    pub diagnostic_events: Vec<DiagnosticEvent>,
201    pub transaction_events: Vec<TransactionEvent>,
202}
203
204#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
205pub struct GetTransactionResponse {
206    pub status: String,
207    pub ledger: Option<u32>,
208    pub application_order: Option<u32>,
209    pub fee_bump: Option<bool>,
210    pub tx_hash: Option<String>,
211    pub created_at: Option<i64>,
212    pub envelope: Option<xdr::TransactionEnvelope>,
213    pub result: Option<xdr::TransactionResult>,
214    pub result_meta: Option<xdr::TransactionMeta>,
215    pub events: GetTransactionEvents,
216}
217
218impl TryInto<GetTransactionResponse> for GetTransactionResponseRaw {
219    type Error = xdr::Error;
220
221    fn try_into(self) -> Result<GetTransactionResponse, Self::Error> {
222        let events = self.events.unwrap_or_default();
223        let result_meta: Option<xdr::TransactionMeta> = self
224            .result_meta_xdr
225            .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
226            .transpose()?;
227
228        let events = match result_meta {
229            Some(xdr::TransactionMeta::V4(_)) => GetTransactionEvents {
230                contract_events: events
231                    .contract_events_xdr
232                    .unwrap_or_default()
233                    .into_iter()
234                    .map(|es| {
235                        es.into_iter()
236                            .filter_map(|e| ContractEvent::from_xdr_base64(e, Limits::none()).ok())
237                            .collect::<Vec<_>>()
238                    })
239                    .collect::<Vec<Vec<ContractEvent>>>(),
240
241                diagnostic_events: events
242                    .diagnostic_events_xdr
243                    .unwrap_or_default()
244                    .iter()
245                    .filter_map(|e| DiagnosticEvent::from_xdr_base64(e, Limits::none()).ok())
246                    .collect(),
247
248                transaction_events: events
249                    .transaction_events_xdr
250                    .unwrap_or_default()
251                    .iter()
252                    .filter_map(|e| TransactionEvent::from_xdr_base64(e, Limits::none()).ok())
253                    .collect(),
254            },
255
256            Some(xdr::TransactionMeta::V3(TransactionMetaV3 {
257                soroban_meta: Some(ref meta),
258                ..
259            })) => GetTransactionEvents {
260                contract_events: vec![],
261                transaction_events: vec![],
262                diagnostic_events: meta.diagnostic_events.clone().into(),
263            },
264
265            _ => GetTransactionEvents {
266                contract_events: vec![],
267                transaction_events: vec![],
268                diagnostic_events: vec![],
269            },
270        };
271
272        Ok(GetTransactionResponse {
273            status: self.status,
274            ledger: self.ledger,
275            application_order: self.application_order,
276            fee_bump: self.fee_bump,
277            tx_hash: self.tx_hash,
278            created_at: self.created_at,
279            envelope: self
280                .envelope_xdr
281                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
282                .transpose()?,
283            result: self
284                .result_xdr
285                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
286                .transpose()?,
287            result_meta,
288            events,
289        })
290    }
291}
292
293impl GetTransactionResponse {
294    ///
295    /// # Errors
296    pub fn return_value(&self) -> Result<xdr::ScVal, Error> {
297        if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
298            soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
299            ..
300        })) = &self.result_meta
301        {
302            return Ok(return_value.clone());
303        }
304
305        if let Some(xdr::TransactionMeta::V4(xdr::TransactionMetaV4 {
306            soroban_meta:
307                Some(xdr::SorobanTransactionMetaV2 {
308                    return_value: Some(return_value),
309                    ..
310                }),
311            ..
312        })) = &self.result_meta
313        {
314            return Ok(return_value.clone());
315        }
316
317        Err(Error::MissingOp)
318    }
319}
320
321#[serde_as]
322#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
323pub struct GetTransactionsResponseRaw {
324    pub transactions: Vec<GetTransactionResponseRaw>,
325    #[serde(rename = "latestLedger")]
326    pub latest_ledger: u32,
327    #[serde(rename = "latestLedgerCloseTimestamp")]
328    pub latest_ledger_close_time: i64,
329    #[serde(rename = "oldestLedger")]
330    pub oldest_ledger: u32,
331    #[serde(rename = "oldestLedgerCloseTimestamp")]
332    pub oldest_ledger_close_time: i64,
333    #[serde_as(as = "DisplayFromStr")]
334    pub cursor: u64,
335}
336
337#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
338pub struct GetTransactionsResponse {
339    pub transactions: Vec<GetTransactionResponse>,
340    pub latest_ledger: u32,
341    pub latest_ledger_close_time: i64,
342    pub oldest_ledger: u32,
343    pub oldest_ledger_close_time: i64,
344    pub cursor: u64,
345}
346
347impl TryInto<GetTransactionsResponse> for GetTransactionsResponseRaw {
348    type Error = xdr::Error; // assuming xdr::Error or any other error type that you use
349
350    fn try_into(self) -> Result<GetTransactionsResponse, Self::Error> {
351        Ok(GetTransactionsResponse {
352            transactions: self
353                .transactions
354                .into_iter()
355                .map(TryInto::try_into)
356                .collect::<Result<Vec<_>, xdr::Error>>()?,
357            latest_ledger: self.latest_ledger,
358            latest_ledger_close_time: self.latest_ledger_close_time,
359            oldest_ledger: self.oldest_ledger,
360            oldest_ledger_close_time: self.oldest_ledger_close_time,
361            cursor: self.cursor,
362        })
363    }
364}
365
366#[serde_as]
367#[derive(serde::Serialize, Debug, Clone)]
368pub struct TransactionsPaginationOptions {
369    #[serde_as(as = "Option<DisplayFromStr>")]
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub cursor: Option<u64>,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub limit: Option<u32>,
374}
375
376#[derive(serde::Serialize, Debug, Clone)]
377pub struct GetTransactionsRequest {
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub start_ledger: Option<u32>,
380    pub pagination: Option<TransactionsPaginationOptions>,
381}
382
383#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
384pub struct LedgerEntryResult {
385    pub key: String,
386    pub xdr: String,
387    #[serde(rename = "lastModifiedLedgerSeq")]
388    pub last_modified_ledger: u32,
389    #[serde(
390        rename = "liveUntilLedgerSeq",
391        skip_serializing_if = "Option::is_none",
392        deserialize_with = "deserialize_option_number_from_string",
393        default
394    )]
395    pub live_until_ledger_seq_ledger_seq: Option<u32>,
396}
397
398#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
399pub struct GetLedgerEntriesResponse {
400    pub entries: Option<Vec<LedgerEntryResult>>,
401    #[serde(rename = "latestLedger")]
402    pub latest_ledger: i64,
403}
404
405#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
406pub struct GetNetworkResponse {
407    #[serde(
408        rename = "friendbotUrl",
409        skip_serializing_if = "Option::is_none",
410        default
411    )]
412    pub friendbot_url: Option<String>,
413    pub passphrase: String,
414    #[serde(rename = "protocolVersion")]
415    pub protocol_version: u32,
416}
417
418#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
419pub struct GetHealthResponse {
420    pub status: String,
421    #[serde(rename = "latestLedger")]
422    pub latest_ledger: u32,
423    #[serde(rename = "oldestLedger")]
424    pub oldest_ledger: u32,
425    #[serde(rename = "ledgerRetentionWindow")]
426    pub ledger_retention_window: u32,
427}
428
429#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
430pub struct GetVersionInfoResponse {
431    pub version: String,
432    #[serde(rename = "commitHash")]
433    pub commmit_hash: String,
434    #[serde(rename = "buildTimestamp")]
435    pub build_timestamp: String,
436    #[serde(rename = "captiveCoreVersion")]
437    pub captive_core_version: String,
438    #[serde(rename = "protocolVersion")]
439    pub protocol_version: u32,
440}
441
442#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
443pub struct GetLatestLedgerResponse {
444    pub id: String,
445    #[serde(rename = "protocolVersion")]
446    pub protocol_version: u32,
447    pub sequence: u32,
448}
449
450#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
451pub struct GetFeeStatsResponse {
452    #[serde(rename = "sorobanInclusionFee")]
453    pub soroban_inclusion_fee: FeeStat,
454    #[serde(rename = "inclusionFee")]
455    pub inclusion_fee: FeeStat,
456    #[serde(
457        rename = "latestLedger",
458        deserialize_with = "deserialize_number_from_string"
459    )]
460    pub latest_ledger: u32,
461}
462
463#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
464pub struct FeeStat {
465    pub max: String,
466    pub min: String,
467    // Fee value which occurs the most often
468    pub mode: String,
469    // 10th nearest-rank fee percentile
470    pub p10: String,
471    // 20th nearest-rank fee percentile
472    pub p20: String,
473    // 30th nearest-rank fee percentile
474    pub p30: String,
475    // 40th nearest-rank fee percentile
476    pub p40: String,
477    // 50th nearest-rank fee percentile
478    pub p50: String,
479    // 60th nearest-rank fee percentile
480    pub p60: String,
481    // 70th nearest-rank fee percentile
482    pub p70: String,
483    // 80th nearest-rank fee percentile
484    pub p80: String,
485    // 90th nearest-rank fee percentile.
486    pub p90: String,
487    // 95th nearest-rank fee percentile.
488    pub p95: String,
489    // 99th nearest-rank fee percentile
490    pub p99: String,
491    // How many transactions are part of the distribution
492    #[serde(
493        rename = "transactionCount",
494        deserialize_with = "deserialize_number_from_string"
495    )]
496    pub transaction_count: u32,
497    // How many consecutive ledgers form the distribution
498    #[serde(
499        rename = "ledgerCount",
500        deserialize_with = "deserialize_number_from_string"
501    )]
502    pub ledger_count: u32,
503}
504
505#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
506pub struct Cost {
507    #[serde(
508        rename = "cpuInsns",
509        deserialize_with = "deserialize_number_from_string"
510    )]
511    pub cpu_insns: u64,
512    #[serde(
513        rename = "memBytes",
514        deserialize_with = "deserialize_number_from_string"
515    )]
516    pub mem_bytes: u64,
517}
518
519#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
520pub struct SimulateHostFunctionResultRaw {
521    #[serde(deserialize_with = "deserialize_default_from_null")]
522    pub auth: Vec<String>,
523    pub xdr: String,
524}
525
526#[derive(Debug, Clone)]
527pub struct SimulateHostFunctionResult {
528    pub auth: Vec<SorobanAuthorizationEntry>,
529    pub xdr: xdr::ScVal,
530}
531
532#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
533#[serde(tag = "type")]
534pub enum LedgerEntryChange {
535    #[serde(rename = "created")]
536    Created { key: String, after: String },
537    #[serde(rename = "deleted")]
538    Deleted { key: String, before: String },
539    #[serde(rename = "updated")]
540    Updated {
541        key: String,
542        before: String,
543        after: String,
544    },
545}
546
547#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
548pub struct SimulateTransactionResponse {
549    #[serde(
550        rename = "minResourceFee",
551        deserialize_with = "deserialize_number_from_string",
552        default
553    )]
554    pub min_resource_fee: u64,
555    #[serde(default)]
556    pub cost: Cost,
557    #[serde(skip_serializing_if = "Vec::is_empty", default)]
558    pub results: Vec<SimulateHostFunctionResultRaw>,
559    #[serde(rename = "transactionData", default)]
560    pub transaction_data: String,
561    #[serde(
562        deserialize_with = "deserialize_default_from_null",
563        skip_serializing_if = "Vec::is_empty",
564        default
565    )]
566    pub events: Vec<String>,
567    #[serde(
568        rename = "restorePreamble",
569        skip_serializing_if = "Option::is_none",
570        default
571    )]
572    pub restore_preamble: Option<RestorePreamble>,
573    #[serde(
574        rename = "stateChanges",
575        skip_serializing_if = "Option::is_none",
576        default
577    )]
578    pub state_changes: Option<Vec<LedgerEntryChange>>,
579    #[serde(rename = "latestLedger")]
580    pub latest_ledger: u32,
581    #[serde(skip_serializing_if = "Option::is_none", default)]
582    pub error: Option<String>,
583}
584
585impl SimulateTransactionResponse {
586    ///
587    /// # Errors
588    pub fn results(&self) -> Result<Vec<SimulateHostFunctionResult>, Error> {
589        self.results
590            .iter()
591            .map(|r| {
592                Ok(SimulateHostFunctionResult {
593                    auth: r
594                        .auth
595                        .iter()
596                        .map(|a| {
597                            Ok(SorobanAuthorizationEntry::from_xdr_base64(
598                                a,
599                                Limits::none(),
600                            )?)
601                        })
602                        .collect::<Result<_, Error>>()?,
603                    xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?,
604                })
605            })
606            .collect()
607    }
608
609    ///
610    /// # Errors
611    pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
612        self.events
613            .iter()
614            .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?))
615            .collect()
616    }
617
618    ///
619    /// # Errors
620    pub fn transaction_data(&self) -> Result<SorobanTransactionData, Error> {
621        Ok(SorobanTransactionData::from_xdr_base64(
622            &self.transaction_data,
623            Limits::none(),
624        )?)
625    }
626}
627
628#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
629pub struct RestorePreamble {
630    #[serde(rename = "transactionData")]
631    pub transaction_data: String,
632    #[serde(
633        rename = "minResourceFee",
634        deserialize_with = "deserialize_number_from_string"
635    )]
636    pub min_resource_fee: u64,
637}
638
639#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
640pub struct GetEventsResponse {
641    #[serde(deserialize_with = "deserialize_default_from_null")]
642    pub events: Vec<Event>,
643    #[serde(rename = "latestLedger")]
644    pub latest_ledger: u32,
645    #[serde(rename = "latestLedgerCloseTime")]
646    pub latest_ledger_close_time: String,
647    #[serde(rename = "oldestLedger")]
648    pub oldest_ledger: u32,
649    #[serde(rename = "oldestLedgerCloseTime")]
650    pub oldest_ledger_close_time: String,
651    pub cursor: String,
652}
653
654#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
655pub struct GetLedgersResponse {
656    #[serde(rename = "latestLedger")]
657    pub latest_ledger: u32,
658    #[serde(
659        rename = "latestLedgerCloseTime",
660        deserialize_with = "deserialize_number_from_string"
661    )]
662    pub latest_ledger_close_time: i64,
663    #[serde(rename = "oldestLedger")]
664    pub oldest_ledger: u32,
665    #[serde(rename = "oldestLedgerCloseTime")]
666    pub oldest_ledger_close_time: i64,
667    pub cursor: String,
668    pub ledgers: Vec<Ledger>,
669}
670
671#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
672pub struct Ledger {
673    pub hash: String,
674    pub sequence: u32,
675    #[serde(rename = "ledgerCloseTime")]
676    pub ledger_close_time: String,
677    #[serde(rename = "headerXdr")]
678    pub header_xdr: String,
679    #[serde(rename = "headerJson")]
680    pub header_json: Option<LedgerHeaderHistoryEntry>,
681    #[serde(rename = "metadataXdr")]
682    pub metadata_xdr: String,
683    #[serde(rename = "metadataJson")]
684    pub metadata_json: Option<LedgerCloseMeta>,
685}
686
687#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
688pub struct Event {
689    #[serde(rename = "type")]
690    pub event_type: String,
691
692    pub ledger: u32,
693    #[serde(rename = "ledgerClosedAt")]
694    pub ledger_closed_at: String,
695    #[serde(rename = "contractId")]
696    pub contract_id: String,
697
698    pub id: String,
699
700    #[serde(
701        rename = "operationIndex",
702        default,
703        skip_serializing_if = "Option::is_none"
704    )]
705    pub operation_index: Option<u32>,
706    #[serde(
707        rename = "transactionIndex",
708        default,
709        skip_serializing_if = "Option::is_none"
710    )]
711    pub transaction_index: Option<u32>,
712    #[serde(rename = "txHash", default, skip_serializing_if = "Option::is_none")]
713    pub tx_hash: Option<String>,
714    #[deprecated(
715        note = "This field is deprecated by Stellar RPC. See https://stellar.org/blog/developers/protocol-23-upgrade-guide"
716    )]
717    #[serde(
718        rename = "inSuccessfulContractCall",
719        default,
720        skip_serializing_if = "Option::is_none"
721    )]
722    pub is_successful_contract_call: Option<bool>,
723
724    pub topic: Vec<String>,
725    pub value: String,
726}
727
728impl Display for Event {
729    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
730        writeln!(
731            f,
732            "Event {} [{}]:",
733            self.id,
734            self.event_type.to_ascii_uppercase()
735        )?;
736        writeln!(
737            f,
738            "  Ledger:   {} (closed at {})",
739            self.ledger, self.ledger_closed_at
740        )?;
741        writeln!(f, "  Contract: {}", self.contract_id)?;
742        writeln!(f, "  Topics:")?;
743
744        for topic in &self.topic {
745            let scval =
746                xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?;
747            writeln!(f, "            {scval:?}")?;
748        }
749
750        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())
751            .map_err(|_| std::fmt::Error)?;
752
753        writeln!(f, "  Value:    {scval:?}")
754    }
755}
756
757pub type SegmentFilter = String;
758pub type TopicFilter = Vec<SegmentFilter>;
759
760impl Event {
761    ///
762    /// # Errors
763    pub fn parse_cursor(&self) -> Result<(u64, i32), Error> {
764        parse_cursor(&self.id)
765    }
766
767    ///
768    /// # Errors
769    pub fn pretty_print(&self) -> Result<(), Box<dyn std::error::Error>> {
770        let mut stdout = StandardStream::stdout(ColorChoice::Auto);
771
772        if !stdout.supports_color() {
773            println!("{self}");
774            return Ok(());
775        }
776
777        let color = match self.event_type.as_str() {
778            "system" => Color::Yellow,
779            _ => Color::Blue,
780        };
781        colored!(
782            stdout,
783            "{}Event{} {}{}{} [{}{}{}{}]:\n",
784            bold!(true),
785            bold!(false),
786            fg!(Some(Color::Green)),
787            self.id,
788            reset!(),
789            bold!(true),
790            fg!(Some(color)),
791            self.event_type.to_ascii_uppercase(),
792            reset!(),
793        )?;
794
795        colored!(
796            stdout,
797            "  Ledger:   {}{}{} (closed at {}{}{})\n",
798            fg!(Some(Color::Green)),
799            self.ledger,
800            reset!(),
801            fg!(Some(Color::Green)),
802            self.ledger_closed_at,
803            reset!(),
804        )?;
805
806        colored!(
807            stdout,
808            "  Contract: {}{}{}\n",
809            fg!(Some(Color::Green)),
810            self.contract_id,
811            reset!(),
812        )?;
813
814        colored!(stdout, "  Topics:\n")?;
815        for topic in &self.topic {
816            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?;
817            colored!(
818                stdout,
819                "            {}{:?}{}\n",
820                fg!(Some(Color::Green)),
821                scval,
822                reset!(),
823            )?;
824        }
825
826        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?;
827        colored!(
828            stdout,
829            "  Value: {}{:?}{}\n\n",
830            fg!(Some(Color::Green)),
831            scval,
832            reset!(),
833        )?;
834
835        Ok(())
836    }
837}
838
839/// Defines non-root authorization for simulated transactions.
840pub enum AuthMode {
841    Enforce,
842    Record,
843    RecordAllowNonRoot,
844}
845
846#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
847pub enum EventType {
848    All,
849    Contract,
850    System,
851}
852
853#[derive(Clone, Debug, Eq, Hash, PartialEq)]
854pub enum LedgerStart {
855    Ledger(u32),
856    Cursor(String),
857}
858
859/// An inclusive ledger range. Construct via [`EventStart::ledger_range`].
860#[derive(Clone, Debug, Eq, Hash, PartialEq)]
861pub struct LedgerRange {
862    start: u32,
863    end: u32,
864}
865
866impl LedgerRange {
867    pub fn start(&self) -> u32 {
868        self.start
869    }
870
871    pub fn end(&self) -> u32 {
872        self.end
873    }
874}
875
876#[derive(Clone, Debug, Eq, Hash, PartialEq)]
877pub enum EventStart {
878    Ledger(u32),
879    /// A range of ledgers, inclusive. Use [`EventStart::ledger_range`] to
880    /// construct this variant with validation.
881    LedgerRange(LedgerRange),
882    Cursor(String),
883}
884
885impl EventStart {
886    /// Construct an [`EventStart::LedgerRange`] ensuring that `start <= end`.
887    ///
888    /// Returns an `Err` with a descriptive message if `start > end`.
889    pub fn ledger_range(start: u32, end: u32) -> Result<Self, String> {
890        if start > end {
891            return Err(format!(
892                "invalid ledger range: start ({start}) must be <= end ({end})"
893            ));
894        }
895        Ok(EventStart::LedgerRange(LedgerRange { start, end }))
896    }
897}
898
899#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
900pub struct FullLedgerEntry {
901    pub key: LedgerKey,
902    pub val: LedgerEntryData,
903    #[serde(rename = "lastModifiedLedgerSeq")]
904    pub last_modified_ledger: u32,
905    #[serde(
906        rename = "liveUntilLedgerSeq",
907        skip_serializing_if = "Option::is_none",
908        deserialize_with = "deserialize_option_number_from_string",
909        default
910    )]
911    pub live_until_ledger_seq: Option<u32>,
912}
913
914#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
915pub struct FullLedgerEntries {
916    pub entries: Vec<FullLedgerEntry>,
917    #[serde(rename = "latestLedger")]
918    pub latest_ledger: i64,
919}
920
921#[derive(Debug, Clone)]
922pub struct Client {
923    base_url: Arc<str>,
924    timeout_in_secs: u64,
925    http_client: Arc<HttpClient>,
926}
927
928#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
929/// Contains configuration for how resources will be calculated when simulating transactions.
930pub struct ResourceConfig {
931    /// Allow this many extra instructions when budgeting resources.
932    #[serde(rename = "instructionLeeway")]
933    pub instruction_leeway: u64,
934}
935
936#[allow(deprecated)] // Can be removed once Client doesn't have any code marked deprecated inside
937impl Client {
938    ///
939    /// # Errors
940    pub fn new(base_url: &str) -> Result<Self, Error> {
941        // Add the port to the base URL if there is no port explicitly included
942        // in the URL and the scheme allows us to infer a default port.
943        // Jsonrpsee requires a port to always be present even if one can be
944        // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048.
945        let uri = base_url.parse::<Uri>().map_err(Error::InvalidRpcUrl)?;
946        let mut parts = uri.into_parts();
947
948        if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) {
949            if authority.port().is_none() {
950                let port = match scheme.as_str() {
951                    "http" => Some(80),
952                    "https" => Some(443),
953                    _ => None,
954                };
955                if let Some(port) = port {
956                    let host = authority.host();
957                    parts.authority = Some(
958                        Authority::from_str(&format!("{host}:{port}"))
959                            .map_err(Error::InvalidRpcUrl)?,
960                    );
961                }
962            }
963        }
964
965        let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?;
966        let base_url = Arc::from(uri.to_string());
967        let headers = Self::default_http_headers();
968        let http_client = Arc::new(
969            HttpClientBuilder::default()
970                .set_headers(headers)
971                .build(&base_url)?,
972        );
973
974        Ok(Self {
975            base_url,
976            timeout_in_secs: 30,
977            http_client,
978        })
979    }
980
981    /// Create a new client with a timeout in seconds
982    /// # Errors
983    #[deprecated(
984        note = "To be marked private in a future major release. Please use `new_with_headers` instead."
985    )]
986    pub fn new_with_timeout(base_url: &str, timeout: u64) -> Result<Self, Error> {
987        let mut client = Self::new(base_url)?;
988        client.timeout_in_secs = timeout;
989        Ok(client)
990    }
991
992    /// Create a new client with additional headers
993    /// # Errors
994    pub fn new_with_headers(base_url: &str, additional_headers: HeaderMap) -> Result<Self, Error> {
995        let mut client = Self::new(base_url)?;
996        let mut headers = Self::default_http_headers();
997
998        for (key, value) in additional_headers {
999            headers.insert(key.ok_or(Error::InvalidResponse)?, value);
1000        }
1001
1002        let http_client = Arc::new(
1003            HttpClientBuilder::default()
1004                .set_headers(headers)
1005                .build(base_url)?,
1006        );
1007
1008        client.http_client = http_client;
1009        Ok(client)
1010    }
1011
1012    fn default_http_headers() -> HeaderMap {
1013        let mut headers = HeaderMap::new();
1014        headers.insert("X-Client-Name", unsafe {
1015            "rs-stellar-rpc-client".parse().unwrap_unchecked()
1016        });
1017        let version = VERSION.unwrap_or("devel");
1018        headers.insert("X-Client-Version", unsafe {
1019            version.parse().unwrap_unchecked()
1020        });
1021
1022        headers
1023    }
1024
1025    #[must_use]
1026    pub fn base_url(&self) -> &str {
1027        &self.base_url
1028    }
1029
1030    #[must_use]
1031    pub fn client(&self) -> &HttpClient {
1032        &self.http_client
1033    }
1034
1035    ///
1036    /// # Errors
1037    pub async fn friendbot_url(&self) -> Result<String, Error> {
1038        let network = self.get_network().await?;
1039        network.friendbot_url.ok_or_else(|| {
1040            Error::NotFound(
1041                "Friendbot".to_string(),
1042                "Friendbot is not available on this network".to_string(),
1043            )
1044        })
1045    }
1046    ///
1047    /// # Errors
1048    pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
1049        let server = self.get_network().await?.passphrase;
1050
1051        if let Some(expected) = expected {
1052            if expected != server {
1053                return Err(Error::InvalidNetworkPassphrase {
1054                    expected: expected.to_string(),
1055                    server,
1056                });
1057            }
1058        }
1059
1060        Ok(server)
1061    }
1062
1063    ///
1064    /// # Errors
1065    pub async fn get_network(&self) -> Result<GetNetworkResponse, Error> {
1066        Ok(self
1067            .client()
1068            .request("getNetwork", ObjectParams::new())
1069            .await?)
1070    }
1071
1072    ///
1073    /// # Errors
1074    pub async fn get_health(&self) -> Result<GetHealthResponse, Error> {
1075        Ok(self
1076            .client()
1077            .request("getHealth", ObjectParams::new())
1078            .await?)
1079    }
1080
1081    ///
1082    /// # Errors
1083    pub async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse, Error> {
1084        Ok(self
1085            .client()
1086            .request("getLatestLedger", ObjectParams::new())
1087            .await?)
1088    }
1089
1090    ///
1091    /// # Errors
1092    pub async fn get_ledgers(
1093        &self,
1094        start: LedgerStart,
1095        limit: Option<usize>,
1096        format: Option<String>,
1097    ) -> Result<GetLedgersResponse, Error> {
1098        let mut oparams = ObjectParams::new();
1099
1100        let mut pagination = serde_json::Map::new();
1101        if let Some(limit) = limit {
1102            pagination.insert("limit".to_string(), limit.into());
1103        }
1104
1105        match start {
1106            LedgerStart::Ledger(l) => oparams.insert("startLedger", l)?,
1107            LedgerStart::Cursor(c) => {
1108                pagination.insert("cursor".to_string(), c.into());
1109            }
1110        }
1111
1112        oparams.insert("pagination", pagination)?;
1113
1114        if let Some(f) = format {
1115            oparams.insert("xdrFormat", f)?;
1116        }
1117
1118        Ok(self.client().request("getLedgers", oparams).await?)
1119    }
1120
1121    ///
1122    /// # Errors
1123    pub async fn get_account(&self, address: &str) -> Result<AccountEntry, Error> {
1124        let key = LedgerKey::Account(LedgerKeyAccount {
1125            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
1126                stellar_strkey::ed25519::PublicKey::from_string(address)?.0,
1127            ))),
1128        });
1129        let keys = Vec::from([key]);
1130        let response = self.get_ledger_entries(&keys).await?;
1131        let entries = response.entries.unwrap_or_default();
1132
1133        if entries.is_empty() {
1134            return Err(Error::NotFound("Account".to_string(), address.to_owned()));
1135        }
1136
1137        let ledger_entry = &entries[0];
1138        let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none());
1139
1140        if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? {
1141            Ok(entry)
1142        } else {
1143            Err(Error::InvalidResponse)
1144        }
1145    }
1146
1147    /// Get network fee stats
1148    /// # Errors
1149    pub async fn get_fee_stats(&self) -> Result<GetFeeStatsResponse, Error> {
1150        Ok(self
1151            .client()
1152            .request("getFeeStats", ObjectParams::new())
1153            .await?)
1154    }
1155
1156    ///
1157    /// # Errors
1158    pub async fn get_version_info(&self) -> Result<GetVersionInfoResponse, Error> {
1159        Ok(self
1160            .client()
1161            .request("getVersionInfo", ObjectParams::new())
1162            .await?)
1163    }
1164
1165    /// Send a transaction to the network and get back the hash of the transaction.
1166    /// # Errors
1167    pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<Hash, Error> {
1168        let mut oparams = ObjectParams::new();
1169        oparams.insert("transaction", tx.to_xdr_base64(Limits::none())?)?;
1170        let SendTransactionResponse {
1171            hash,
1172            error_result_xdr,
1173            status,
1174            ..
1175        } = self
1176            .client()
1177            .request("sendTransaction", oparams)
1178            .await
1179            .map_err(|err| {
1180                Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}"))
1181            })?;
1182
1183        if status == "ERROR" {
1184            let error = error_result_xdr
1185                .ok_or(Error::MissingError)
1186                .and_then(|x| {
1187                    TransactionResult::read_xdr_base64(&mut Limited::new(
1188                        x.as_bytes(),
1189                        Limits::none(),
1190                    ))
1191                    .map_err(|_| Error::InvalidResponse)
1192                })
1193                .map(|r| r.result)?;
1194
1195            return Err(Error::TransactionSubmissionFailed(format!("{error:#?}")));
1196        }
1197
1198        Ok(Hash::from_str(&hash)?)
1199    }
1200
1201    ///
1202    /// # Errors
1203    pub async fn send_transaction_polling(
1204        &self,
1205        tx: &TransactionEnvelope,
1206    ) -> Result<GetTransactionResponse, Error> {
1207        let hash = self.send_transaction(tx).await?;
1208        self.get_transaction_polling(&hash, None).await
1209    }
1210
1211    ///
1212    /// # Errors
1213    pub async fn simulate_transaction_envelope(
1214        &self,
1215        tx: &TransactionEnvelope,
1216        auth_mode: Option<AuthMode>,
1217    ) -> Result<SimulateTransactionResponse, Error> {
1218        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1219        let mut params = ObjectParams::new();
1220
1221        params.insert("transaction", base64_tx)?;
1222
1223        match auth_mode {
1224            Some(AuthMode::Enforce) => {
1225                params.insert("authMode", "enforce")?;
1226            }
1227            Some(AuthMode::Record) => {
1228                params.insert("authMode", "record")?;
1229            }
1230            Some(AuthMode::RecordAllowNonRoot) => {
1231                params.insert("authMode", "record_allow_nonroot")?;
1232            }
1233            None => {}
1234        }
1235
1236        let sim_res = self.client().request("simulateTransaction", params).await?;
1237
1238        Ok(sim_res)
1239    }
1240
1241    /// Internal function, not to be used.
1242    /// # Errors
1243    pub async fn next_simulate_transaction_envelope(
1244        &self,
1245        tx: &TransactionEnvelope,
1246        auth_mode: Option<AuthMode>,
1247        resource_config: Option<ResourceConfig>,
1248    ) -> Result<SimulateTransactionResponse, Error> {
1249        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1250        let mut params = ObjectParams::new();
1251
1252        params.insert("transaction", base64_tx)?;
1253
1254        match auth_mode {
1255            Some(AuthMode::Enforce) => {
1256                params.insert("authMode", "enforce")?;
1257            }
1258            Some(AuthMode::Record) => {
1259                params.insert("authMode", "record")?;
1260            }
1261            Some(AuthMode::RecordAllowNonRoot) => {
1262                params.insert("authMode", "record_allow_nonroot")?;
1263            }
1264            None => {}
1265        }
1266
1267        if let Some(ref config) = resource_config {
1268            let mut resource_config_params = ObjectParams::new();
1269            resource_config_params.insert("instructionLeeway", config.instruction_leeway)?;
1270            params.insert("resourceConfig", resource_config)?;
1271        }
1272
1273        let sim_res = self.client().request("simulateTransaction", params).await?;
1274
1275        Ok(sim_res)
1276    }
1277
1278    ///
1279    /// # Errors
1280    pub async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse, Error> {
1281        let mut oparams = ObjectParams::new();
1282        oparams.insert("hash", tx_id)?;
1283        let resp: GetTransactionResponseRaw =
1284            self.client().request("getTransaction", oparams).await?;
1285
1286        Ok(resp.try_into()?)
1287    }
1288
1289    ///
1290    /// # Errors
1291    pub async fn get_transactions(
1292        &self,
1293        request: GetTransactionsRequest,
1294    ) -> Result<GetTransactionsResponse, Error> {
1295        let mut oparams = ObjectParams::new();
1296
1297        if let Some(start_ledger) = request.start_ledger {
1298            oparams.insert("startLedger", start_ledger)?;
1299        }
1300
1301        if let Some(pagination_params) = request.pagination {
1302            let pagination = serde_json::json!(pagination_params);
1303            oparams.insert("pagination", pagination)?;
1304        }
1305
1306        let resp: GetTransactionsResponseRaw =
1307            self.client().request("getTransactions", oparams).await?;
1308
1309        Ok(resp.try_into()?)
1310    }
1311
1312    /// Poll the transaction status. Can provide a timeout in seconds, otherwise uses the default timeout.
1313    ///
1314    /// It uses exponential backoff with a base of 1 second and a maximum of 30 seconds.
1315    ///
1316    /// # Errors
1317    /// - `Error::TransactionSubmissionTimeout` if the transaction status is not found within the timeout
1318    /// - `Error::TransactionSubmissionFailed` if the transaction status is "FAILED"
1319    /// - `Error::UnexpectedTransactionStatus` if the transaction status is not one of "SUCCESS", "FAILED", or ``NOT_FOUND``
1320    /// - `json_rpsee` Errors
1321    pub async fn get_transaction_polling(
1322        &self,
1323        tx_id: &Hash,
1324        timeout_s: Option<Duration>,
1325    ) -> Result<GetTransactionResponse, Error> {
1326        // Poll the transaction status
1327        let start = Instant::now();
1328        let timeout = timeout_s.unwrap_or(Duration::from_secs(self.timeout_in_secs));
1329        // see https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=50731
1330        // Is optimimal exponent for expontial backoff
1331        let exponential_backoff: f64 = 1.0 / (1.0 - E.powf(-1.0));
1332        let mut sleep_time = Duration::from_secs(1);
1333        loop {
1334            let response = self.get_transaction(tx_id).await?;
1335            match response.status.as_str() {
1336                "SUCCESS" => return Ok(response),
1337
1338                "FAILED" => {
1339                    return Err(Error::TransactionSubmissionFailed(format!(
1340                        "{:#?}",
1341                        response.result
1342                    )))
1343                }
1344
1345                "NOT_FOUND" => (),
1346                _ => {
1347                    return Err(Error::UnexpectedTransactionStatus(response.status));
1348                }
1349            }
1350
1351            if start.elapsed() > timeout {
1352                return Err(Error::TransactionSubmissionTimeout);
1353            }
1354
1355            sleep(sleep_time).await;
1356            sleep_time = Duration::from_secs_f64(sleep_time.as_secs_f64() * exponential_backoff);
1357        }
1358    }
1359
1360    ///
1361    /// # Errors
1362    pub async fn get_ledger_entries(
1363        &self,
1364        keys: &[LedgerKey],
1365    ) -> Result<GetLedgerEntriesResponse, Error> {
1366        let mut base64_keys: Vec<String> = vec![];
1367
1368        for k in keys {
1369            let base64_result = k.to_xdr_base64(Limits::none());
1370            if base64_result.is_err() {
1371                return Err(Error::Xdr(XdrError::Invalid));
1372            }
1373            base64_keys.push(k.to_xdr_base64(Limits::none())?);
1374        }
1375
1376        let mut oparams = ObjectParams::new();
1377        oparams.insert("keys", base64_keys)?;
1378
1379        Ok(self.client().request("getLedgerEntries", oparams).await?)
1380    }
1381
1382    ///
1383    /// # Errors
1384    pub async fn get_full_ledger_entries(
1385        &self,
1386        ledger_keys: &[LedgerKey],
1387    ) -> Result<FullLedgerEntries, Error> {
1388        let keys = ledger_keys
1389            .iter()
1390            .filter(|key| !matches!(key, LedgerKey::Ttl(_)))
1391            .map(Clone::clone)
1392            .collect::<Vec<_>>();
1393        let GetLedgerEntriesResponse {
1394            entries,
1395            latest_ledger,
1396        } = self.get_ledger_entries(&keys).await?;
1397        let entries = entries
1398            .unwrap_or_default()
1399            .iter()
1400            .map(
1401                |LedgerEntryResult {
1402                     key,
1403                     xdr,
1404                     last_modified_ledger,
1405                     live_until_ledger_seq_ledger_seq,
1406                 }| {
1407                    Ok(FullLedgerEntry {
1408                        key: LedgerKey::from_xdr_base64(key, Limits::none())?,
1409                        val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?,
1410                        live_until_ledger_seq: *live_until_ledger_seq_ledger_seq,
1411                        last_modified_ledger: *last_modified_ledger,
1412                    })
1413                },
1414            )
1415            .collect::<Result<Vec<_>, Error>>()?;
1416        Ok(FullLedgerEntries {
1417            entries,
1418            latest_ledger,
1419        })
1420    }
1421
1422    ///
1423    /// # Errors
1424    pub async fn get_events(
1425        &self,
1426        start: EventStart,
1427        event_type: Option<EventType>,
1428        contract_ids: &[String],
1429        topics: &[TopicFilter],
1430        limit: Option<usize>,
1431    ) -> Result<GetEventsResponse, Error> {
1432        let mut filters = serde_json::Map::new();
1433
1434        event_type
1435            .and_then(|t| match t {
1436                EventType::All => None, // all is the default, so avoid incl. the param
1437                EventType::Contract => Some("contract"),
1438                EventType::System => Some("system"),
1439            })
1440            .map(|t| filters.insert("type".to_string(), t.into()));
1441
1442        filters.insert("topics".to_string(), topics.into());
1443        filters.insert("contractIds".to_string(), contract_ids.into());
1444
1445        let mut pagination = serde_json::Map::new();
1446        if let Some(limit) = limit {
1447            pagination.insert("limit".to_string(), limit.into());
1448        }
1449
1450        let mut oparams = ObjectParams::new();
1451        match start {
1452            EventStart::Ledger(l) => oparams.insert("startLedger", l)?,
1453            EventStart::LedgerRange(r) => {
1454                oparams.insert("startLedger", r.start())?;
1455                oparams.insert("endLedger", r.end())?;
1456            }
1457            EventStart::Cursor(c) => {
1458                pagination.insert("cursor".to_string(), c.into());
1459            }
1460        }
1461        oparams.insert("filters", vec![filters])?;
1462        oparams.insert("pagination", pagination)?;
1463
1464        Ok(self.client().request("getEvents", oparams).await?)
1465    }
1466
1467    ///
1468    /// # Errors
1469    pub async fn get_contract_data(
1470        &self,
1471        contract_id: &[u8; 32],
1472    ) -> Result<ContractDataEntry, Error> {
1473        // Get the contract from the network
1474        let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData {
1475            contract: xdr::ScAddress::Contract(ContractId(xdr::Hash(*contract_id))),
1476            key: xdr::ScVal::LedgerKeyContractInstance,
1477            durability: xdr::ContractDataDurability::Persistent,
1478        });
1479        let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
1480        let entries = contract_ref.entries.unwrap_or_default();
1481        if entries.is_empty() {
1482            let contract_address = stellar_strkey::Contract(*contract_id).to_string();
1483            return Err(Error::NotFound(
1484                "Contract".to_string(),
1485                contract_address.to_string(),
1486            ));
1487        }
1488        let contract_ref_entry = &entries[0];
1489        match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1490            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1491            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1492        }
1493    }
1494
1495    ///
1496    /// # Errors
1497    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1498    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1499        match self.get_contract_data(contract_id).await? {
1500            xdr::ContractDataEntry {
1501                val:
1502                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1503                        executable: xdr::ContractExecutable::Wasm(hash),
1504                        ..
1505                    }),
1506                ..
1507            } => self.get_remote_wasm_from_hash(hash).await,
1508            scval => Err(Error::UnexpectedToken(scval)),
1509        }
1510    }
1511
1512    ///
1513    /// # Errors
1514    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1515    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1516        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1517        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1518        let entries = contract_data.entries.unwrap_or_default();
1519        if entries.is_empty() {
1520            return Err(Error::NotFound(
1521                "Contract Code".to_string(),
1522                hex::encode(hash),
1523            ));
1524        }
1525        let contract_data_entry = &entries[0];
1526        match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1527            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1528            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1529        }
1530    }
1531
1532    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1533    ///
1534    /// # Errors
1535    /// - Could fail to find contract or have a network error
1536    pub async fn get_contract_instance(
1537        &self,
1538        contract_id: &[u8; 32],
1539    ) -> Result<ScContractInstance, Error> {
1540        let contract_data = self.get_contract_data(contract_id).await?;
1541        match contract_data.val {
1542            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1543            scval => Err(Error::UnexpectedContractInstance(scval)),
1544        }
1545    }
1546}
1547
1548pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1549    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1550    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1551    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1552    Ok((toid_part, start_index))
1553}
1554
1555fn deserialize_option_i64_from_string_or_number<'de, D>(
1556    deserializer: D,
1557) -> Result<Option<i64>, D::Error>
1558where
1559    D: serde::Deserializer<'de>,
1560{
1561    use serde::Deserialize;
1562
1563    #[derive(Deserialize)]
1564    #[serde(untagged)]
1565    enum StringOrNumber {
1566        String(String),
1567        Number(i64),
1568    }
1569
1570    match Option::<StringOrNumber>::deserialize(deserializer)? {
1571        None => Ok(None),
1572        Some(StringOrNumber::String(s)) => {
1573            s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
1574        }
1575        Some(StringOrNumber::Number(n)) => Ok(Some(n)),
1576    }
1577}
1578
1579#[cfg(test)]
1580mod tests {
1581    use super::*;
1582    use std::env;
1583    use std::fs;
1584    use std::path::PathBuf;
1585
1586    // Determines whether or not a particular filter matches a topic based on
1587    // the same semantics as the RPC server:
1588    //
1589    //  - for an exact segment match, the filter is a base64-encoded ScVal
1590    //  - for a wildcard, single-segment match, the string "*" matches exactly
1591    //    one segment
1592    //  - for a wildcard, multi-segment match, the string "**" as the last
1593    //    element of the filter matches zero or more trailing segments
1594    //
1595    // [API Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx)
1596    // [Code Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203)
1597    fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
1598        if filter.is_empty() {
1599            return false;
1600        }
1601
1602        // "**" as the last filter element matches zero or more trailing segments.
1603        if let Some((last, prefix)) = filter.split_last() {
1604            if last == "**" {
1605                return topic.len() >= prefix.len()
1606                    && prefix
1607                        .iter()
1608                        .enumerate()
1609                        .all(|(i, s)| *s == "*" || topic[i] == *s);
1610            }
1611        }
1612
1613        filter.len() == topic.len()
1614            && filter
1615                .iter()
1616                .enumerate()
1617                .all(|(i, s)| *s == "*" || topic[i] == *s)
1618    }
1619
1620    fn get_repo_root() -> PathBuf {
1621        let mut path = env::current_exe().expect("Failed to get current executable path");
1622        // Navigate up the directory tree until we find the repository root
1623        while path.pop() {
1624            if path.join("Cargo.toml").exists() {
1625                return path;
1626            }
1627        }
1628        panic!("Could not find repository root");
1629    }
1630
1631    fn read_json_file(name: &str) -> String {
1632        let repo_root = get_repo_root();
1633        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1634        fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1635    }
1636
1637    #[test]
1638    fn simulation_transaction_response_parsing() {
1639        let s = r#"{
1640 "minResourceFee": "100000000",
1641 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1642 "transactionData": "",
1643 "latestLedger": 1234,
1644 "stateChanges": [{
1645    "type": "created",
1646    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1647    "before": null,
1648    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1649  }]
1650  }"#;
1651
1652        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1653        assert_eq!(
1654            resp.state_changes.unwrap()[0],
1655            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1656        );
1657        assert_eq!(resp.min_resource_fee, 100_000_000);
1658    }
1659
1660    #[test]
1661    fn simulation_transaction_response_parsing_mostly_empty() {
1662        let s = r#"{
1663 "latestLedger": 1234
1664        }"#;
1665
1666        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1667        assert_eq!(resp.latest_ledger, 1_234);
1668    }
1669
1670    #[test]
1671    fn test_parse_transaction_response_p23() {
1672        let response_content = read_json_file("transaction_response_p23.json");
1673        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1674            .expect("Failed to parse JSON from transaction_response_p23.json");
1675        let result = full_response["result"].clone();
1676        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1677            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1678        let response: GetTransactionResponse = raw_response
1679            .try_into()
1680            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1681
1682        assert_eq!(2, response.events.transaction_events.iter().len());
1683        assert_eq!(1, response.events.contract_events.len());
1684        assert_eq!(21, response.events.diagnostic_events.iter().len());
1685        assert_eq!(
1686            response.tx_hash.as_deref(),
1687            Some("bfe15f83ea850b7bf86fd7152f9074033f2aec2a045e40a8872ac56726a6e35c")
1688        );
1689        assert_eq!(response.created_at, Some(1_751_666_924));
1690        assert_eq!(response.application_order, Some(1));
1691        assert_eq!(response.fee_bump, Some(false));
1692    }
1693
1694    #[test]
1695    fn test_parse_transaction_response_p22() {
1696        let response_content = read_json_file("transaction_response_p22.json");
1697        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1698            .expect("Failed to parse JSON from transaction_response_p22.json");
1699        let result = full_response["result"].clone();
1700        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1701            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1702        let response: GetTransactionResponse = raw_response
1703            .try_into()
1704            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1705
1706        assert_eq!(23, response.events.diagnostic_events.iter().len());
1707        assert_eq!(
1708            response.tx_hash.as_deref(),
1709            Some("a738ccc7f8f457d4367b78c098569ebee23258c71f128d7a2c61585652345937")
1710        );
1711        assert_eq!(response.created_at, Some(1_751_747_980));
1712        assert_eq!(response.application_order, Some(1));
1713        assert_eq!(response.fee_bump, Some(false));
1714    }
1715
1716    #[test]
1717    fn test_parse_get_transactions_response() {
1718        let response_content = read_json_file("transactions_response.json");
1719
1720        // Parse the entire response
1721        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1722            .expect("Failed to parse JSON from transactions_response.json");
1723
1724        // Extract the "result" field
1725        let result = full_response["result"].clone();
1726        // Parse the "result" content as GetTransactionsResponseRaw
1727        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1728            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1729
1730        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1731        let response: GetTransactionsResponse = raw_response
1732            .try_into()
1733            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1734
1735        // Assertions
1736        assert_eq!(response.transactions.len(), 5);
1737        assert_eq!(response.latest_ledger, 556_962);
1738        assert_eq!(response.cursor, 2_379_420_471_922_689);
1739
1740        // Additional assertions for specific transaction attributes
1741        assert_eq!(response.transactions[0].status, "SUCCESS");
1742        //assert_eq!(response.transactions[0].application_order, 1);
1743        //assert_eq!(response.transactions[0].ledger, 554000);
1744    }
1745
1746    #[test]
1747    fn test_rpc_url_default_ports() {
1748        // Default ports are added.
1749        let client = Client::new("http://example.com").unwrap();
1750        assert_eq!(client.base_url(), "http://example.com:80/");
1751        let client = Client::new("https://example.com").unwrap();
1752        assert_eq!(client.base_url(), "https://example.com:443/");
1753
1754        // Ports are not added when already present.
1755        let client = Client::new("http://example.com:8080").unwrap();
1756        assert_eq!(client.base_url(), "http://example.com:8080/");
1757        let client = Client::new("https://example.com:8080").unwrap();
1758        assert_eq!(client.base_url(), "https://example.com:8080/");
1759
1760        // Paths are not modified.
1761        let client = Client::new("http://example.com/a/b/c").unwrap();
1762        assert_eq!(client.base_url(), "http://example.com:80/a/b/c");
1763        let client = Client::new("https://example.com/a/b/c").unwrap();
1764        assert_eq!(client.base_url(), "https://example.com:443/a/b/c");
1765        let client = Client::new("http://example.com/a/b/c/").unwrap();
1766        assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1767        let client = Client::new("https://example.com/a/b/c/").unwrap();
1768        assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1769        let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1770        assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1771        let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1772        assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1773    }
1774
1775    #[test]
1776    fn test_parse_events_response() {
1777        let response_content = read_json_file("events_response_p23.json");
1778        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1779            .expect("Failed to parse JSON from events_response_p23.json");
1780        let result = full_response["result"].clone();
1781
1782        // Deserialize
1783        let resp: GetEventsResponse = serde_json::from_value(result.clone())
1784            .expect("Failed to parse 'result' into GetEventsResponse");
1785
1786        // Verify specific field values from the fixture.
1787        assert_eq!(resp.events[0].operation_index, Some(0));
1788        assert_eq!(resp.events[0].transaction_index, Some(0));
1789        assert_eq!(
1790            resp.events[0].tx_hash.as_deref(),
1791            Some("e42da3c70c90cc319e2cfaa2f69a7bd04aefcc4159b12caa0df216fbb3ab43b4")
1792        );
1793        #[allow(deprecated)]
1794        {
1795            assert_eq!(resp.events[0].is_successful_contract_call, Some(true));
1796        }
1797
1798        // Re-serialize
1799        let reserialized = serde_json::to_value(&resp).expect("Failed to serialize response");
1800
1801        // Compare
1802        assert_eq!(
1803            result, reserialized,
1804            "Deserialization should preserve all data"
1805        );
1806    }
1807
1808    #[test]
1809    fn test_parse_events_response_p22() {
1810        // Ensure we can still deserialize Event from protocol 22 responses,
1811        // which do not include operationIndex or transactionIndex.
1812        let response_content = read_json_file("events_response_p22.json");
1813        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1814            .expect("Failed to parse JSON from events_response_p22.json");
1815        let first_event = full_response["result"]["events"][0].clone();
1816
1817        // Deserialize; this should succeed even though some fields are absent.
1818        let event: Event = serde_json::from_value(first_event)
1819            .expect("Failed to parse protocol 22 event into Event");
1820
1821        assert!(event.operation_index.is_none());
1822        assert!(event.transaction_index.is_none());
1823    }
1824
1825    #[test]
1826    fn test_ledger_range_valid() {
1827        let r = EventStart::ledger_range(10, 20).unwrap();
1828        assert_eq!(r, EventStart::ledger_range(10, 20).unwrap());
1829
1830        // equal start and end is valid
1831        assert!(EventStart::ledger_range(10, 10).is_ok());
1832    }
1833
1834    #[test]
1835    fn test_ledger_range_invalid() {
1836        let err = EventStart::ledger_range(100, 50).unwrap_err();
1837        assert!(err.contains("start (100)") && err.contains("end (50)"));
1838    }
1839
1840    #[test]
1841    // Taken from [RPC server
1842    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1843    fn test_does_topic_match() {
1844        struct TestCase<'a> {
1845            name: &'a str,
1846            filter: Vec<&'a str>,
1847            includes: Vec<Vec<&'a str>>,
1848            excludes: Vec<Vec<&'a str>>,
1849        }
1850
1851        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1852        let number = "AAAAAQB6Mcc=";
1853        let star = "*";
1854
1855        for tc in vec![
1856            // No filter means match nothing.
1857            TestCase {
1858                name: "<empty>",
1859                filter: vec![],
1860                includes: vec![],
1861                excludes: vec![vec![xfer]],
1862            },
1863            // "*" should match "transfer/" but not "transfer/transfer" or
1864            // "transfer/amount", because * is specified as a SINGLE segment
1865            // wildcard.
1866            TestCase {
1867                name: "*",
1868                filter: vec![star],
1869                includes: vec![vec![xfer]],
1870                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1871            },
1872            // "*/transfer" should match anything preceding "transfer", but
1873            // nothing that isn't exactly two segments long.
1874            TestCase {
1875                name: "*/transfer",
1876                filter: vec![star, xfer],
1877                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1878                excludes: vec![
1879                    vec![number],
1880                    vec![number, number],
1881                    vec![number, xfer, number],
1882                    vec![xfer],
1883                    vec![xfer, number],
1884                    vec![xfer, xfer, xfer],
1885                ],
1886            },
1887            // The inverse case of before: "transfer/*" should match any single
1888            // segment after a segment that is exactly "transfer", but no
1889            // additional segments.
1890            TestCase {
1891                name: "transfer/*",
1892                filter: vec![xfer, star],
1893                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1894                excludes: vec![
1895                    vec![number],
1896                    vec![number, number],
1897                    vec![number, xfer, number],
1898                    vec![xfer],
1899                    vec![number, xfer],
1900                    vec![xfer, xfer, xfer],
1901                ],
1902            },
1903            // Here, we extend to exactly two wild segments after transfer.
1904            TestCase {
1905                name: "transfer/*/*",
1906                filter: vec![xfer, star, star],
1907                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1908                excludes: vec![
1909                    vec![number],
1910                    vec![number, number],
1911                    vec![number, xfer],
1912                    vec![number, xfer, number, number],
1913                    vec![xfer],
1914                    vec![xfer, xfer, xfer, xfer],
1915                ],
1916            },
1917            // Here, we ensure wildcards can be in the middle of a filter: only
1918            // exact matches happen on the ends, while the middle can be
1919            // anything.
1920            TestCase {
1921                name: "transfer/*/number",
1922                filter: vec![xfer, star, number],
1923                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1924                excludes: vec![
1925                    vec![number],
1926                    vec![number, number],
1927                    vec![number, number, number],
1928                    vec![number, xfer, number],
1929                    vec![xfer],
1930                    vec![number, xfer],
1931                    vec![xfer, xfer, xfer],
1932                    vec![xfer, number, xfer],
1933                ],
1934            },
1935            // "**" as the sole filter element matches any topic (0+ segments).
1936            TestCase {
1937                name: "**",
1938                filter: vec!["**"],
1939                includes: vec![
1940                    vec![],
1941                    vec![xfer],
1942                    vec![xfer, number],
1943                    vec![xfer, number, number],
1944                ],
1945                excludes: vec![],
1946            },
1947            // "transfer/**" matches "transfer" followed by 0+ segments.
1948            TestCase {
1949                name: "transfer/**",
1950                filter: vec![xfer, "**"],
1951                includes: vec![
1952                    vec![xfer],
1953                    vec![xfer, number],
1954                    vec![xfer, number, number],
1955                    vec![xfer, xfer, xfer],
1956                ],
1957                excludes: vec![
1958                    vec![],
1959                    vec![number],
1960                    vec![number, xfer],
1961                    vec![number, number],
1962                ],
1963            },
1964            // "transfer/number/**" matches exactly "transfer/number" followed
1965            // by 0+ segments.
1966            TestCase {
1967                name: "transfer/number/**",
1968                filter: vec![xfer, number, "**"],
1969                includes: vec![
1970                    vec![xfer, number],
1971                    vec![xfer, number, number],
1972                    vec![xfer, number, xfer, number],
1973                ],
1974                excludes: vec![
1975                    vec![],
1976                    vec![xfer],
1977                    vec![number],
1978                    vec![number, xfer],
1979                    vec![xfer, xfer],
1980                ],
1981            },
1982        ] {
1983            for topic in tc.includes {
1984                assert!(
1985                    does_topic_match(
1986                        &topic
1987                            .iter()
1988                            .map(std::string::ToString::to_string)
1989                            .collect::<Vec<String>>(),
1990                        &tc.filter
1991                            .iter()
1992                            .map(std::string::ToString::to_string)
1993                            .collect::<Vec<String>>()
1994                    ),
1995                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
1996                    tc.name,
1997                    topic,
1998                    tc.filter
1999                );
2000            }
2001
2002            for topic in tc.excludes {
2003                assert!(
2004                    !does_topic_match(
2005                        // make deep copies of the vecs
2006                        &topic
2007                            .iter()
2008                            .map(std::string::ToString::to_string)
2009                            .collect::<Vec<String>>(),
2010                        &tc.filter
2011                            .iter()
2012                            .map(std::string::ToString::to_string)
2013                            .collect::<Vec<String>>()
2014                    ),
2015                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
2016                    tc.name,
2017                    topic,
2018                    tc.filter
2019                );
2020            }
2021        }
2022    }
2023}