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