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::Error),
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("Contract".to_string(), contract_address));
1484        }
1485        let contract_ref_entry = &entries[0];
1486        match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1487            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1488            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1489        }
1490    }
1491
1492    ///
1493    /// # Errors
1494    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1495    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1496        match self.get_contract_data(contract_id).await? {
1497            xdr::ContractDataEntry {
1498                val:
1499                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1500                        executable: xdr::ContractExecutable::Wasm(hash),
1501                        ..
1502                    }),
1503                ..
1504            } => self.get_remote_wasm_from_hash(hash).await,
1505            scval => Err(Error::UnexpectedToken(scval)),
1506        }
1507    }
1508
1509    ///
1510    /// # Errors
1511    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1512    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1513        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1514        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1515        let entries = contract_data.entries.unwrap_or_default();
1516        if entries.is_empty() {
1517            return Err(Error::NotFound(
1518                "Contract Code".to_string(),
1519                hex::encode(hash),
1520            ));
1521        }
1522        let contract_data_entry = &entries[0];
1523        match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1524            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1525            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1526        }
1527    }
1528
1529    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1530    ///
1531    /// # Errors
1532    /// - Could fail to find contract or have a network error
1533    pub async fn get_contract_instance(
1534        &self,
1535        contract_id: &[u8; 32],
1536    ) -> Result<ScContractInstance, Error> {
1537        let contract_data = self.get_contract_data(contract_id).await?;
1538        match contract_data.val {
1539            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1540            scval => Err(Error::UnexpectedContractInstance(scval)),
1541        }
1542    }
1543}
1544
1545pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1546    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1547    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1548    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1549    Ok((toid_part, start_index))
1550}
1551
1552fn deserialize_option_i64_from_string_or_number<'de, D>(
1553    deserializer: D,
1554) -> Result<Option<i64>, D::Error>
1555where
1556    D: serde::Deserializer<'de>,
1557{
1558    use serde::Deserialize;
1559
1560    #[derive(Deserialize)]
1561    #[serde(untagged)]
1562    enum StringOrNumber {
1563        String(String),
1564        Number(i64),
1565    }
1566
1567    match Option::<StringOrNumber>::deserialize(deserializer)? {
1568        None => Ok(None),
1569        Some(StringOrNumber::String(s)) => {
1570            s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
1571        }
1572        Some(StringOrNumber::Number(n)) => Ok(Some(n)),
1573    }
1574}
1575
1576#[cfg(test)]
1577mod tests {
1578    use super::*;
1579    use std::env;
1580    use std::fs;
1581    use std::path::PathBuf;
1582
1583    // Determines whether or not a particular filter matches a topic based on
1584    // the same semantics as the RPC server:
1585    //
1586    //  - for an exact segment match, the filter is a base64-encoded ScVal
1587    //  - for a wildcard, single-segment match, the string "*" matches exactly
1588    //    one segment
1589    //  - for a wildcard, multi-segment match, the string "**" as the last
1590    //    element of the filter matches zero or more trailing segments
1591    //
1592    // [API Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx)
1593    // [Code Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203)
1594    fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
1595        if filter.is_empty() {
1596            return false;
1597        }
1598
1599        // "**" as the last filter element matches zero or more trailing segments.
1600        if let Some((last, prefix)) = filter.split_last() {
1601            if last == "**" {
1602                return topic.len() >= prefix.len()
1603                    && prefix
1604                        .iter()
1605                        .enumerate()
1606                        .all(|(i, s)| *s == "*" || topic[i] == *s);
1607            }
1608        }
1609
1610        filter.len() == topic.len()
1611            && filter
1612                .iter()
1613                .enumerate()
1614                .all(|(i, s)| *s == "*" || topic[i] == *s)
1615    }
1616
1617    fn get_repo_root() -> PathBuf {
1618        let mut path = env::current_exe().expect("Failed to get current executable path");
1619        // Navigate up the directory tree until we find the repository root
1620        while path.pop() {
1621            if path.join("Cargo.toml").exists() {
1622                return path;
1623            }
1624        }
1625        panic!("Could not find repository root");
1626    }
1627
1628    fn read_json_file(name: &str) -> String {
1629        let repo_root = get_repo_root();
1630        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1631        fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1632    }
1633
1634    #[test]
1635    fn simulation_transaction_response_parsing() {
1636        let s = r#"{
1637 "minResourceFee": "100000000",
1638 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1639 "transactionData": "",
1640 "latestLedger": 1234,
1641 "stateChanges": [{
1642    "type": "created",
1643    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1644    "before": null,
1645    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1646  }]
1647  }"#;
1648
1649        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1650        assert_eq!(
1651            resp.state_changes.unwrap()[0],
1652            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1653        );
1654        assert_eq!(resp.min_resource_fee, 100_000_000);
1655    }
1656
1657    #[test]
1658    fn simulation_transaction_response_parsing_mostly_empty() {
1659        let s = r#"{
1660 "latestLedger": 1234
1661        }"#;
1662
1663        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1664        assert_eq!(resp.latest_ledger, 1_234);
1665    }
1666
1667    #[test]
1668    fn test_parse_transaction_response_p23() {
1669        let response_content = read_json_file("transaction_response_p23.json");
1670        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1671            .expect("Failed to parse JSON from transaction_response_p23.json");
1672        let result = full_response["result"].clone();
1673        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1674            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1675        let response: GetTransactionResponse = raw_response
1676            .try_into()
1677            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1678
1679        assert_eq!(2, response.events.transaction_events.iter().len());
1680        assert_eq!(1, response.events.contract_events.len());
1681        assert_eq!(21, response.events.diagnostic_events.iter().len());
1682        assert_eq!(
1683            response.tx_hash.as_deref(),
1684            Some("bfe15f83ea850b7bf86fd7152f9074033f2aec2a045e40a8872ac56726a6e35c")
1685        );
1686        assert_eq!(response.created_at, Some(1_751_666_924));
1687        assert_eq!(response.application_order, Some(1));
1688        assert_eq!(response.fee_bump, Some(false));
1689    }
1690
1691    #[test]
1692    fn test_parse_transaction_response_p22() {
1693        let response_content = read_json_file("transaction_response_p22.json");
1694        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1695            .expect("Failed to parse JSON from transaction_response_p22.json");
1696        let result = full_response["result"].clone();
1697        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1698            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1699        let response: GetTransactionResponse = raw_response
1700            .try_into()
1701            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1702
1703        assert_eq!(23, response.events.diagnostic_events.iter().len());
1704        assert_eq!(
1705            response.tx_hash.as_deref(),
1706            Some("a738ccc7f8f457d4367b78c098569ebee23258c71f128d7a2c61585652345937")
1707        );
1708        assert_eq!(response.created_at, Some(1_751_747_980));
1709        assert_eq!(response.application_order, Some(1));
1710        assert_eq!(response.fee_bump, Some(false));
1711    }
1712
1713    #[test]
1714    fn test_parse_get_transactions_response() {
1715        let response_content = read_json_file("transactions_response.json");
1716
1717        // Parse the entire response
1718        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1719            .expect("Failed to parse JSON from transactions_response.json");
1720
1721        // Extract the "result" field
1722        let result = full_response["result"].clone();
1723        // Parse the "result" content as GetTransactionsResponseRaw
1724        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1725            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1726
1727        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1728        let response: GetTransactionsResponse = raw_response
1729            .try_into()
1730            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1731
1732        // Assertions
1733        assert_eq!(response.transactions.len(), 5);
1734        assert_eq!(response.latest_ledger, 556_962);
1735        assert_eq!(response.cursor, 2_379_420_471_922_689);
1736
1737        // Additional assertions for specific transaction attributes
1738        assert_eq!(response.transactions[0].status, "SUCCESS");
1739        //assert_eq!(response.transactions[0].application_order, 1);
1740        //assert_eq!(response.transactions[0].ledger, 554000);
1741    }
1742
1743    #[test]
1744    fn test_rpc_url_default_ports() {
1745        // Default ports are added.
1746        let client = Client::new("http://example.com").unwrap();
1747        assert_eq!(client.base_url(), "http://example.com:80/");
1748        let client = Client::new("https://example.com").unwrap();
1749        assert_eq!(client.base_url(), "https://example.com:443/");
1750
1751        // Ports are not added when already present.
1752        let client = Client::new("http://example.com:8080").unwrap();
1753        assert_eq!(client.base_url(), "http://example.com:8080/");
1754        let client = Client::new("https://example.com:8080").unwrap();
1755        assert_eq!(client.base_url(), "https://example.com:8080/");
1756
1757        // Paths are not modified.
1758        let client = Client::new("http://example.com/a/b/c").unwrap();
1759        assert_eq!(client.base_url(), "http://example.com:80/a/b/c");
1760        let client = Client::new("https://example.com/a/b/c").unwrap();
1761        assert_eq!(client.base_url(), "https://example.com:443/a/b/c");
1762        let client = Client::new("http://example.com/a/b/c/").unwrap();
1763        assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1764        let client = Client::new("https://example.com/a/b/c/").unwrap();
1765        assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1766        let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1767        assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1768        let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1769        assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1770    }
1771
1772    #[test]
1773    fn test_parse_events_response() {
1774        let response_content = read_json_file("events_response_p23.json");
1775        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1776            .expect("Failed to parse JSON from events_response_p23.json");
1777        let result = full_response["result"].clone();
1778
1779        // Deserialize
1780        let resp: GetEventsResponse = serde_json::from_value(result.clone())
1781            .expect("Failed to parse 'result' into GetEventsResponse");
1782
1783        // Verify specific field values from the fixture.
1784        assert_eq!(resp.events[0].operation_index, Some(0));
1785        assert_eq!(resp.events[0].transaction_index, Some(0));
1786        assert_eq!(
1787            resp.events[0].tx_hash.as_deref(),
1788            Some("e42da3c70c90cc319e2cfaa2f69a7bd04aefcc4159b12caa0df216fbb3ab43b4")
1789        );
1790        #[allow(deprecated)]
1791        {
1792            assert_eq!(resp.events[0].is_successful_contract_call, Some(true));
1793        }
1794
1795        // Re-serialize
1796        let reserialized = serde_json::to_value(&resp).expect("Failed to serialize response");
1797
1798        // Compare
1799        assert_eq!(
1800            result, reserialized,
1801            "Deserialization should preserve all data"
1802        );
1803    }
1804
1805    #[test]
1806    fn test_parse_events_response_p22() {
1807        // Ensure we can still deserialize Event from protocol 22 responses,
1808        // which do not include operationIndex or transactionIndex.
1809        let response_content = read_json_file("events_response_p22.json");
1810        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1811            .expect("Failed to parse JSON from events_response_p22.json");
1812        let first_event = full_response["result"]["events"][0].clone();
1813
1814        // Deserialize; this should succeed even though some fields are absent.
1815        let event: Event = serde_json::from_value(first_event)
1816            .expect("Failed to parse protocol 22 event into Event");
1817
1818        assert!(event.operation_index.is_none());
1819        assert!(event.transaction_index.is_none());
1820    }
1821
1822    #[test]
1823    fn test_ledger_range_valid() {
1824        let r = EventStart::ledger_range(10, 20).unwrap();
1825        assert_eq!(r, EventStart::ledger_range(10, 20).unwrap());
1826
1827        // equal start and end is valid
1828        assert!(EventStart::ledger_range(10, 10).is_ok());
1829    }
1830
1831    #[test]
1832    fn test_ledger_range_invalid() {
1833        let err = EventStart::ledger_range(100, 50).unwrap_err();
1834        assert!(err.contains("start (100)") && err.contains("end (50)"));
1835    }
1836
1837    #[test]
1838    // Taken from [RPC server
1839    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1840    fn test_does_topic_match() {
1841        struct TestCase<'a> {
1842            name: &'a str,
1843            filter: Vec<&'a str>,
1844            includes: Vec<Vec<&'a str>>,
1845            excludes: Vec<Vec<&'a str>>,
1846        }
1847
1848        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1849        let number = "AAAAAQB6Mcc=";
1850        let star = "*";
1851
1852        for tc in vec![
1853            // No filter means match nothing.
1854            TestCase {
1855                name: "<empty>",
1856                filter: vec![],
1857                includes: vec![],
1858                excludes: vec![vec![xfer]],
1859            },
1860            // "*" should match "transfer/" but not "transfer/transfer" or
1861            // "transfer/amount", because * is specified as a SINGLE segment
1862            // wildcard.
1863            TestCase {
1864                name: "*",
1865                filter: vec![star],
1866                includes: vec![vec![xfer]],
1867                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1868            },
1869            // "*/transfer" should match anything preceding "transfer", but
1870            // nothing that isn't exactly two segments long.
1871            TestCase {
1872                name: "*/transfer",
1873                filter: vec![star, xfer],
1874                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1875                excludes: vec![
1876                    vec![number],
1877                    vec![number, number],
1878                    vec![number, xfer, number],
1879                    vec![xfer],
1880                    vec![xfer, number],
1881                    vec![xfer, xfer, xfer],
1882                ],
1883            },
1884            // The inverse case of before: "transfer/*" should match any single
1885            // segment after a segment that is exactly "transfer", but no
1886            // additional segments.
1887            TestCase {
1888                name: "transfer/*",
1889                filter: vec![xfer, star],
1890                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1891                excludes: vec![
1892                    vec![number],
1893                    vec![number, number],
1894                    vec![number, xfer, number],
1895                    vec![xfer],
1896                    vec![number, xfer],
1897                    vec![xfer, xfer, xfer],
1898                ],
1899            },
1900            // Here, we extend to exactly two wild segments after transfer.
1901            TestCase {
1902                name: "transfer/*/*",
1903                filter: vec![xfer, star, star],
1904                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1905                excludes: vec![
1906                    vec![number],
1907                    vec![number, number],
1908                    vec![number, xfer],
1909                    vec![number, xfer, number, number],
1910                    vec![xfer],
1911                    vec![xfer, xfer, xfer, xfer],
1912                ],
1913            },
1914            // Here, we ensure wildcards can be in the middle of a filter: only
1915            // exact matches happen on the ends, while the middle can be
1916            // anything.
1917            TestCase {
1918                name: "transfer/*/number",
1919                filter: vec![xfer, star, number],
1920                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1921                excludes: vec![
1922                    vec![number],
1923                    vec![number, number],
1924                    vec![number, number, number],
1925                    vec![number, xfer, number],
1926                    vec![xfer],
1927                    vec![number, xfer],
1928                    vec![xfer, xfer, xfer],
1929                    vec![xfer, number, xfer],
1930                ],
1931            },
1932            // "**" as the sole filter element matches any topic (0+ segments).
1933            TestCase {
1934                name: "**",
1935                filter: vec!["**"],
1936                includes: vec![
1937                    vec![],
1938                    vec![xfer],
1939                    vec![xfer, number],
1940                    vec![xfer, number, number],
1941                ],
1942                excludes: vec![],
1943            },
1944            // "transfer/**" matches "transfer" followed by 0+ segments.
1945            TestCase {
1946                name: "transfer/**",
1947                filter: vec![xfer, "**"],
1948                includes: vec![
1949                    vec![xfer],
1950                    vec![xfer, number],
1951                    vec![xfer, number, number],
1952                    vec![xfer, xfer, xfer],
1953                ],
1954                excludes: vec![
1955                    vec![],
1956                    vec![number],
1957                    vec![number, xfer],
1958                    vec![number, number],
1959                ],
1960            },
1961            // "transfer/number/**" matches exactly "transfer/number" followed
1962            // by 0+ segments.
1963            TestCase {
1964                name: "transfer/number/**",
1965                filter: vec![xfer, number, "**"],
1966                includes: vec![
1967                    vec![xfer, number],
1968                    vec![xfer, number, number],
1969                    vec![xfer, number, xfer, number],
1970                ],
1971                excludes: vec![
1972                    vec![],
1973                    vec![xfer],
1974                    vec![number],
1975                    vec![number, xfer],
1976                    vec![xfer, xfer],
1977                ],
1978            },
1979        ] {
1980            for topic in tc.includes {
1981                assert!(
1982                    does_topic_match(
1983                        &topic
1984                            .iter()
1985                            .map(std::string::ToString::to_string)
1986                            .collect::<Vec<String>>(),
1987                        &tc.filter
1988                            .iter()
1989                            .map(std::string::ToString::to_string)
1990                            .collect::<Vec<String>>()
1991                    ),
1992                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
1993                    tc.name,
1994                    topic,
1995                    tc.filter
1996                );
1997            }
1998
1999            for topic in tc.excludes {
2000                assert!(
2001                    !does_topic_match(
2002                        // make deep copies of the vecs
2003                        &topic
2004                            .iter()
2005                            .map(std::string::ToString::to_string)
2006                            .collect::<Vec<String>>(),
2007                        &tc.filter
2008                            .iter()
2009                            .map(std::string::ToString::to_string)
2010                            .collect::<Vec<String>>()
2011                    ),
2012                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
2013                    tc.name,
2014                    topic,
2015                    tc.filter
2016                );
2017            }
2018        }
2019    }
2020}