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 = "envelopeXdr",
133        skip_serializing_if = "Option::is_none",
134        default
135    )]
136    pub envelope_xdr: Option<String>,
137
138    #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)]
139    pub result_xdr: Option<String>,
140
141    #[serde(
142        rename = "resultMetaXdr",
143        skip_serializing_if = "Option::is_none",
144        default
145    )]
146    pub result_meta_xdr: Option<String>,
147
148    #[serde(rename = "events", skip_serializing_if = "Option::is_none", default)]
149    pub events: Option<GetTransactionEventsRaw>,
150}
151
152#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Default)]
153pub struct GetTransactionEventsRaw {
154    #[serde(
155        rename = "contractEventsXdr",
156        skip_serializing_if = "Option::is_none",
157        default
158    )]
159    pub contract_events_xdr: Option<Vec<Vec<String>>>,
160
161    #[serde(
162        rename = "diagnosticEventsXdr",
163        skip_serializing_if = "Option::is_none",
164        default
165    )]
166    pub diagnostic_events_xdr: Option<Vec<String>>,
167
168    #[serde(
169        rename = "transactionEventsXdr",
170        skip_serializing_if = "Option::is_none",
171        default
172    )]
173    pub transaction_events_xdr: Option<Vec<String>>,
174}
175
176#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
177pub struct GetTransactionEvents {
178    pub contract_events: Vec<Vec<ContractEvent>>,
179    pub diagnostic_events: Vec<DiagnosticEvent>,
180    pub transaction_events: Vec<TransactionEvent>,
181}
182
183#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
184pub struct GetTransactionResponse {
185    pub status: String,
186    pub ledger: Option<u32>,
187    pub envelope: Option<xdr::TransactionEnvelope>,
188    pub result: Option<xdr::TransactionResult>,
189    pub result_meta: Option<xdr::TransactionMeta>,
190    pub events: GetTransactionEvents,
191}
192
193impl TryInto<GetTransactionResponse> for GetTransactionResponseRaw {
194    type Error = xdr::Error;
195
196    fn try_into(self) -> Result<GetTransactionResponse, Self::Error> {
197        let events = self.events.unwrap_or_default();
198        let result_meta: Option<xdr::TransactionMeta> = self
199            .result_meta_xdr
200            .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
201            .transpose()?;
202
203        let events = match result_meta {
204            Some(xdr::TransactionMeta::V4(_)) => GetTransactionEvents {
205                contract_events: events
206                    .contract_events_xdr
207                    .unwrap_or_default()
208                    .into_iter()
209                    .map(|es| {
210                        es.into_iter()
211                            .filter_map(|e| ContractEvent::from_xdr_base64(e, Limits::none()).ok())
212                            .collect::<Vec<_>>()
213                    })
214                    .collect::<Vec<Vec<ContractEvent>>>(),
215
216                diagnostic_events: events
217                    .diagnostic_events_xdr
218                    .unwrap_or_default()
219                    .iter()
220                    .filter_map(|e| DiagnosticEvent::from_xdr_base64(e, Limits::none()).ok())
221                    .collect(),
222
223                transaction_events: events
224                    .transaction_events_xdr
225                    .unwrap_or_default()
226                    .iter()
227                    .filter_map(|e| TransactionEvent::from_xdr_base64(e, Limits::none()).ok())
228                    .collect(),
229            },
230
231            Some(xdr::TransactionMeta::V3(TransactionMetaV3 {
232                soroban_meta: Some(ref meta),
233                ..
234            })) => GetTransactionEvents {
235                contract_events: vec![],
236                transaction_events: vec![],
237                diagnostic_events: meta.diagnostic_events.clone().into(),
238            },
239
240            _ => GetTransactionEvents {
241                contract_events: vec![],
242                transaction_events: vec![],
243                diagnostic_events: vec![],
244            },
245        };
246
247        Ok(GetTransactionResponse {
248            status: self.status,
249            ledger: self.ledger,
250            envelope: self
251                .envelope_xdr
252                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
253                .transpose()?,
254            result: self
255                .result_xdr
256                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
257                .transpose()?,
258            result_meta: result_meta,
259            events: events,
260        })
261    }
262}
263
264impl GetTransactionResponse {
265    ///
266    /// # Errors
267    pub fn return_value(&self) -> Result<xdr::ScVal, Error> {
268        if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
269            soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
270            ..
271        })) = &self.result_meta
272        {
273            return Ok(return_value.clone());
274        }
275
276        if let Some(xdr::TransactionMeta::V4(xdr::TransactionMetaV4 {
277            soroban_meta:
278                Some(xdr::SorobanTransactionMetaV2 {
279                    return_value: Some(return_value),
280                    ..
281                }),
282            ..
283        })) = &self.result_meta
284        {
285            return Ok(return_value.clone());
286        }
287
288        Err(Error::MissingOp)
289    }
290}
291
292#[serde_as]
293#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
294pub struct GetTransactionsResponseRaw {
295    pub transactions: Vec<GetTransactionResponseRaw>,
296    #[serde(rename = "latestLedger")]
297    pub latest_ledger: u32,
298    #[serde(rename = "latestLedgerCloseTimestamp")]
299    pub latest_ledger_close_time: i64,
300    #[serde(rename = "oldestLedger")]
301    pub oldest_ledger: u32,
302    #[serde(rename = "oldestLedgerCloseTimestamp")]
303    pub oldest_ledger_close_time: i64,
304    #[serde_as(as = "DisplayFromStr")]
305    pub cursor: u64,
306}
307#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
308pub struct GetTransactionsResponse {
309    pub transactions: Vec<GetTransactionResponse>,
310    pub latest_ledger: u32,
311    pub latest_ledger_close_time: i64,
312    pub oldest_ledger: u32,
313    pub oldest_ledger_close_time: i64,
314    pub cursor: u64,
315}
316impl TryInto<GetTransactionsResponse> for GetTransactionsResponseRaw {
317    type Error = xdr::Error; // assuming xdr::Error or any other error type that you use
318
319    fn try_into(self) -> Result<GetTransactionsResponse, Self::Error> {
320        Ok(GetTransactionsResponse {
321            transactions: self
322                .transactions
323                .into_iter()
324                .map(TryInto::try_into)
325                .collect::<Result<Vec<_>, xdr::Error>>()?,
326            latest_ledger: self.latest_ledger,
327            latest_ledger_close_time: self.latest_ledger_close_time,
328            oldest_ledger: self.oldest_ledger,
329            oldest_ledger_close_time: self.oldest_ledger_close_time,
330            cursor: self.cursor,
331        })
332    }
333}
334
335#[serde_as]
336#[derive(serde::Serialize, Debug, Clone)]
337pub struct TransactionsPaginationOptions {
338    #[serde_as(as = "Option<DisplayFromStr>")]
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub cursor: Option<u64>,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub limit: Option<u32>,
343}
344
345#[derive(serde::Serialize, Debug, Clone)]
346pub struct GetTransactionsRequest {
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub start_ledger: Option<u32>,
349    pub pagination: Option<TransactionsPaginationOptions>,
350}
351
352#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
353pub struct LedgerEntryResult {
354    pub key: String,
355    pub xdr: String,
356    #[serde(rename = "lastModifiedLedgerSeq")]
357    pub last_modified_ledger: u32,
358    #[serde(
359        rename = "liveUntilLedgerSeq",
360        skip_serializing_if = "Option::is_none",
361        deserialize_with = "deserialize_option_number_from_string",
362        default
363    )]
364    pub live_until_ledger_seq_ledger_seq: Option<u32>,
365}
366
367#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
368pub struct GetLedgerEntriesResponse {
369    pub entries: Option<Vec<LedgerEntryResult>>,
370    #[serde(rename = "latestLedger")]
371    pub latest_ledger: i64,
372}
373
374#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
375pub struct GetNetworkResponse {
376    #[serde(
377        rename = "friendbotUrl",
378        skip_serializing_if = "Option::is_none",
379        default
380    )]
381    pub friendbot_url: Option<String>,
382    pub passphrase: String,
383    #[serde(rename = "protocolVersion")]
384    pub protocol_version: u32,
385}
386
387#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
388pub struct GetHealthResponse {
389    pub status: String,
390    #[serde(rename = "latestLedger")]
391    pub latest_ledger: u32,
392    #[serde(rename = "oldestLedger")]
393    pub oldest_ledger: u32,
394    #[serde(rename = "ledgerRetentionWindow")]
395    pub ledger_retention_window: u32,
396}
397
398#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
399pub struct GetVersionInfoResponse {
400    pub version: String,
401    #[serde(rename = "commitHash")]
402    pub commmit_hash: String,
403    #[serde(rename = "buildTimestamp")]
404    pub build_timestamp: String,
405    #[serde(rename = "captiveCoreVersion")]
406    pub captive_core_version: String,
407    #[serde(rename = "protocolVersion")]
408    pub protocol_version: u32,
409}
410
411#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
412pub struct GetLatestLedgerResponse {
413    pub id: String,
414    #[serde(rename = "protocolVersion")]
415    pub protocol_version: u32,
416    pub sequence: u32,
417}
418
419#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
420pub struct GetFeeStatsResponse {
421    #[serde(rename = "sorobanInclusionFee")]
422    pub soroban_inclusion_fee: FeeStat,
423    #[serde(rename = "inclusionFee")]
424    pub inclusion_fee: FeeStat,
425    #[serde(
426        rename = "latestLedger",
427        deserialize_with = "deserialize_number_from_string"
428    )]
429    pub latest_ledger: u32,
430}
431
432#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
433pub struct FeeStat {
434    pub max: String,
435    pub min: String,
436    // Fee value which occurs the most often
437    pub mode: String,
438    // 10th nearest-rank fee percentile
439    pub p10: String,
440    // 20th nearest-rank fee percentile
441    pub p20: String,
442    // 30th nearest-rank fee percentile
443    pub p30: String,
444    // 40th nearest-rank fee percentile
445    pub p40: String,
446    // 50th nearest-rank fee percentile
447    pub p50: String,
448    // 60th nearest-rank fee percentile
449    pub p60: String,
450    // 70th nearest-rank fee percentile
451    pub p70: String,
452    // 80th nearest-rank fee percentile
453    pub p80: String,
454    // 90th nearest-rank fee percentile.
455    pub p90: String,
456    // 95th nearest-rank fee percentile.
457    pub p95: String,
458    // 99th nearest-rank fee percentile
459    pub p99: String,
460    // How many transactions are part of the distribution
461    #[serde(
462        rename = "transactionCount",
463        deserialize_with = "deserialize_number_from_string"
464    )]
465    pub transaction_count: u32,
466    // How many consecutive ledgers form the distribution
467    #[serde(
468        rename = "ledgerCount",
469        deserialize_with = "deserialize_number_from_string"
470    )]
471    pub ledger_count: u32,
472}
473
474#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
475pub struct Cost {
476    #[serde(
477        rename = "cpuInsns",
478        deserialize_with = "deserialize_number_from_string"
479    )]
480    pub cpu_insns: u64,
481    #[serde(
482        rename = "memBytes",
483        deserialize_with = "deserialize_number_from_string"
484    )]
485    pub mem_bytes: u64,
486}
487
488#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
489pub struct SimulateHostFunctionResultRaw {
490    #[serde(deserialize_with = "deserialize_default_from_null")]
491    pub auth: Vec<String>,
492    pub xdr: String,
493}
494
495#[derive(Debug, Clone)]
496pub struct SimulateHostFunctionResult {
497    pub auth: Vec<SorobanAuthorizationEntry>,
498    pub xdr: xdr::ScVal,
499}
500
501#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
502#[serde(tag = "type")]
503pub enum LedgerEntryChange {
504    #[serde(rename = "created")]
505    Created { key: String, after: String },
506    #[serde(rename = "deleted")]
507    Deleted { key: String, before: String },
508    #[serde(rename = "updated")]
509    Updated {
510        key: String,
511        before: String,
512        after: String,
513    },
514}
515
516#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
517pub struct SimulateTransactionResponse {
518    #[serde(
519        rename = "minResourceFee",
520        deserialize_with = "deserialize_number_from_string",
521        default
522    )]
523    pub min_resource_fee: u64,
524    #[serde(default)]
525    pub cost: Cost,
526    #[serde(skip_serializing_if = "Vec::is_empty", default)]
527    pub results: Vec<SimulateHostFunctionResultRaw>,
528    #[serde(rename = "transactionData", default)]
529    pub transaction_data: String,
530    #[serde(
531        deserialize_with = "deserialize_default_from_null",
532        skip_serializing_if = "Vec::is_empty",
533        default
534    )]
535    pub events: Vec<String>,
536    #[serde(
537        rename = "restorePreamble",
538        skip_serializing_if = "Option::is_none",
539        default
540    )]
541    pub restore_preamble: Option<RestorePreamble>,
542    #[serde(
543        rename = "stateChanges",
544        skip_serializing_if = "Option::is_none",
545        default
546    )]
547    pub state_changes: Option<Vec<LedgerEntryChange>>,
548    #[serde(rename = "latestLedger")]
549    pub latest_ledger: u32,
550    #[serde(skip_serializing_if = "Option::is_none", default)]
551    pub error: Option<String>,
552}
553
554impl SimulateTransactionResponse {
555    ///
556    /// # Errors
557    pub fn results(&self) -> Result<Vec<SimulateHostFunctionResult>, Error> {
558        self.results
559            .iter()
560            .map(|r| {
561                Ok(SimulateHostFunctionResult {
562                    auth: r
563                        .auth
564                        .iter()
565                        .map(|a| {
566                            Ok(SorobanAuthorizationEntry::from_xdr_base64(
567                                a,
568                                Limits::none(),
569                            )?)
570                        })
571                        .collect::<Result<_, Error>>()?,
572                    xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?,
573                })
574            })
575            .collect()
576    }
577
578    ///
579    /// # Errors
580    pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
581        self.events
582            .iter()
583            .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?))
584            .collect()
585    }
586
587    ///
588    /// # Errors
589    pub fn transaction_data(&self) -> Result<SorobanTransactionData, Error> {
590        Ok(SorobanTransactionData::from_xdr_base64(
591            &self.transaction_data,
592            Limits::none(),
593        )?)
594    }
595}
596
597#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
598pub struct RestorePreamble {
599    #[serde(rename = "transactionData")]
600    pub transaction_data: String,
601    #[serde(
602        rename = "minResourceFee",
603        deserialize_with = "deserialize_number_from_string"
604    )]
605    pub min_resource_fee: u64,
606}
607
608#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
609pub struct GetEventsResponse {
610    #[serde(deserialize_with = "deserialize_default_from_null")]
611    pub events: Vec<Event>,
612    #[serde(rename = "latestLedger")]
613    pub latest_ledger: u32,
614    #[serde(rename = "latestLedgerCloseTime")]
615    pub latest_ledger_close_time: String,
616    #[serde(rename = "oldestLedger")]
617    pub oldest_ledger: u32,
618    #[serde(rename = "oldestLedgerCloseTime")]
619    pub oldest_ledger_close_time: String,
620    pub cursor: String,
621}
622
623#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
624pub struct GetLedgersResponse {
625    #[serde(rename = "latestLedger")]
626    pub latest_ledger: u32,
627    #[serde(
628        rename = "latestLedgerCloseTime",
629        deserialize_with = "deserialize_number_from_string"
630    )]
631    pub latest_ledger_close_time: i64,
632    #[serde(rename = "oldestLedger")]
633    pub oldest_ledger: u32,
634    #[serde(rename = "oldestLedgerCloseTime")]
635    pub oldest_ledger_close_time: i64,
636    pub cursor: String,
637    pub ledgers: Vec<Ledger>,
638}
639
640#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
641pub struct Ledger {
642    pub hash: String,
643    pub sequence: u32,
644    #[serde(rename = "ledgerCloseTime")]
645    pub ledger_close_time: String,
646    #[serde(rename = "headerXdr")]
647    pub header_xdr: String,
648    #[serde(rename = "headerJson")]
649    pub header_json: Option<LedgerHeaderHistoryEntry>,
650    #[serde(rename = "metadataXdr")]
651    pub metadata_xdr: String,
652    #[serde(rename = "metadataJson")]
653    pub metadata_json: Option<LedgerCloseMeta>,
654}
655
656// Determines whether or not a particular filter matches a topic based on the
657// same semantics as the RPC server:
658//
659//  - for an exact segment match, the filter is a base64-encoded ScVal
660//  - for a wildcard, single-segment match, the string "*" matches exactly one
661//    segment
662//
663// The expectation is that a `filter` is a comma-separated list of segments that
664// has previously been validated, and `topic` is the list of segments applicable
665// for this event.
666//
667// [API
668// Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx)
669// [Code
670// Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203)
671#[must_use]
672pub fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
673    filter.len() == topic.len()
674        && filter
675            .iter()
676            .enumerate()
677            .all(|(i, s)| *s == "*" || topic[i] == *s)
678}
679
680#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
681pub struct Event {
682    #[serde(rename = "type")]
683    pub event_type: String,
684
685    pub ledger: u32,
686    #[serde(rename = "ledgerClosedAt")]
687    pub ledger_closed_at: String,
688
689    pub id: String,
690
691    #[serde(rename = "contractId")]
692    pub contract_id: String,
693    pub topic: Vec<String>,
694    pub value: String,
695}
696
697impl Display for Event {
698    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699        writeln!(
700            f,
701            "Event {} [{}]:",
702            self.id,
703            self.event_type.to_ascii_uppercase()
704        )?;
705        writeln!(
706            f,
707            "  Ledger:   {} (closed at {})",
708            self.ledger, self.ledger_closed_at
709        )?;
710        writeln!(f, "  Contract: {}", self.contract_id)?;
711        writeln!(f, "  Topics:")?;
712
713        for topic in &self.topic {
714            let scval =
715                xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?;
716            writeln!(f, "            {scval:?}")?;
717        }
718
719        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())
720            .map_err(|_| std::fmt::Error)?;
721
722        writeln!(f, "  Value:    {scval:?}")
723    }
724}
725
726impl Event {
727    ///
728    /// # Errors
729    pub fn parse_cursor(&self) -> Result<(u64, i32), Error> {
730        parse_cursor(&self.id)
731    }
732
733    ///
734    /// # Errors
735    pub fn pretty_print(&self) -> Result<(), Box<dyn std::error::Error>> {
736        let mut stdout = StandardStream::stdout(ColorChoice::Auto);
737
738        if !stdout.supports_color() {
739            println!("{self}");
740            return Ok(());
741        }
742
743        let color = match self.event_type.as_str() {
744            "system" => Color::Yellow,
745            _ => Color::Blue,
746        };
747        colored!(
748            stdout,
749            "{}Event{} {}{}{} [{}{}{}{}]:\n",
750            bold!(true),
751            bold!(false),
752            fg!(Some(Color::Green)),
753            self.id,
754            reset!(),
755            bold!(true),
756            fg!(Some(color)),
757            self.event_type.to_ascii_uppercase(),
758            reset!(),
759        )?;
760
761        colored!(
762            stdout,
763            "  Ledger:   {}{}{} (closed at {}{}{})\n",
764            fg!(Some(Color::Green)),
765            self.ledger,
766            reset!(),
767            fg!(Some(Color::Green)),
768            self.ledger_closed_at,
769            reset!(),
770        )?;
771
772        colored!(
773            stdout,
774            "  Contract: {}{}{}\n",
775            fg!(Some(Color::Green)),
776            self.contract_id,
777            reset!(),
778        )?;
779
780        colored!(stdout, "  Topics:\n")?;
781        for topic in &self.topic {
782            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?;
783            colored!(
784                stdout,
785                "            {}{:?}{}\n",
786                fg!(Some(Color::Green)),
787                scval,
788                reset!(),
789            )?;
790        }
791
792        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?;
793        colored!(
794            stdout,
795            "  Value: {}{:?}{}\n\n",
796            fg!(Some(Color::Green)),
797            scval,
798            reset!(),
799        )?;
800
801        Ok(())
802    }
803}
804
805/// Defines non-root authorization for simulated transactions.
806pub enum AuthMode {
807    Enforce,
808    Record,
809    RecordAllowNonRoot,
810}
811
812#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
813pub enum EventType {
814    All,
815    Contract,
816    System,
817}
818
819#[derive(Clone, Debug, Eq, Hash, PartialEq)]
820pub enum LedgerStart {
821    Ledger(u32),
822    Cursor(String),
823}
824
825#[derive(Clone, Debug, Eq, Hash, PartialEq)]
826pub enum EventStart {
827    Ledger(u32),
828    Cursor(String),
829}
830
831#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
832pub struct FullLedgerEntry {
833    pub key: LedgerKey,
834    pub val: LedgerEntryData,
835    #[serde(rename = "lastModifiedLedgerSeq")]
836    pub last_modified_ledger: u32,
837    #[serde(
838        rename = "liveUntilLedgerSeq",
839        skip_serializing_if = "Option::is_none",
840        deserialize_with = "deserialize_option_number_from_string",
841        default
842    )]
843    pub live_until_ledger_seq: Option<u32>,
844}
845
846#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
847pub struct FullLedgerEntries {
848    pub entries: Vec<FullLedgerEntry>,
849    #[serde(rename = "latestLedger")]
850    pub latest_ledger: i64,
851}
852
853#[derive(Debug, Clone)]
854pub struct Client {
855    base_url: Arc<str>,
856    timeout_in_secs: u64,
857    http_client: Arc<HttpClient>,
858}
859
860#[allow(deprecated)] // Can be removed once Client doesn't have any code marked deprecated inside
861impl Client {
862    ///
863    /// # Errors
864    pub fn new(base_url: &str) -> Result<Self, Error> {
865        // Add the port to the base URL if there is no port explicitly included
866        // in the URL and the scheme allows us to infer a default port.
867        // Jsonrpsee requires a port to always be present even if one can be
868        // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048.
869        let uri = base_url.parse::<Uri>().map_err(Error::InvalidRpcUrl)?;
870        let mut parts = uri.into_parts();
871
872        if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) {
873            if authority.port().is_none() {
874                let port = match scheme.as_str() {
875                    "http" => Some(80),
876                    "https" => Some(443),
877                    _ => None,
878                };
879                if let Some(port) = port {
880                    let host = authority.host();
881                    parts.authority = Some(
882                        Authority::from_str(&format!("{host}:{port}"))
883                            .map_err(Error::InvalidRpcUrl)?,
884                    );
885                }
886            }
887        }
888
889        let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?;
890        let base_url = Arc::from(uri.to_string());
891        let headers = Self::default_http_headers();
892        let http_client = Arc::new(
893            HttpClientBuilder::default()
894                .set_headers(headers)
895                .build(&base_url)?,
896        );
897
898        Ok(Self {
899            base_url,
900            timeout_in_secs: 30,
901            http_client,
902        })
903    }
904
905    /// Create a new client with a timeout in seconds
906    /// # Errors
907    #[deprecated(
908        note = "To be marked private in a future major release. Please use `new_with_headers` instead."
909    )]
910    pub fn new_with_timeout(base_url: &str, timeout: u64) -> Result<Self, Error> {
911        let mut client = Self::new(base_url)?;
912        client.timeout_in_secs = timeout;
913        Ok(client)
914    }
915
916    /// Create a new client with additional headers
917    /// # Errors
918    pub fn new_with_headers(base_url: &str, additional_headers: HeaderMap) -> Result<Self, Error> {
919        let mut client = Self::new(base_url)?;
920        let mut headers = Self::default_http_headers();
921
922        for (key, value) in additional_headers {
923            headers.insert(key.ok_or(Error::InvalidResponse)?, value);
924        }
925
926        let http_client = Arc::new(
927            HttpClientBuilder::default()
928                .set_headers(headers)
929                .build(base_url)?,
930        );
931
932        client.http_client = http_client;
933        Ok(client)
934    }
935
936    fn default_http_headers() -> HeaderMap {
937        let mut headers = HeaderMap::new();
938        headers.insert("X-Client-Name", unsafe {
939            "rs-stellar-rpc-client".parse().unwrap_unchecked()
940        });
941        let version = VERSION.unwrap_or("devel");
942        headers.insert("X-Client-Version", unsafe {
943            version.parse().unwrap_unchecked()
944        });
945
946        headers
947    }
948
949    #[must_use]
950    pub fn base_url(&self) -> &str {
951        &self.base_url
952    }
953
954    #[must_use]
955    pub fn client(&self) -> &HttpClient {
956        &self.http_client
957    }
958
959    ///
960    /// # Errors
961    pub async fn friendbot_url(&self) -> Result<String, Error> {
962        let network = self.get_network().await?;
963        network.friendbot_url.ok_or_else(|| {
964            Error::NotFound(
965                "Friendbot".to_string(),
966                "Friendbot is not available on this network".to_string(),
967            )
968        })
969    }
970    ///
971    /// # Errors
972    pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
973        let server = self.get_network().await?.passphrase;
974
975        if let Some(expected) = expected {
976            if expected != server {
977                return Err(Error::InvalidNetworkPassphrase {
978                    expected: expected.to_string(),
979                    server,
980                });
981            }
982        }
983
984        Ok(server)
985    }
986
987    ///
988    /// # Errors
989    pub async fn get_network(&self) -> Result<GetNetworkResponse, Error> {
990        Ok(self
991            .client()
992            .request("getNetwork", ObjectParams::new())
993            .await?)
994    }
995
996    ///
997    /// # Errors
998    pub async fn get_health(&self) -> Result<GetHealthResponse, Error> {
999        Ok(self
1000            .client()
1001            .request("getHealth", ObjectParams::new())
1002            .await?)
1003    }
1004
1005    ///
1006    /// # Errors
1007    pub async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse, Error> {
1008        Ok(self
1009            .client()
1010            .request("getLatestLedger", ObjectParams::new())
1011            .await?)
1012    }
1013
1014    ///
1015    /// # Errors
1016    pub async fn get_ledgers(
1017        &self,
1018        start: LedgerStart,
1019        limit: Option<usize>,
1020        format: Option<String>,
1021    ) -> Result<GetLedgersResponse, Error> {
1022        let mut oparams = ObjectParams::new();
1023
1024        let mut pagination = serde_json::Map::new();
1025        if let Some(limit) = limit {
1026            pagination.insert("limit".to_string(), limit.into());
1027        }
1028
1029        match start {
1030            LedgerStart::Ledger(l) => oparams.insert("startLedger", l)?,
1031            LedgerStart::Cursor(c) => {
1032                pagination.insert("cursor".to_string(), c.into());
1033            }
1034        };
1035
1036        oparams.insert("pagination", pagination)?;
1037
1038        if let Some(f) = format {
1039            oparams.insert("xdrFormat", f)?;
1040        }
1041
1042        Ok(self.client().request("getLedgers", oparams).await?)
1043    }
1044
1045    ///
1046    /// # Errors
1047    pub async fn get_account(&self, address: &str) -> Result<AccountEntry, Error> {
1048        let key = LedgerKey::Account(LedgerKeyAccount {
1049            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
1050                stellar_strkey::ed25519::PublicKey::from_string(address)?.0,
1051            ))),
1052        });
1053        let keys = Vec::from([key]);
1054        let response = self.get_ledger_entries(&keys).await?;
1055        let entries = response.entries.unwrap_or_default();
1056
1057        if entries.is_empty() {
1058            return Err(Error::NotFound("Account".to_string(), address.to_owned()));
1059        }
1060
1061        let ledger_entry = &entries[0];
1062        let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none());
1063
1064        if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? {
1065            Ok(entry)
1066        } else {
1067            Err(Error::InvalidResponse)
1068        }
1069    }
1070
1071    /// Get network fee stats
1072    /// # Errors
1073    pub async fn get_fee_stats(&self) -> Result<GetFeeStatsResponse, Error> {
1074        Ok(self
1075            .client()
1076            .request("getFeeStats", ObjectParams::new())
1077            .await?)
1078    }
1079
1080    ///
1081    /// # Errors
1082    pub async fn get_version_info(&self) -> Result<GetVersionInfoResponse, Error> {
1083        Ok(self
1084            .client()
1085            .request("getVersionInfo", ObjectParams::new())
1086            .await?)
1087    }
1088
1089    /// Send a transaction to the network and get back the hash of the transaction.
1090    /// # Errors
1091    pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<Hash, Error> {
1092        let mut oparams = ObjectParams::new();
1093        oparams.insert("transaction", tx.to_xdr_base64(Limits::none())?)?;
1094        let SendTransactionResponse {
1095            hash,
1096            error_result_xdr,
1097            status,
1098            ..
1099        } = self
1100            .client()
1101            .request("sendTransaction", oparams)
1102            .await
1103            .map_err(|err| {
1104                Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}"))
1105            })?;
1106
1107        if status == "ERROR" {
1108            let error = error_result_xdr
1109                .ok_or(Error::MissingError)
1110                .and_then(|x| {
1111                    TransactionResult::read_xdr_base64(&mut Limited::new(
1112                        x.as_bytes(),
1113                        Limits::none(),
1114                    ))
1115                    .map_err(|_| Error::InvalidResponse)
1116                })
1117                .map(|r| r.result)?;
1118
1119            return Err(Error::TransactionSubmissionFailed(format!("{error:#?}")));
1120        }
1121
1122        Ok(Hash::from_str(&hash)?)
1123    }
1124
1125    ///
1126    /// # Errors
1127    pub async fn send_transaction_polling(
1128        &self,
1129        tx: &TransactionEnvelope,
1130    ) -> Result<GetTransactionResponse, Error> {
1131        let hash = self.send_transaction(tx).await?;
1132        self.get_transaction_polling(&hash, None).await
1133    }
1134
1135    ///
1136    /// # Errors
1137    pub async fn simulate_transaction_envelope(
1138        &self,
1139        tx: &TransactionEnvelope,
1140        auth_mode: Option<AuthMode>,
1141    ) -> Result<SimulateTransactionResponse, Error> {
1142        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1143        let mut params = ObjectParams::new();
1144
1145        params.insert("transaction", base64_tx)?;
1146
1147        match auth_mode {
1148            Some(AuthMode::Enforce) => {
1149                params.insert("authMode", "enforce")?;
1150            }
1151            Some(AuthMode::Record) => {
1152                params.insert("authMode", "record")?;
1153            }
1154            Some(AuthMode::RecordAllowNonRoot) => {
1155                params.insert("authMode", "record_allow_nonroot")?;
1156            }
1157            None => {}
1158        }
1159
1160        let sim_res = self.client().request("simulateTransaction", params).await?;
1161
1162        Ok(sim_res)
1163    }
1164
1165    ///
1166    /// # Errors
1167    pub async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse, Error> {
1168        let mut oparams = ObjectParams::new();
1169        oparams.insert("hash", tx_id)?;
1170        let resp: GetTransactionResponseRaw =
1171            self.client().request("getTransaction", oparams).await?;
1172
1173        Ok(resp.try_into()?)
1174    }
1175
1176    ///
1177    /// # Errors
1178    pub async fn get_transactions(
1179        &self,
1180        request: GetTransactionsRequest,
1181    ) -> Result<GetTransactionsResponse, Error> {
1182        let mut oparams = ObjectParams::new();
1183
1184        if let Some(start_ledger) = request.start_ledger {
1185            oparams.insert("startLedger", start_ledger)?;
1186        }
1187
1188        if let Some(pagination_params) = request.pagination {
1189            let pagination = serde_json::json!(pagination_params);
1190            oparams.insert("pagination", pagination)?;
1191        }
1192
1193        let resp: GetTransactionsResponseRaw =
1194            self.client().request("getTransactions", oparams).await?;
1195
1196        Ok(resp.try_into()?)
1197    }
1198
1199    /// Poll the transaction status. Can provide a timeout in seconds, otherwise uses the default timeout.
1200    ///
1201    /// It uses exponential backoff with a base of 1 second and a maximum of 30 seconds.
1202    ///
1203    /// # Errors
1204    /// - `Error::TransactionSubmissionTimeout` if the transaction status is not found within the timeout
1205    /// - `Error::TransactionSubmissionFailed` if the transaction status is "FAILED"
1206    /// - `Error::UnexpectedTransactionStatus` if the transaction status is not one of "SUCCESS", "FAILED", or ``NOT_FOUND``
1207    /// - `json_rpsee` Errors
1208    pub async fn get_transaction_polling(
1209        &self,
1210        tx_id: &Hash,
1211        timeout_s: Option<Duration>,
1212    ) -> Result<GetTransactionResponse, Error> {
1213        // Poll the transaction status
1214        let start = Instant::now();
1215        let timeout = timeout_s.unwrap_or(Duration::from_secs(self.timeout_in_secs));
1216        // see https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=50731
1217        // Is optimimal exponent for expontial backoff
1218        let exponential_backoff: f64 = 1.0 / (1.0 - E.powf(-1.0));
1219        let mut sleep_time = Duration::from_secs(1);
1220        loop {
1221            let response = self.get_transaction(tx_id).await?;
1222            match response.status.as_str() {
1223                "SUCCESS" => return Ok(response),
1224
1225                "FAILED" => {
1226                    return Err(Error::TransactionSubmissionFailed(format!(
1227                        "{:#?}",
1228                        response.result
1229                    )))
1230                }
1231
1232                "NOT_FOUND" => (),
1233                _ => {
1234                    return Err(Error::UnexpectedTransactionStatus(response.status));
1235                }
1236            }
1237
1238            if start.elapsed() > timeout {
1239                return Err(Error::TransactionSubmissionTimeout);
1240            }
1241
1242            sleep(sleep_time).await;
1243            sleep_time = Duration::from_secs_f64(sleep_time.as_secs_f64() * exponential_backoff);
1244        }
1245    }
1246
1247    ///
1248    /// # Errors
1249    pub async fn get_ledger_entries(
1250        &self,
1251        keys: &[LedgerKey],
1252    ) -> Result<GetLedgerEntriesResponse, Error> {
1253        let mut base64_keys: Vec<String> = vec![];
1254
1255        for k in keys {
1256            let base64_result = k.to_xdr_base64(Limits::none());
1257            if base64_result.is_err() {
1258                return Err(Error::Xdr(XdrError::Invalid));
1259            }
1260            base64_keys.push(k.to_xdr_base64(Limits::none())?);
1261        }
1262
1263        let mut oparams = ObjectParams::new();
1264        oparams.insert("keys", base64_keys)?;
1265
1266        Ok(self.client().request("getLedgerEntries", oparams).await?)
1267    }
1268
1269    ///
1270    /// # Errors
1271    pub async fn get_full_ledger_entries(
1272        &self,
1273        ledger_keys: &[LedgerKey],
1274    ) -> Result<FullLedgerEntries, Error> {
1275        let keys = ledger_keys
1276            .iter()
1277            .filter(|key| !matches!(key, LedgerKey::Ttl(_)))
1278            .map(Clone::clone)
1279            .collect::<Vec<_>>();
1280        let GetLedgerEntriesResponse {
1281            entries,
1282            latest_ledger,
1283        } = self.get_ledger_entries(&keys).await?;
1284        let entries = entries
1285            .unwrap_or_default()
1286            .iter()
1287            .map(
1288                |LedgerEntryResult {
1289                     key,
1290                     xdr,
1291                     last_modified_ledger,
1292                     live_until_ledger_seq_ledger_seq,
1293                 }| {
1294                    Ok(FullLedgerEntry {
1295                        key: LedgerKey::from_xdr_base64(key, Limits::none())?,
1296                        val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?,
1297                        live_until_ledger_seq: *live_until_ledger_seq_ledger_seq,
1298                        last_modified_ledger: *last_modified_ledger,
1299                    })
1300                },
1301            )
1302            .collect::<Result<Vec<_>, Error>>()?;
1303        Ok(FullLedgerEntries {
1304            entries,
1305            latest_ledger,
1306        })
1307    }
1308    ///
1309    /// # Errors
1310    pub async fn get_events(
1311        &self,
1312        start: EventStart,
1313        event_type: Option<EventType>,
1314        contract_ids: &[String],
1315        topics: &[String],
1316        limit: Option<usize>,
1317    ) -> Result<GetEventsResponse, Error> {
1318        let mut filters = serde_json::Map::new();
1319
1320        event_type
1321            .and_then(|t| match t {
1322                EventType::All => None, // all is the default, so avoid incl. the param
1323                EventType::Contract => Some("contract"),
1324                EventType::System => Some("system"),
1325            })
1326            .map(|t| filters.insert("type".to_string(), t.into()));
1327
1328        filters.insert("topics".to_string(), topics.into());
1329        filters.insert("contractIds".to_string(), contract_ids.into());
1330
1331        let mut pagination = serde_json::Map::new();
1332        if let Some(limit) = limit {
1333            pagination.insert("limit".to_string(), limit.into());
1334        }
1335
1336        let mut oparams = ObjectParams::new();
1337        match start {
1338            EventStart::Ledger(l) => oparams.insert("startLedger", l)?,
1339            EventStart::Cursor(c) => {
1340                pagination.insert("cursor".to_string(), c.into());
1341            }
1342        }
1343        oparams.insert("filters", vec![filters])?;
1344        oparams.insert("pagination", pagination)?;
1345
1346        Ok(self.client().request("getEvents", oparams).await?)
1347    }
1348
1349    ///
1350    /// # Errors
1351    pub async fn get_contract_data(
1352        &self,
1353        contract_id: &[u8; 32],
1354    ) -> Result<ContractDataEntry, Error> {
1355        // Get the contract from the network
1356        let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData {
1357            contract: xdr::ScAddress::Contract(ContractId(xdr::Hash(*contract_id))),
1358            key: xdr::ScVal::LedgerKeyContractInstance,
1359            durability: xdr::ContractDataDurability::Persistent,
1360        });
1361        let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
1362        let entries = contract_ref.entries.unwrap_or_default();
1363        if entries.is_empty() {
1364            let contract_address = stellar_strkey::Contract(*contract_id).to_string();
1365            return Err(Error::NotFound("Contract".to_string(), contract_address));
1366        }
1367        let contract_ref_entry = &entries[0];
1368        match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1369            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1370            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1371        }
1372    }
1373
1374    ///
1375    /// # Errors
1376    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1377    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1378        match self.get_contract_data(contract_id).await? {
1379            xdr::ContractDataEntry {
1380                val:
1381                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1382                        executable: xdr::ContractExecutable::Wasm(hash),
1383                        ..
1384                    }),
1385                ..
1386            } => self.get_remote_wasm_from_hash(hash).await,
1387            scval => Err(Error::UnexpectedToken(scval)),
1388        }
1389    }
1390
1391    ///
1392    /// # Errors
1393    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1394    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1395        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1396        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1397        let entries = contract_data.entries.unwrap_or_default();
1398        if entries.is_empty() {
1399            return Err(Error::NotFound(
1400                "Contract Code".to_string(),
1401                hex::encode(hash),
1402            ));
1403        }
1404        let contract_data_entry = &entries[0];
1405        match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1406            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1407            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1408        }
1409    }
1410
1411    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1412    ///
1413    /// # Errors
1414    /// - Could fail to find contract or have a network error
1415    pub async fn get_contract_instance(
1416        &self,
1417        contract_id: &[u8; 32],
1418    ) -> Result<ScContractInstance, Error> {
1419        let contract_data = self.get_contract_data(contract_id).await?;
1420        match contract_data.val {
1421            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1422            scval => Err(Error::UnexpectedContractInstance(scval)),
1423        }
1424    }
1425}
1426
1427pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1428    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1429    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1430    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1431    Ok((toid_part, start_index))
1432}
1433
1434#[cfg(test)]
1435mod tests {
1436    use super::*;
1437    use std::env;
1438    use std::fs;
1439    use std::path::PathBuf;
1440
1441    fn get_repo_root() -> PathBuf {
1442        let mut path = env::current_exe().expect("Failed to get current executable path");
1443        // Navigate up the directory tree until we find the repository root
1444        while path.pop() {
1445            if path.join("Cargo.toml").exists() {
1446                return path;
1447            }
1448        }
1449        panic!("Could not find repository root");
1450    }
1451
1452    fn read_json_file(name: &str) -> String {
1453        let repo_root = get_repo_root();
1454        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1455        fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1456    }
1457
1458    #[test]
1459    fn simulation_transaction_response_parsing() {
1460        let s = r#"{
1461 "minResourceFee": "100000000",
1462 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1463 "transactionData": "",
1464 "latestLedger": 1234,
1465 "stateChanges": [{
1466    "type": "created",
1467    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1468    "before": null,
1469    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1470  }]
1471  }"#;
1472
1473        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1474        assert_eq!(
1475            resp.state_changes.unwrap()[0],
1476            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1477        );
1478        assert_eq!(resp.min_resource_fee, 100_000_000);
1479    }
1480
1481    #[test]
1482    fn simulation_transaction_response_parsing_mostly_empty() {
1483        let s = r#"{
1484 "latestLedger": 1234
1485        }"#;
1486
1487        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1488        assert_eq!(resp.latest_ledger, 1_234);
1489    }
1490
1491    #[test]
1492    fn test_parse_transaction_response_p23() {
1493        let response_content = read_json_file("transaction_response_p23.json");
1494        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1495            .expect("Failed to parse JSON from transaction_response_p23.json");
1496        let result = full_response["result"].clone();
1497        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1498            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1499        let response: GetTransactionResponse = raw_response
1500            .try_into()
1501            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1502
1503        assert_eq!(2, response.events.transaction_events.iter().len());
1504        assert_eq!(1, response.events.contract_events.len());
1505        assert_eq!(21, response.events.diagnostic_events.iter().len());
1506    }
1507
1508    #[test]
1509    fn test_parse_transaction_response_p22() {
1510        let response_content = read_json_file("transaction_response_p22.json");
1511        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1512            .expect("Failed to parse JSON from transaction_response_p22.json");
1513        let result = full_response["result"].clone();
1514        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1515            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1516        let response: GetTransactionResponse = raw_response
1517            .try_into()
1518            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1519
1520        assert_eq!(23, response.events.diagnostic_events.iter().len());
1521    }
1522
1523    #[test]
1524    fn test_parse_get_transactions_response() {
1525        let response_content = read_json_file("transactions_response.json");
1526
1527        // Parse the entire response
1528        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1529            .expect("Failed to parse JSON from transactions_response.json");
1530
1531        // Extract the "result" field
1532        let result = full_response["result"].clone();
1533        // Parse the "result" content as GetTransactionsResponseRaw
1534        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1535            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1536
1537        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1538        let response: GetTransactionsResponse = raw_response
1539            .try_into()
1540            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1541
1542        // Assertions
1543        assert_eq!(response.transactions.len(), 5);
1544        assert_eq!(response.latest_ledger, 556_962);
1545        assert_eq!(response.cursor, 2_379_420_471_922_689);
1546
1547        // Additional assertions for specific transaction attributes
1548        assert_eq!(response.transactions[0].status, "SUCCESS");
1549        //assert_eq!(response.transactions[0].application_order, 1);
1550        //assert_eq!(response.transactions[0].ledger, 554000);
1551    }
1552
1553    #[test]
1554    fn test_rpc_url_default_ports() {
1555        // Default ports are added.
1556        let client = Client::new("http://example.com").unwrap();
1557        assert_eq!(client.base_url(), "http://example.com:80/");
1558        let client = Client::new("https://example.com").unwrap();
1559        assert_eq!(client.base_url(), "https://example.com:443/");
1560
1561        // Ports are not added when already present.
1562        let client = Client::new("http://example.com:8080").unwrap();
1563        assert_eq!(client.base_url(), "http://example.com:8080/");
1564        let client = Client::new("https://example.com:8080").unwrap();
1565        assert_eq!(client.base_url(), "https://example.com:8080/");
1566
1567        // Paths are not modified.
1568        let client = Client::new("http://example.com/a/b/c").unwrap();
1569        assert_eq!(client.base_url(), "http://example.com:80/a/b/c");
1570        let client = Client::new("https://example.com/a/b/c").unwrap();
1571        assert_eq!(client.base_url(), "https://example.com:443/a/b/c");
1572        let client = Client::new("http://example.com/a/b/c/").unwrap();
1573        assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1574        let client = Client::new("https://example.com/a/b/c/").unwrap();
1575        assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1576        let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1577        assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1578        let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1579        assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1580    }
1581
1582    #[test]
1583    // Taken from [RPC server
1584    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1585    fn test_does_topic_match() {
1586        struct TestCase<'a> {
1587            name: &'a str,
1588            filter: Vec<&'a str>,
1589            includes: Vec<Vec<&'a str>>,
1590            excludes: Vec<Vec<&'a str>>,
1591        }
1592
1593        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1594        let number = "AAAAAQB6Mcc=";
1595        let star = "*";
1596
1597        for tc in vec![
1598            // No filter means match nothing.
1599            TestCase {
1600                name: "<empty>",
1601                filter: vec![],
1602                includes: vec![],
1603                excludes: vec![vec![xfer]],
1604            },
1605            // "*" should match "transfer/" but not "transfer/transfer" or
1606            // "transfer/amount", because * is specified as a SINGLE segment
1607            // wildcard.
1608            TestCase {
1609                name: "*",
1610                filter: vec![star],
1611                includes: vec![vec![xfer]],
1612                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1613            },
1614            // "*/transfer" should match anything preceding "transfer", but
1615            // nothing that isn't exactly two segments long.
1616            TestCase {
1617                name: "*/transfer",
1618                filter: vec![star, xfer],
1619                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1620                excludes: vec![
1621                    vec![number],
1622                    vec![number, number],
1623                    vec![number, xfer, number],
1624                    vec![xfer],
1625                    vec![xfer, number],
1626                    vec![xfer, xfer, xfer],
1627                ],
1628            },
1629            // The inverse case of before: "transfer/*" should match any single
1630            // segment after a segment that is exactly "transfer", but no
1631            // additional segments.
1632            TestCase {
1633                name: "transfer/*",
1634                filter: vec![xfer, star],
1635                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1636                excludes: vec![
1637                    vec![number],
1638                    vec![number, number],
1639                    vec![number, xfer, number],
1640                    vec![xfer],
1641                    vec![number, xfer],
1642                    vec![xfer, xfer, xfer],
1643                ],
1644            },
1645            // Here, we extend to exactly two wild segments after transfer.
1646            TestCase {
1647                name: "transfer/*/*",
1648                filter: vec![xfer, star, star],
1649                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1650                excludes: vec![
1651                    vec![number],
1652                    vec![number, number],
1653                    vec![number, xfer],
1654                    vec![number, xfer, number, number],
1655                    vec![xfer],
1656                    vec![xfer, xfer, xfer, xfer],
1657                ],
1658            },
1659            // Here, we ensure wildcards can be in the middle of a filter: only
1660            // exact matches happen on the ends, while the middle can be
1661            // anything.
1662            TestCase {
1663                name: "transfer/*/number",
1664                filter: vec![xfer, star, number],
1665                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1666                excludes: vec![
1667                    vec![number],
1668                    vec![number, number],
1669                    vec![number, number, number],
1670                    vec![number, xfer, number],
1671                    vec![xfer],
1672                    vec![number, xfer],
1673                    vec![xfer, xfer, xfer],
1674                    vec![xfer, number, xfer],
1675                ],
1676            },
1677        ] {
1678            for topic in tc.includes {
1679                assert!(
1680                    does_topic_match(
1681                        &topic
1682                            .iter()
1683                            .map(std::string::ToString::to_string)
1684                            .collect::<Vec<String>>(),
1685                        &tc.filter
1686                            .iter()
1687                            .map(std::string::ToString::to_string)
1688                            .collect::<Vec<String>>()
1689                    ),
1690                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
1691                    tc.name,
1692                    topic,
1693                    tc.filter
1694                );
1695            }
1696
1697            for topic in tc.excludes {
1698                assert!(
1699                    !does_topic_match(
1700                        // make deep copies of the vecs
1701                        &topic
1702                            .iter()
1703                            .map(std::string::ToString::to_string)
1704                            .collect::<Vec<String>>(),
1705                        &tc.filter
1706                            .iter()
1707                            .map(std::string::ToString::to_string)
1708                            .collect::<Vec<String>>()
1709                    ),
1710                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
1711                    tc.name,
1712                    topic,
1713                    tc.filter
1714                );
1715            }
1716        }
1717    }
1718}