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
728pub type SegmentFilter = String;
729pub type TopicFilter = Vec<SegmentFilter>;
730
731impl Event {
732    ///
733    /// # Errors
734    pub fn parse_cursor(&self) -> Result<(u64, i32), Error> {
735        parse_cursor(&self.id)
736    }
737
738    ///
739    /// # Errors
740    pub fn pretty_print(&self) -> Result<(), Box<dyn std::error::Error>> {
741        let mut stdout = StandardStream::stdout(ColorChoice::Auto);
742
743        if !stdout.supports_color() {
744            println!("{self}");
745            return Ok(());
746        }
747
748        let color = match self.event_type.as_str() {
749            "system" => Color::Yellow,
750            _ => Color::Blue,
751        };
752        colored!(
753            stdout,
754            "{}Event{} {}{}{} [{}{}{}{}]:\n",
755            bold!(true),
756            bold!(false),
757            fg!(Some(Color::Green)),
758            self.id,
759            reset!(),
760            bold!(true),
761            fg!(Some(color)),
762            self.event_type.to_ascii_uppercase(),
763            reset!(),
764        )?;
765
766        colored!(
767            stdout,
768            "  Ledger:   {}{}{} (closed at {}{}{})\n",
769            fg!(Some(Color::Green)),
770            self.ledger,
771            reset!(),
772            fg!(Some(Color::Green)),
773            self.ledger_closed_at,
774            reset!(),
775        )?;
776
777        colored!(
778            stdout,
779            "  Contract: {}{}{}\n",
780            fg!(Some(Color::Green)),
781            self.contract_id,
782            reset!(),
783        )?;
784
785        colored!(stdout, "  Topics:\n")?;
786        for topic in &self.topic {
787            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?;
788            colored!(
789                stdout,
790                "            {}{:?}{}\n",
791                fg!(Some(Color::Green)),
792                scval,
793                reset!(),
794            )?;
795        }
796
797        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?;
798        colored!(
799            stdout,
800            "  Value: {}{:?}{}\n\n",
801            fg!(Some(Color::Green)),
802            scval,
803            reset!(),
804        )?;
805
806        Ok(())
807    }
808}
809
810/// Defines non-root authorization for simulated transactions.
811pub enum AuthMode {
812    Enforce,
813    Record,
814    RecordAllowNonRoot,
815}
816
817#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
818pub enum EventType {
819    All,
820    Contract,
821    System,
822}
823
824#[derive(Clone, Debug, Eq, Hash, PartialEq)]
825pub enum LedgerStart {
826    Ledger(u32),
827    Cursor(String),
828}
829
830#[derive(Clone, Debug, Eq, Hash, PartialEq)]
831pub enum EventStart {
832    Ledger(u32),
833    Cursor(String),
834}
835
836#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
837pub struct FullLedgerEntry {
838    pub key: LedgerKey,
839    pub val: LedgerEntryData,
840    #[serde(rename = "lastModifiedLedgerSeq")]
841    pub last_modified_ledger: u32,
842    #[serde(
843        rename = "liveUntilLedgerSeq",
844        skip_serializing_if = "Option::is_none",
845        deserialize_with = "deserialize_option_number_from_string",
846        default
847    )]
848    pub live_until_ledger_seq: Option<u32>,
849}
850
851#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
852pub struct FullLedgerEntries {
853    pub entries: Vec<FullLedgerEntry>,
854    #[serde(rename = "latestLedger")]
855    pub latest_ledger: i64,
856}
857
858#[derive(Debug, Clone)]
859pub struct Client {
860    base_url: Arc<str>,
861    timeout_in_secs: u64,
862    http_client: Arc<HttpClient>,
863}
864
865#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
866/// Contains configuration for how resources will be calculated when simulating transactions.
867pub struct ResourceConfig {
868    /// Allow this many extra instructions when budgeting resources.
869    #[serde(rename = "instructionLeeway")]
870    pub instruction_leeway: u64,
871}
872
873#[allow(deprecated)] // Can be removed once Client doesn't have any code marked deprecated inside
874impl Client {
875    ///
876    /// # Errors
877    pub fn new(base_url: &str) -> Result<Self, Error> {
878        // Add the port to the base URL if there is no port explicitly included
879        // in the URL and the scheme allows us to infer a default port.
880        // Jsonrpsee requires a port to always be present even if one can be
881        // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048.
882        let uri = base_url.parse::<Uri>().map_err(Error::InvalidRpcUrl)?;
883        let mut parts = uri.into_parts();
884
885        if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) {
886            if authority.port().is_none() {
887                let port = match scheme.as_str() {
888                    "http" => Some(80),
889                    "https" => Some(443),
890                    _ => None,
891                };
892                if let Some(port) = port {
893                    let host = authority.host();
894                    parts.authority = Some(
895                        Authority::from_str(&format!("{host}:{port}"))
896                            .map_err(Error::InvalidRpcUrl)?,
897                    );
898                }
899            }
900        }
901
902        let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?;
903        let base_url = Arc::from(uri.to_string());
904        let headers = Self::default_http_headers();
905        let http_client = Arc::new(
906            HttpClientBuilder::default()
907                .set_headers(headers)
908                .build(&base_url)?,
909        );
910
911        Ok(Self {
912            base_url,
913            timeout_in_secs: 30,
914            http_client,
915        })
916    }
917
918    /// Create a new client with a timeout in seconds
919    /// # Errors
920    #[deprecated(
921        note = "To be marked private in a future major release. Please use `new_with_headers` instead."
922    )]
923    pub fn new_with_timeout(base_url: &str, timeout: u64) -> Result<Self, Error> {
924        let mut client = Self::new(base_url)?;
925        client.timeout_in_secs = timeout;
926        Ok(client)
927    }
928
929    /// Create a new client with additional headers
930    /// # Errors
931    pub fn new_with_headers(base_url: &str, additional_headers: HeaderMap) -> Result<Self, Error> {
932        let mut client = Self::new(base_url)?;
933        let mut headers = Self::default_http_headers();
934
935        for (key, value) in additional_headers {
936            headers.insert(key.ok_or(Error::InvalidResponse)?, value);
937        }
938
939        let http_client = Arc::new(
940            HttpClientBuilder::default()
941                .set_headers(headers)
942                .build(base_url)?,
943        );
944
945        client.http_client = http_client;
946        Ok(client)
947    }
948
949    fn default_http_headers() -> HeaderMap {
950        let mut headers = HeaderMap::new();
951        headers.insert("X-Client-Name", unsafe {
952            "rs-stellar-rpc-client".parse().unwrap_unchecked()
953        });
954        let version = VERSION.unwrap_or("devel");
955        headers.insert("X-Client-Version", unsafe {
956            version.parse().unwrap_unchecked()
957        });
958
959        headers
960    }
961
962    #[must_use]
963    pub fn base_url(&self) -> &str {
964        &self.base_url
965    }
966
967    #[must_use]
968    pub fn client(&self) -> &HttpClient {
969        &self.http_client
970    }
971
972    ///
973    /// # Errors
974    pub async fn friendbot_url(&self) -> Result<String, Error> {
975        let network = self.get_network().await?;
976        network.friendbot_url.ok_or_else(|| {
977            Error::NotFound(
978                "Friendbot".to_string(),
979                "Friendbot is not available on this network".to_string(),
980            )
981        })
982    }
983    ///
984    /// # Errors
985    pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
986        let server = self.get_network().await?.passphrase;
987
988        if let Some(expected) = expected {
989            if expected != server {
990                return Err(Error::InvalidNetworkPassphrase {
991                    expected: expected.to_string(),
992                    server,
993                });
994            }
995        }
996
997        Ok(server)
998    }
999
1000    ///
1001    /// # Errors
1002    pub async fn get_network(&self) -> Result<GetNetworkResponse, Error> {
1003        Ok(self
1004            .client()
1005            .request("getNetwork", ObjectParams::new())
1006            .await?)
1007    }
1008
1009    ///
1010    /// # Errors
1011    pub async fn get_health(&self) -> Result<GetHealthResponse, Error> {
1012        Ok(self
1013            .client()
1014            .request("getHealth", ObjectParams::new())
1015            .await?)
1016    }
1017
1018    ///
1019    /// # Errors
1020    pub async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse, Error> {
1021        Ok(self
1022            .client()
1023            .request("getLatestLedger", ObjectParams::new())
1024            .await?)
1025    }
1026
1027    ///
1028    /// # Errors
1029    pub async fn get_ledgers(
1030        &self,
1031        start: LedgerStart,
1032        limit: Option<usize>,
1033        format: Option<String>,
1034    ) -> Result<GetLedgersResponse, Error> {
1035        let mut oparams = ObjectParams::new();
1036
1037        let mut pagination = serde_json::Map::new();
1038        if let Some(limit) = limit {
1039            pagination.insert("limit".to_string(), limit.into());
1040        }
1041
1042        match start {
1043            LedgerStart::Ledger(l) => oparams.insert("startLedger", l)?,
1044            LedgerStart::Cursor(c) => {
1045                pagination.insert("cursor".to_string(), c.into());
1046            }
1047        }
1048
1049        oparams.insert("pagination", pagination)?;
1050
1051        if let Some(f) = format {
1052            oparams.insert("xdrFormat", f)?;
1053        }
1054
1055        Ok(self.client().request("getLedgers", oparams).await?)
1056    }
1057
1058    ///
1059    /// # Errors
1060    pub async fn get_account(&self, address: &str) -> Result<AccountEntry, Error> {
1061        let key = LedgerKey::Account(LedgerKeyAccount {
1062            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
1063                stellar_strkey::ed25519::PublicKey::from_string(address)?.0,
1064            ))),
1065        });
1066        let keys = Vec::from([key]);
1067        let response = self.get_ledger_entries(&keys).await?;
1068        let entries = response.entries.unwrap_or_default();
1069
1070        if entries.is_empty() {
1071            return Err(Error::NotFound("Account".to_string(), address.to_owned()));
1072        }
1073
1074        let ledger_entry = &entries[0];
1075        let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none());
1076
1077        if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? {
1078            Ok(entry)
1079        } else {
1080            Err(Error::InvalidResponse)
1081        }
1082    }
1083
1084    /// Get network fee stats
1085    /// # Errors
1086    pub async fn get_fee_stats(&self) -> Result<GetFeeStatsResponse, Error> {
1087        Ok(self
1088            .client()
1089            .request("getFeeStats", ObjectParams::new())
1090            .await?)
1091    }
1092
1093    ///
1094    /// # Errors
1095    pub async fn get_version_info(&self) -> Result<GetVersionInfoResponse, Error> {
1096        Ok(self
1097            .client()
1098            .request("getVersionInfo", ObjectParams::new())
1099            .await?)
1100    }
1101
1102    /// Send a transaction to the network and get back the hash of the transaction.
1103    /// # Errors
1104    pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<Hash, Error> {
1105        let mut oparams = ObjectParams::new();
1106        oparams.insert("transaction", tx.to_xdr_base64(Limits::none())?)?;
1107        let SendTransactionResponse {
1108            hash,
1109            error_result_xdr,
1110            status,
1111            ..
1112        } = self
1113            .client()
1114            .request("sendTransaction", oparams)
1115            .await
1116            .map_err(|err| {
1117                Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}"))
1118            })?;
1119
1120        if status == "ERROR" {
1121            let error = error_result_xdr
1122                .ok_or(Error::MissingError)
1123                .and_then(|x| {
1124                    TransactionResult::read_xdr_base64(&mut Limited::new(
1125                        x.as_bytes(),
1126                        Limits::none(),
1127                    ))
1128                    .map_err(|_| Error::InvalidResponse)
1129                })
1130                .map(|r| r.result)?;
1131
1132            return Err(Error::TransactionSubmissionFailed(format!("{error:#?}")));
1133        }
1134
1135        Ok(Hash::from_str(&hash)?)
1136    }
1137
1138    ///
1139    /// # Errors
1140    pub async fn send_transaction_polling(
1141        &self,
1142        tx: &TransactionEnvelope,
1143    ) -> Result<GetTransactionResponse, Error> {
1144        let hash = self.send_transaction(tx).await?;
1145        self.get_transaction_polling(&hash, None).await
1146    }
1147
1148    ///
1149    /// # Errors
1150    pub async fn simulate_transaction_envelope(
1151        &self,
1152        tx: &TransactionEnvelope,
1153        auth_mode: Option<AuthMode>,
1154    ) -> Result<SimulateTransactionResponse, Error> {
1155        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1156        let mut params = ObjectParams::new();
1157
1158        params.insert("transaction", base64_tx)?;
1159
1160        match auth_mode {
1161            Some(AuthMode::Enforce) => {
1162                params.insert("authMode", "enforce")?;
1163            }
1164            Some(AuthMode::Record) => {
1165                params.insert("authMode", "record")?;
1166            }
1167            Some(AuthMode::RecordAllowNonRoot) => {
1168                params.insert("authMode", "record_allow_nonroot")?;
1169            }
1170            None => {}
1171        }
1172
1173        let sim_res = self.client().request("simulateTransaction", params).await?;
1174
1175        Ok(sim_res)
1176    }
1177
1178    /// Internal function, not to be used.
1179    /// # Errors
1180    pub async fn next_simulate_transaction_envelope(
1181        &self,
1182        tx: &TransactionEnvelope,
1183        auth_mode: Option<AuthMode>,
1184        resource_config: Option<ResourceConfig>,
1185    ) -> Result<SimulateTransactionResponse, Error> {
1186        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1187        let mut params = ObjectParams::new();
1188
1189        params.insert("transaction", base64_tx)?;
1190
1191        match auth_mode {
1192            Some(AuthMode::Enforce) => {
1193                params.insert("authMode", "enforce")?;
1194            }
1195            Some(AuthMode::Record) => {
1196                params.insert("authMode", "record")?;
1197            }
1198            Some(AuthMode::RecordAllowNonRoot) => {
1199                params.insert("authMode", "record_allow_nonroot")?;
1200            }
1201            None => {}
1202        }
1203
1204        if let Some(ref config) = resource_config {
1205            let mut resource_config_params = ObjectParams::new();
1206            resource_config_params.insert("instructionLeeway", config.instruction_leeway)?;
1207            params.insert("resourceConfig", resource_config)?;
1208        }
1209
1210        let sim_res = self.client().request("simulateTransaction", params).await?;
1211
1212        Ok(sim_res)
1213    }
1214
1215    ///
1216    /// # Errors
1217    pub async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse, Error> {
1218        let mut oparams = ObjectParams::new();
1219        oparams.insert("hash", tx_id)?;
1220        let resp: GetTransactionResponseRaw =
1221            self.client().request("getTransaction", oparams).await?;
1222
1223        Ok(resp.try_into()?)
1224    }
1225
1226    ///
1227    /// # Errors
1228    pub async fn get_transactions(
1229        &self,
1230        request: GetTransactionsRequest,
1231    ) -> Result<GetTransactionsResponse, Error> {
1232        let mut oparams = ObjectParams::new();
1233
1234        if let Some(start_ledger) = request.start_ledger {
1235            oparams.insert("startLedger", start_ledger)?;
1236        }
1237
1238        if let Some(pagination_params) = request.pagination {
1239            let pagination = serde_json::json!(pagination_params);
1240            oparams.insert("pagination", pagination)?;
1241        }
1242
1243        let resp: GetTransactionsResponseRaw =
1244            self.client().request("getTransactions", oparams).await?;
1245
1246        Ok(resp.try_into()?)
1247    }
1248
1249    /// Poll the transaction status. Can provide a timeout in seconds, otherwise uses the default timeout.
1250    ///
1251    /// It uses exponential backoff with a base of 1 second and a maximum of 30 seconds.
1252    ///
1253    /// # Errors
1254    /// - `Error::TransactionSubmissionTimeout` if the transaction status is not found within the timeout
1255    /// - `Error::TransactionSubmissionFailed` if the transaction status is "FAILED"
1256    /// - `Error::UnexpectedTransactionStatus` if the transaction status is not one of "SUCCESS", "FAILED", or ``NOT_FOUND``
1257    /// - `json_rpsee` Errors
1258    pub async fn get_transaction_polling(
1259        &self,
1260        tx_id: &Hash,
1261        timeout_s: Option<Duration>,
1262    ) -> Result<GetTransactionResponse, Error> {
1263        // Poll the transaction status
1264        let start = Instant::now();
1265        let timeout = timeout_s.unwrap_or(Duration::from_secs(self.timeout_in_secs));
1266        // see https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=50731
1267        // Is optimimal exponent for expontial backoff
1268        let exponential_backoff: f64 = 1.0 / (1.0 - E.powf(-1.0));
1269        let mut sleep_time = Duration::from_secs(1);
1270        loop {
1271            let response = self.get_transaction(tx_id).await?;
1272            match response.status.as_str() {
1273                "SUCCESS" => return Ok(response),
1274
1275                "FAILED" => {
1276                    return Err(Error::TransactionSubmissionFailed(format!(
1277                        "{:#?}",
1278                        response.result
1279                    )))
1280                }
1281
1282                "NOT_FOUND" => (),
1283                _ => {
1284                    return Err(Error::UnexpectedTransactionStatus(response.status));
1285                }
1286            }
1287
1288            if start.elapsed() > timeout {
1289                return Err(Error::TransactionSubmissionTimeout);
1290            }
1291
1292            sleep(sleep_time).await;
1293            sleep_time = Duration::from_secs_f64(sleep_time.as_secs_f64() * exponential_backoff);
1294        }
1295    }
1296
1297    ///
1298    /// # Errors
1299    pub async fn get_ledger_entries(
1300        &self,
1301        keys: &[LedgerKey],
1302    ) -> Result<GetLedgerEntriesResponse, Error> {
1303        let mut base64_keys: Vec<String> = vec![];
1304
1305        for k in keys {
1306            let base64_result = k.to_xdr_base64(Limits::none());
1307            if base64_result.is_err() {
1308                return Err(Error::Xdr(XdrError::Invalid));
1309            }
1310            base64_keys.push(k.to_xdr_base64(Limits::none())?);
1311        }
1312
1313        let mut oparams = ObjectParams::new();
1314        oparams.insert("keys", base64_keys)?;
1315
1316        Ok(self.client().request("getLedgerEntries", oparams).await?)
1317    }
1318
1319    ///
1320    /// # Errors
1321    pub async fn get_full_ledger_entries(
1322        &self,
1323        ledger_keys: &[LedgerKey],
1324    ) -> Result<FullLedgerEntries, Error> {
1325        let keys = ledger_keys
1326            .iter()
1327            .filter(|key| !matches!(key, LedgerKey::Ttl(_)))
1328            .map(Clone::clone)
1329            .collect::<Vec<_>>();
1330        let GetLedgerEntriesResponse {
1331            entries,
1332            latest_ledger,
1333        } = self.get_ledger_entries(&keys).await?;
1334        let entries = entries
1335            .unwrap_or_default()
1336            .iter()
1337            .map(
1338                |LedgerEntryResult {
1339                     key,
1340                     xdr,
1341                     last_modified_ledger,
1342                     live_until_ledger_seq_ledger_seq,
1343                 }| {
1344                    Ok(FullLedgerEntry {
1345                        key: LedgerKey::from_xdr_base64(key, Limits::none())?,
1346                        val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?,
1347                        live_until_ledger_seq: *live_until_ledger_seq_ledger_seq,
1348                        last_modified_ledger: *last_modified_ledger,
1349                    })
1350                },
1351            )
1352            .collect::<Result<Vec<_>, Error>>()?;
1353        Ok(FullLedgerEntries {
1354            entries,
1355            latest_ledger,
1356        })
1357    }
1358
1359    ///
1360    /// # Errors
1361    pub async fn get_events(
1362        &self,
1363        start: EventStart,
1364        event_type: Option<EventType>,
1365        contract_ids: &[String],
1366        topics: &[TopicFilter],
1367        limit: Option<usize>,
1368    ) -> Result<GetEventsResponse, Error> {
1369        let mut filters = serde_json::Map::new();
1370
1371        event_type
1372            .and_then(|t| match t {
1373                EventType::All => None, // all is the default, so avoid incl. the param
1374                EventType::Contract => Some("contract"),
1375                EventType::System => Some("system"),
1376            })
1377            .map(|t| filters.insert("type".to_string(), t.into()));
1378
1379        filters.insert("topics".to_string(), topics.into());
1380        filters.insert("contractIds".to_string(), contract_ids.into());
1381
1382        let mut pagination = serde_json::Map::new();
1383        if let Some(limit) = limit {
1384            pagination.insert("limit".to_string(), limit.into());
1385        }
1386
1387        let mut oparams = ObjectParams::new();
1388        match start {
1389            EventStart::Ledger(l) => oparams.insert("startLedger", l)?,
1390            EventStart::Cursor(c) => {
1391                pagination.insert("cursor".to_string(), c.into());
1392            }
1393        }
1394        oparams.insert("filters", vec![filters])?;
1395        oparams.insert("pagination", pagination)?;
1396
1397        Ok(self.client().request("getEvents", oparams).await?)
1398    }
1399
1400    ///
1401    /// # Errors
1402    pub async fn get_contract_data(
1403        &self,
1404        contract_id: &[u8; 32],
1405    ) -> Result<ContractDataEntry, Error> {
1406        // Get the contract from the network
1407        let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData {
1408            contract: xdr::ScAddress::Contract(ContractId(xdr::Hash(*contract_id))),
1409            key: xdr::ScVal::LedgerKeyContractInstance,
1410            durability: xdr::ContractDataDurability::Persistent,
1411        });
1412        let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
1413        let entries = contract_ref.entries.unwrap_or_default();
1414        if entries.is_empty() {
1415            let contract_address = stellar_strkey::Contract(*contract_id).to_string();
1416            return Err(Error::NotFound("Contract".to_string(), contract_address));
1417        }
1418        let contract_ref_entry = &entries[0];
1419        match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1420            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1421            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1422        }
1423    }
1424
1425    ///
1426    /// # Errors
1427    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1428    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1429        match self.get_contract_data(contract_id).await? {
1430            xdr::ContractDataEntry {
1431                val:
1432                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1433                        executable: xdr::ContractExecutable::Wasm(hash),
1434                        ..
1435                    }),
1436                ..
1437            } => self.get_remote_wasm_from_hash(hash).await,
1438            scval => Err(Error::UnexpectedToken(scval)),
1439        }
1440    }
1441
1442    ///
1443    /// # Errors
1444    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1445    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1446        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1447        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1448        let entries = contract_data.entries.unwrap_or_default();
1449        if entries.is_empty() {
1450            return Err(Error::NotFound(
1451                "Contract Code".to_string(),
1452                hex::encode(hash),
1453            ));
1454        }
1455        let contract_data_entry = &entries[0];
1456        match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1457            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1458            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1459        }
1460    }
1461
1462    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1463    ///
1464    /// # Errors
1465    /// - Could fail to find contract or have a network error
1466    pub async fn get_contract_instance(
1467        &self,
1468        contract_id: &[u8; 32],
1469    ) -> Result<ScContractInstance, Error> {
1470        let contract_data = self.get_contract_data(contract_id).await?;
1471        match contract_data.val {
1472            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1473            scval => Err(Error::UnexpectedContractInstance(scval)),
1474        }
1475    }
1476}
1477
1478pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1479    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1480    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1481    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1482    Ok((toid_part, start_index))
1483}
1484
1485#[cfg(test)]
1486mod tests {
1487    use super::*;
1488    use std::env;
1489    use std::fs;
1490    use std::path::PathBuf;
1491
1492    fn get_repo_root() -> PathBuf {
1493        let mut path = env::current_exe().expect("Failed to get current executable path");
1494        // Navigate up the directory tree until we find the repository root
1495        while path.pop() {
1496            if path.join("Cargo.toml").exists() {
1497                return path;
1498            }
1499        }
1500        panic!("Could not find repository root");
1501    }
1502
1503    fn read_json_file(name: &str) -> String {
1504        let repo_root = get_repo_root();
1505        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1506        fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1507    }
1508
1509    #[test]
1510    fn simulation_transaction_response_parsing() {
1511        let s = r#"{
1512 "minResourceFee": "100000000",
1513 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1514 "transactionData": "",
1515 "latestLedger": 1234,
1516 "stateChanges": [{
1517    "type": "created",
1518    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1519    "before": null,
1520    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1521  }]
1522  }"#;
1523
1524        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1525        assert_eq!(
1526            resp.state_changes.unwrap()[0],
1527            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1528        );
1529        assert_eq!(resp.min_resource_fee, 100_000_000);
1530    }
1531
1532    #[test]
1533    fn simulation_transaction_response_parsing_mostly_empty() {
1534        let s = r#"{
1535 "latestLedger": 1234
1536        }"#;
1537
1538        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1539        assert_eq!(resp.latest_ledger, 1_234);
1540    }
1541
1542    #[test]
1543    fn test_parse_transaction_response_p23() {
1544        let response_content = read_json_file("transaction_response_p23.json");
1545        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1546            .expect("Failed to parse JSON from transaction_response_p23.json");
1547        let result = full_response["result"].clone();
1548        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1549            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1550        let response: GetTransactionResponse = raw_response
1551            .try_into()
1552            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1553
1554        assert_eq!(2, response.events.transaction_events.iter().len());
1555        assert_eq!(1, response.events.contract_events.len());
1556        assert_eq!(21, response.events.diagnostic_events.iter().len());
1557    }
1558
1559    #[test]
1560    fn test_parse_transaction_response_p22() {
1561        let response_content = read_json_file("transaction_response_p22.json");
1562        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1563            .expect("Failed to parse JSON from transaction_response_p22.json");
1564        let result = full_response["result"].clone();
1565        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1566            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1567        let response: GetTransactionResponse = raw_response
1568            .try_into()
1569            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1570
1571        assert_eq!(23, response.events.diagnostic_events.iter().len());
1572    }
1573
1574    #[test]
1575    fn test_parse_get_transactions_response() {
1576        let response_content = read_json_file("transactions_response.json");
1577
1578        // Parse the entire response
1579        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1580            .expect("Failed to parse JSON from transactions_response.json");
1581
1582        // Extract the "result" field
1583        let result = full_response["result"].clone();
1584        // Parse the "result" content as GetTransactionsResponseRaw
1585        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1586            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1587
1588        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1589        let response: GetTransactionsResponse = raw_response
1590            .try_into()
1591            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1592
1593        // Assertions
1594        assert_eq!(response.transactions.len(), 5);
1595        assert_eq!(response.latest_ledger, 556_962);
1596        assert_eq!(response.cursor, 2_379_420_471_922_689);
1597
1598        // Additional assertions for specific transaction attributes
1599        assert_eq!(response.transactions[0].status, "SUCCESS");
1600        //assert_eq!(response.transactions[0].application_order, 1);
1601        //assert_eq!(response.transactions[0].ledger, 554000);
1602    }
1603
1604    #[test]
1605    fn test_rpc_url_default_ports() {
1606        // Default ports are added.
1607        let client = Client::new("http://example.com").unwrap();
1608        assert_eq!(client.base_url(), "http://example.com:80/");
1609        let client = Client::new("https://example.com").unwrap();
1610        assert_eq!(client.base_url(), "https://example.com:443/");
1611
1612        // Ports are not added when already present.
1613        let client = Client::new("http://example.com:8080").unwrap();
1614        assert_eq!(client.base_url(), "http://example.com:8080/");
1615        let client = Client::new("https://example.com:8080").unwrap();
1616        assert_eq!(client.base_url(), "https://example.com:8080/");
1617
1618        // Paths are not modified.
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/c/").unwrap();
1624        assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1625        let client = Client::new("https://example.com/a/b/c/").unwrap();
1626        assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1627        let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1628        assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1629        let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1630        assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1631    }
1632
1633    #[test]
1634    // Taken from [RPC server
1635    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1636    fn test_does_topic_match() {
1637        struct TestCase<'a> {
1638            name: &'a str,
1639            filter: Vec<&'a str>,
1640            includes: Vec<Vec<&'a str>>,
1641            excludes: Vec<Vec<&'a str>>,
1642        }
1643
1644        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1645        let number = "AAAAAQB6Mcc=";
1646        let star = "*";
1647
1648        for tc in vec![
1649            // No filter means match nothing.
1650            TestCase {
1651                name: "<empty>",
1652                filter: vec![],
1653                includes: vec![],
1654                excludes: vec![vec![xfer]],
1655            },
1656            // "*" should match "transfer/" but not "transfer/transfer" or
1657            // "transfer/amount", because * is specified as a SINGLE segment
1658            // wildcard.
1659            TestCase {
1660                name: "*",
1661                filter: vec![star],
1662                includes: vec![vec![xfer]],
1663                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1664            },
1665            // "*/transfer" should match anything preceding "transfer", but
1666            // nothing that isn't exactly two segments long.
1667            TestCase {
1668                name: "*/transfer",
1669                filter: vec![star, xfer],
1670                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1671                excludes: vec![
1672                    vec![number],
1673                    vec![number, number],
1674                    vec![number, xfer, number],
1675                    vec![xfer],
1676                    vec![xfer, number],
1677                    vec![xfer, xfer, xfer],
1678                ],
1679            },
1680            // The inverse case of before: "transfer/*" should match any single
1681            // segment after a segment that is exactly "transfer", but no
1682            // additional segments.
1683            TestCase {
1684                name: "transfer/*",
1685                filter: vec![xfer, star],
1686                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1687                excludes: vec![
1688                    vec![number],
1689                    vec![number, number],
1690                    vec![number, xfer, number],
1691                    vec![xfer],
1692                    vec![number, xfer],
1693                    vec![xfer, xfer, xfer],
1694                ],
1695            },
1696            // Here, we extend to exactly two wild segments after transfer.
1697            TestCase {
1698                name: "transfer/*/*",
1699                filter: vec![xfer, star, star],
1700                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1701                excludes: vec![
1702                    vec![number],
1703                    vec![number, number],
1704                    vec![number, xfer],
1705                    vec![number, xfer, number, number],
1706                    vec![xfer],
1707                    vec![xfer, xfer, xfer, xfer],
1708                ],
1709            },
1710            // Here, we ensure wildcards can be in the middle of a filter: only
1711            // exact matches happen on the ends, while the middle can be
1712            // anything.
1713            TestCase {
1714                name: "transfer/*/number",
1715                filter: vec![xfer, star, number],
1716                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1717                excludes: vec![
1718                    vec![number],
1719                    vec![number, number],
1720                    vec![number, number, number],
1721                    vec![number, xfer, number],
1722                    vec![xfer],
1723                    vec![number, xfer],
1724                    vec![xfer, xfer, xfer],
1725                    vec![xfer, number, xfer],
1726                ],
1727            },
1728        ] {
1729            for topic in tc.includes {
1730                assert!(
1731                    does_topic_match(
1732                        &topic
1733                            .iter()
1734                            .map(std::string::ToString::to_string)
1735                            .collect::<Vec<String>>(),
1736                        &tc.filter
1737                            .iter()
1738                            .map(std::string::ToString::to_string)
1739                            .collect::<Vec<String>>()
1740                    ),
1741                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
1742                    tc.name,
1743                    topic,
1744                    tc.filter
1745                );
1746            }
1747
1748            for topic in tc.excludes {
1749                assert!(
1750                    !does_topic_match(
1751                        // make deep copies of the vecs
1752                        &topic
1753                            .iter()
1754                            .map(std::string::ToString::to_string)
1755                            .collect::<Vec<String>>(),
1756                        &tc.filter
1757                            .iter()
1758                            .map(std::string::ToString::to_string)
1759                            .collect::<Vec<String>>()
1760                    ),
1761                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
1762                    tc.name,
1763                    topic,
1764                    tc.filter
1765                );
1766            }
1767        }
1768    }
1769}