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