Skip to main content

wasi_stellar_rpc_client/
lib.rs

1use itertools::Itertools;
2use serde_aux::prelude::{
3    deserialize_default_from_null, deserialize_number_from_string,
4    deserialize_option_number_from_string,
5};
6use serde_with::{serde_as, DisplayFromStr};
7use stellar_xdr::curr::{
8    self as xdr, AccountEntry, AccountId, ContractDataEntry, ContractEvent, ContractId,
9    DiagnosticEvent, Error as XdrError, Hash, LedgerCloseMeta, LedgerEntryData, LedgerFootprint,
10    LedgerHeaderHistoryEntry, LedgerKey, LedgerKeyAccount, Limited, Limits, PublicKey, ReadXdr,
11    ScContractInstance, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData,
12    TransactionEnvelope, TransactionEvent, TransactionMetaV3, TransactionResult, Uint256, VecM,
13    WriteXdr,
14};
15use wasi_jsonrpsee_core::params::ObjectParams;
16use wasi_jsonrpsee_core::{self, client::ClientT};
17use wasi_jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder};
18
19use std::{
20    f64::consts::E,
21    fmt::Display,
22    str::FromStr,
23    sync::Arc,
24    time::{Duration, Instant},
25};
26
27use termcolor::{Color, ColorChoice, StandardStream, WriteColor};
28use termcolor_output::colored;
29use tokio::time::sleep;
30
31const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
32
33pub type LogEvents = fn(
34    footprint: &LedgerFootprint,
35    auth: &[VecM<SorobanAuthorizationEntry>],
36    events: &[DiagnosticEvent],
37) -> ();
38
39pub type LogResources = fn(resources: &SorobanResources) -> ();
40
41#[derive(thiserror::Error, Debug)]
42#[allow(deprecated)] // Can be removed once Error enum doesn't have any code marked deprecated inside
43pub enum Error {
44    #[error(transparent)]
45    InvalidAddress(#[from] stellar_strkey::DecodeError),
46    #[error("invalid response from server")]
47    InvalidResponse,
48    #[error("provided network passphrase {expected:?} does not match the server: {server:?}")]
49    InvalidNetworkPassphrase { expected: String, server: String },
50    #[error("xdr processing error: {0}")]
51    Xdr(#[from] XdrError),
52    #[error("invalid friendbot url: {0}")]
53    InvalidUrl(String),
54    #[error(transparent)]
55    JsonRpc(#[from] wasi_jsonrpsee_core::ClientError),
56    #[error("json decoding error: {0}")]
57    Serde(#[from] serde_json::Error),
58    #[error("transaction failed: {0}")]
59    TransactionFailed(String),
60    #[error("transaction submission failed: {0}")]
61    TransactionSubmissionFailed(String),
62    #[error("expected transaction status: {0}")]
63    UnexpectedTransactionStatus(String),
64    #[error("transaction submission timeout")]
65    TransactionSubmissionTimeout,
66    #[error("transaction simulation failed: {0}")]
67    TransactionSimulationFailed(String),
68    #[error("{0} not found: {1}")]
69    NotFound(String, String),
70    #[error("Missing result in successful response")]
71    MissingResult,
72    #[error("Failed to read Error response from server")]
73    MissingError,
74    #[error("Missing signing key for account {address}")]
75    MissingSignerForAddress { address: String },
76    #[error("cursor is not valid")]
77    InvalidCursor,
78    #[error("unexpected ({length}) simulate transaction result length")]
79    UnexpectedSimulateTransactionResultSize { length: usize },
80    #[error("unexpected ({count}) number of operations")]
81    UnexpectedOperationCount { count: usize },
82    #[error("Transaction contains unsupported operation type")]
83    UnsupportedOperationType,
84    #[error("unexpected contract code data type: {0:?}")]
85    UnexpectedContractCodeDataType(Box<LedgerEntryData>),
86    #[error("unexpected contract instance type: {0:?}")]
87    UnexpectedContractInstance(Box<xdr::ScVal>),
88    #[error("unexpected contract code got token {0:?}")]
89    #[deprecated(note = "To be removed in future versions")]
90    UnexpectedToken(Box<ContractDataEntry>),
91    #[error("Fee was too large {0}")]
92    LargeFee(u64),
93    #[error("Cannot authorize raw transactions")]
94    CannotAuthorizeRawTransaction,
95    #[error("Missing result for tnx")]
96    MissingOp,
97}
98
99#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
100pub struct SendTransactionResponse {
101    pub hash: String,
102    pub status: String,
103    #[serde(
104        rename = "errorResultXdr",
105        skip_serializing_if = "Option::is_none",
106        default
107    )]
108    pub error_result_xdr: Option<String>,
109    #[serde(rename = "latestLedger")]
110    pub latest_ledger: u32,
111    #[serde(
112        rename = "latestLedgerCloseTime",
113        deserialize_with = "deserialize_number_from_string"
114    )]
115    pub latest_ledger_close_time: u32,
116}
117
118#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
119// TODO: add ledger info and application order
120pub struct GetTransactionResponseRaw {
121    pub status: String,
122
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub ledger: Option<u32>,
125
126    #[serde(
127        rename = "applicationOrder",
128        skip_serializing_if = "Option::is_none",
129        default
130    )]
131    pub application_order: Option<u32>,
132
133    #[serde(rename = "feeBump", skip_serializing_if = "Option::is_none", default)]
134    pub fee_bump: Option<bool>,
135
136    #[serde(
137        rename = "envelopeXdr",
138        skip_serializing_if = "Option::is_none",
139        default
140    )]
141    pub envelope_xdr: Option<String>,
142
143    #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)]
144    pub result_xdr: Option<String>,
145
146    #[serde(
147        rename = "resultMetaXdr",
148        skip_serializing_if = "Option::is_none",
149        default
150    )]
151    pub result_meta_xdr: Option<String>,
152
153    #[serde(rename = "txHash", skip_serializing_if = "Option::is_none", default)]
154    pub tx_hash: Option<String>,
155
156    #[serde(
157        rename = "createdAt",
158        deserialize_with = "deserialize_option_i64_from_string_or_number",
159        skip_serializing_if = "Option::is_none",
160        default
161    )]
162    pub created_at: Option<i64>,
163
164    #[serde(rename = "events", skip_serializing_if = "Option::is_none", default)]
165    pub events: Option<GetTransactionEventsRaw>,
166}
167
168#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Default)]
169pub struct GetTransactionEventsRaw {
170    #[serde(
171        rename = "contractEventsXdr",
172        skip_serializing_if = "Option::is_none",
173        default
174    )]
175    pub contract_events_xdr: Option<Vec<Vec<String>>>,
176
177    #[serde(
178        rename = "diagnosticEventsXdr",
179        skip_serializing_if = "Option::is_none",
180        default
181    )]
182    pub diagnostic_events_xdr: Option<Vec<String>>,
183
184    #[serde(
185        rename = "transactionEventsXdr",
186        skip_serializing_if = "Option::is_none",
187        default
188    )]
189    pub transaction_events_xdr: Option<Vec<String>>,
190}
191
192#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
193pub struct GetTransactionEvents {
194    pub contract_events: Vec<Vec<ContractEvent>>,
195    pub diagnostic_events: Vec<DiagnosticEvent>,
196    pub transaction_events: Vec<TransactionEvent>,
197}
198
199#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
200pub struct GetTransactionResponse {
201    pub status: String,
202    pub ledger: Option<u32>,
203    pub application_order: Option<u32>,
204    pub fee_bump: Option<bool>,
205    pub tx_hash: Option<String>,
206    pub created_at: Option<i64>,
207    pub envelope: Option<xdr::TransactionEnvelope>,
208    pub result: Option<xdr::TransactionResult>,
209    pub result_meta: Option<xdr::TransactionMeta>,
210    pub events: GetTransactionEvents,
211}
212
213impl TryInto<GetTransactionResponse> for GetTransactionResponseRaw {
214    type Error = xdr::Error;
215
216    fn try_into(self) -> Result<GetTransactionResponse, Self::Error> {
217        let events = self.events.unwrap_or_default();
218        let result_meta: Option<xdr::TransactionMeta> = self
219            .result_meta_xdr
220            .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
221            .transpose()?;
222
223        let events = match result_meta {
224            Some(xdr::TransactionMeta::V4(_)) => GetTransactionEvents {
225                contract_events: events
226                    .contract_events_xdr
227                    .unwrap_or_default()
228                    .into_iter()
229                    .map(|es| {
230                        es.into_iter()
231                            .filter_map(|e| ContractEvent::from_xdr_base64(e, Limits::none()).ok())
232                            .collect::<Vec<_>>()
233                    })
234                    .collect::<Vec<Vec<ContractEvent>>>(),
235
236                diagnostic_events: events
237                    .diagnostic_events_xdr
238                    .unwrap_or_default()
239                    .iter()
240                    .filter_map(|e| DiagnosticEvent::from_xdr_base64(e, Limits::none()).ok())
241                    .collect(),
242
243                transaction_events: events
244                    .transaction_events_xdr
245                    .unwrap_or_default()
246                    .iter()
247                    .filter_map(|e| TransactionEvent::from_xdr_base64(e, Limits::none()).ok())
248                    .collect(),
249            },
250
251            Some(xdr::TransactionMeta::V3(TransactionMetaV3 {
252                soroban_meta: Some(ref meta),
253                ..
254            })) => GetTransactionEvents {
255                contract_events: vec![],
256                transaction_events: vec![],
257                diagnostic_events: meta.diagnostic_events.clone().into(),
258            },
259
260            _ => GetTransactionEvents {
261                contract_events: vec![],
262                transaction_events: vec![],
263                diagnostic_events: vec![],
264            },
265        };
266
267        Ok(GetTransactionResponse {
268            status: self.status,
269            ledger: self.ledger,
270            application_order: self.application_order,
271            fee_bump: self.fee_bump,
272            tx_hash: self.tx_hash,
273            created_at: self.created_at,
274            envelope: self
275                .envelope_xdr
276                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
277                .transpose()?,
278            result: self
279                .result_xdr
280                .map(|v| ReadXdr::from_xdr_base64(v, Limits::none()))
281                .transpose()?,
282            result_meta,
283            events,
284        })
285    }
286}
287
288impl GetTransactionResponse {
289    ///
290    /// # Errors
291    pub fn return_value(&self) -> Result<xdr::ScVal, Error> {
292        if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
293            soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
294            ..
295        })) = &self.result_meta
296        {
297            return Ok(return_value.clone());
298        }
299
300        if let Some(xdr::TransactionMeta::V4(xdr::TransactionMetaV4 {
301            soroban_meta:
302                Some(xdr::SorobanTransactionMetaV2 {
303                    return_value: Some(return_value),
304                    ..
305                }),
306            ..
307        })) = &self.result_meta
308        {
309            return Ok(return_value.clone());
310        }
311
312        Err(Error::MissingOp)
313    }
314}
315
316#[serde_as]
317#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
318pub struct GetTransactionsResponseRaw {
319    pub transactions: Vec<GetTransactionResponseRaw>,
320    #[serde(rename = "latestLedger")]
321    pub latest_ledger: u32,
322    #[serde(rename = "latestLedgerCloseTimestamp")]
323    pub latest_ledger_close_time: i64,
324    #[serde(rename = "oldestLedger")]
325    pub oldest_ledger: u32,
326    #[serde(rename = "oldestLedgerCloseTimestamp")]
327    pub oldest_ledger_close_time: i64,
328    #[serde_as(as = "DisplayFromStr")]
329    pub cursor: u64,
330}
331
332#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
333pub struct GetTransactionsResponse {
334    pub transactions: Vec<GetTransactionResponse>,
335    pub latest_ledger: u32,
336    pub latest_ledger_close_time: i64,
337    pub oldest_ledger: u32,
338    pub oldest_ledger_close_time: i64,
339    pub cursor: u64,
340}
341
342impl TryInto<GetTransactionsResponse> for GetTransactionsResponseRaw {
343    type Error = xdr::Error; // assuming xdr::Error or any other error type that you use
344
345    fn try_into(self) -> Result<GetTransactionsResponse, Self::Error> {
346        Ok(GetTransactionsResponse {
347            transactions: self
348                .transactions
349                .into_iter()
350                .map(TryInto::try_into)
351                .collect::<Result<Vec<_>, xdr::Error>>()?,
352            latest_ledger: self.latest_ledger,
353            latest_ledger_close_time: self.latest_ledger_close_time,
354            oldest_ledger: self.oldest_ledger,
355            oldest_ledger_close_time: self.oldest_ledger_close_time,
356            cursor: self.cursor,
357        })
358    }
359}
360
361#[serde_as]
362#[derive(serde::Serialize, Debug, Clone)]
363pub struct TransactionsPaginationOptions {
364    #[serde_as(as = "Option<DisplayFromStr>")]
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub cursor: Option<u64>,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub limit: Option<u32>,
369}
370
371#[derive(serde::Serialize, Debug, Clone)]
372pub struct GetTransactionsRequest {
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub start_ledger: Option<u32>,
375    pub pagination: Option<TransactionsPaginationOptions>,
376}
377
378#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
379pub struct LedgerEntryResult {
380    pub key: String,
381    pub xdr: String,
382    #[serde(rename = "lastModifiedLedgerSeq")]
383    pub last_modified_ledger: u32,
384    #[serde(
385        rename = "liveUntilLedgerSeq",
386        skip_serializing_if = "Option::is_none",
387        deserialize_with = "deserialize_option_number_from_string",
388        default
389    )]
390    pub live_until_ledger_seq_ledger_seq: Option<u32>,
391}
392
393#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
394pub struct GetLedgerEntriesResponse {
395    pub entries: Option<Vec<LedgerEntryResult>>,
396    #[serde(rename = "latestLedger")]
397    pub latest_ledger: i64,
398}
399
400#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
401pub struct GetNetworkResponse {
402    #[serde(
403        rename = "friendbotUrl",
404        skip_serializing_if = "Option::is_none",
405        default
406    )]
407    pub friendbot_url: Option<String>,
408    pub passphrase: String,
409    #[serde(rename = "protocolVersion")]
410    pub protocol_version: u32,
411}
412
413#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
414pub struct GetHealthResponse {
415    pub status: String,
416    #[serde(rename = "latestLedger")]
417    pub latest_ledger: u32,
418    #[serde(rename = "oldestLedger")]
419    pub oldest_ledger: u32,
420    #[serde(rename = "ledgerRetentionWindow")]
421    pub ledger_retention_window: u32,
422}
423
424#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
425pub struct GetVersionInfoResponse {
426    pub version: String,
427    #[serde(rename = "commitHash")]
428    pub commmit_hash: String,
429    #[serde(rename = "buildTimestamp")]
430    pub build_timestamp: String,
431    #[serde(rename = "captiveCoreVersion")]
432    pub captive_core_version: String,
433    #[serde(rename = "protocolVersion")]
434    pub protocol_version: u32,
435}
436
437#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
438pub struct GetLatestLedgerResponse {
439    pub id: String,
440    #[serde(rename = "protocolVersion")]
441    pub protocol_version: u32,
442    pub sequence: u32,
443}
444
445#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
446pub struct GetFeeStatsResponse {
447    #[serde(rename = "sorobanInclusionFee")]
448    pub soroban_inclusion_fee: FeeStat,
449    #[serde(rename = "inclusionFee")]
450    pub inclusion_fee: FeeStat,
451    #[serde(
452        rename = "latestLedger",
453        deserialize_with = "deserialize_number_from_string"
454    )]
455    pub latest_ledger: u32,
456}
457
458#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
459pub struct FeeStat {
460    pub max: String,
461    pub min: String,
462    // Fee value which occurs the most often
463    pub mode: String,
464    // 10th nearest-rank fee percentile
465    pub p10: String,
466    // 20th nearest-rank fee percentile
467    pub p20: String,
468    // 30th nearest-rank fee percentile
469    pub p30: String,
470    // 40th nearest-rank fee percentile
471    pub p40: String,
472    // 50th nearest-rank fee percentile
473    pub p50: String,
474    // 60th nearest-rank fee percentile
475    pub p60: String,
476    // 70th nearest-rank fee percentile
477    pub p70: String,
478    // 80th nearest-rank fee percentile
479    pub p80: String,
480    // 90th nearest-rank fee percentile.
481    pub p90: String,
482    // 95th nearest-rank fee percentile.
483    pub p95: String,
484    // 99th nearest-rank fee percentile
485    pub p99: String,
486    // How many transactions are part of the distribution
487    #[serde(
488        rename = "transactionCount",
489        deserialize_with = "deserialize_number_from_string"
490    )]
491    pub transaction_count: u32,
492    // How many consecutive ledgers form the distribution
493    #[serde(
494        rename = "ledgerCount",
495        deserialize_with = "deserialize_number_from_string"
496    )]
497    pub ledger_count: u32,
498}
499
500#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
501pub struct Cost {
502    #[serde(
503        rename = "cpuInsns",
504        deserialize_with = "deserialize_number_from_string"
505    )]
506    pub cpu_insns: u64,
507    #[serde(
508        rename = "memBytes",
509        deserialize_with = "deserialize_number_from_string"
510    )]
511    pub mem_bytes: u64,
512}
513
514#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
515pub struct SimulateHostFunctionResultRaw {
516    #[serde(deserialize_with = "deserialize_default_from_null")]
517    pub auth: Vec<String>,
518    pub xdr: String,
519}
520
521#[derive(Debug, Clone)]
522pub struct SimulateHostFunctionResult {
523    pub auth: Vec<SorobanAuthorizationEntry>,
524    pub xdr: xdr::ScVal,
525}
526
527#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
528#[serde(tag = "type")]
529pub enum LedgerEntryChange {
530    #[serde(rename = "created")]
531    Created { key: String, after: String },
532    #[serde(rename = "deleted")]
533    Deleted { key: String, before: String },
534    #[serde(rename = "updated")]
535    Updated {
536        key: String,
537        before: String,
538        after: String,
539    },
540}
541
542#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
543pub struct SimulateTransactionResponse {
544    #[serde(
545        rename = "minResourceFee",
546        deserialize_with = "deserialize_number_from_string",
547        default
548    )]
549    pub min_resource_fee: u64,
550    #[serde(default)]
551    pub cost: Cost,
552    #[serde(skip_serializing_if = "Vec::is_empty", default)]
553    pub results: Vec<SimulateHostFunctionResultRaw>,
554    #[serde(rename = "transactionData", default)]
555    pub transaction_data: String,
556    #[serde(
557        deserialize_with = "deserialize_default_from_null",
558        skip_serializing_if = "Vec::is_empty",
559        default
560    )]
561    pub events: Vec<String>,
562    #[serde(
563        rename = "restorePreamble",
564        skip_serializing_if = "Option::is_none",
565        default
566    )]
567    pub restore_preamble: Option<RestorePreamble>,
568    #[serde(
569        rename = "stateChanges",
570        skip_serializing_if = "Option::is_none",
571        default
572    )]
573    pub state_changes: Option<Vec<LedgerEntryChange>>,
574    #[serde(rename = "latestLedger")]
575    pub latest_ledger: u32,
576    #[serde(skip_serializing_if = "Option::is_none", default)]
577    pub error: Option<String>,
578}
579
580impl SimulateTransactionResponse {
581    ///
582    /// # Errors
583    pub fn results(&self) -> Result<Vec<SimulateHostFunctionResult>, Error> {
584        self.results
585            .iter()
586            .map(|r| {
587                Ok(SimulateHostFunctionResult {
588                    auth: r
589                        .auth
590                        .iter()
591                        .map(|a| {
592                            Ok(SorobanAuthorizationEntry::from_xdr_base64(
593                                a,
594                                Limits::none(),
595                            )?)
596                        })
597                        .collect::<Result<_, Error>>()?,
598                    xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?,
599                })
600            })
601            .collect()
602    }
603
604    ///
605    /// # Errors
606    pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
607        self.events
608            .iter()
609            .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?))
610            .collect()
611    }
612
613    ///
614    /// # Errors
615    pub fn transaction_data(&self) -> Result<SorobanTransactionData, Error> {
616        Ok(SorobanTransactionData::from_xdr_base64(
617            &self.transaction_data,
618            Limits::none(),
619        )?)
620    }
621}
622
623#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
624pub struct RestorePreamble {
625    #[serde(rename = "transactionData")]
626    pub transaction_data: String,
627    #[serde(
628        rename = "minResourceFee",
629        deserialize_with = "deserialize_number_from_string"
630    )]
631    pub min_resource_fee: u64,
632}
633
634#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
635pub struct GetEventsResponse {
636    #[serde(deserialize_with = "deserialize_default_from_null")]
637    pub events: Vec<Event>,
638    #[serde(rename = "latestLedger")]
639    pub latest_ledger: u32,
640    #[serde(rename = "latestLedgerCloseTime")]
641    pub latest_ledger_close_time: String,
642    #[serde(rename = "oldestLedger")]
643    pub oldest_ledger: u32,
644    #[serde(rename = "oldestLedgerCloseTime")]
645    pub oldest_ledger_close_time: String,
646    pub cursor: String,
647}
648
649#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
650pub struct GetLedgersResponse {
651    #[serde(rename = "latestLedger")]
652    pub latest_ledger: u32,
653    #[serde(
654        rename = "latestLedgerCloseTime",
655        deserialize_with = "deserialize_number_from_string"
656    )]
657    pub latest_ledger_close_time: i64,
658    #[serde(rename = "oldestLedger")]
659    pub oldest_ledger: u32,
660    #[serde(rename = "oldestLedgerCloseTime")]
661    pub oldest_ledger_close_time: i64,
662    pub cursor: String,
663    pub ledgers: Vec<Ledger>,
664}
665
666#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
667pub struct Ledger {
668    pub hash: String,
669    pub sequence: u32,
670    #[serde(rename = "ledgerCloseTime")]
671    pub ledger_close_time: String,
672    #[serde(rename = "headerXdr")]
673    pub header_xdr: String,
674    #[serde(rename = "headerJson")]
675    pub header_json: Option<LedgerHeaderHistoryEntry>,
676    #[serde(rename = "metadataXdr")]
677    pub metadata_xdr: String,
678    #[serde(rename = "metadataJson")]
679    pub metadata_json: Option<LedgerCloseMeta>,
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    #[serde(rename = "contractId")]
691    pub contract_id: String,
692
693    pub id: String,
694
695    #[serde(
696        rename = "operationIndex",
697        default,
698        skip_serializing_if = "Option::is_none"
699    )]
700    pub operation_index: Option<u32>,
701    #[serde(
702        rename = "transactionIndex",
703        default,
704        skip_serializing_if = "Option::is_none"
705    )]
706    pub transaction_index: Option<u32>,
707    #[serde(rename = "txHash", default, skip_serializing_if = "Option::is_none")]
708    pub tx_hash: Option<String>,
709    #[deprecated(
710        note = "This field is deprecated by Stellar RPC. See https://stellar.org/blog/developers/protocol-23-upgrade-guide"
711    )]
712    #[serde(
713        rename = "inSuccessfulContractCall",
714        default,
715        skip_serializing_if = "Option::is_none"
716    )]
717    pub is_successful_contract_call: Option<bool>,
718
719    pub topic: Vec<String>,
720    pub value: String,
721}
722
723impl Display for Event {
724    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
725        writeln!(
726            f,
727            "Event {} [{}]:",
728            self.id,
729            self.event_type.to_ascii_uppercase()
730        )?;
731        writeln!(
732            f,
733            "  Ledger:   {} (closed at {})",
734            self.ledger, self.ledger_closed_at
735        )?;
736        writeln!(f, "  Contract: {}", self.contract_id)?;
737        writeln!(f, "  Topics:")?;
738
739        for topic in &self.topic {
740            let scval =
741                xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?;
742            writeln!(f, "            {scval:?}")?;
743        }
744
745        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())
746            .map_err(|_| std::fmt::Error)?;
747
748        writeln!(f, "  Value:    {scval:?}")
749    }
750}
751
752pub type SegmentFilter = String;
753pub type TopicFilter = Vec<SegmentFilter>;
754
755impl Event {
756    ///
757    /// # Errors
758    pub fn parse_cursor(&self) -> Result<(u64, i32), Error> {
759        parse_cursor(&self.id)
760    }
761
762    ///
763    /// # Errors
764    pub fn pretty_print(&self) -> Result<(), Box<dyn std::error::Error>> {
765        let mut stdout = StandardStream::stdout(ColorChoice::Auto);
766
767        if !stdout.supports_color() {
768            println!("{self}");
769            return Ok(());
770        }
771
772        let color = match self.event_type.as_str() {
773            "system" => Color::Yellow,
774            _ => Color::Blue,
775        };
776        colored!(
777            stdout,
778            "{}Event{} {}{}{} [{}{}{}{}]:\n",
779            bold!(true),
780            bold!(false),
781            fg!(Some(Color::Green)),
782            self.id,
783            reset!(),
784            bold!(true),
785            fg!(Some(color)),
786            self.event_type.to_ascii_uppercase(),
787            reset!(),
788        )?;
789
790        colored!(
791            stdout,
792            "  Ledger:   {}{}{} (closed at {}{}{})\n",
793            fg!(Some(Color::Green)),
794            self.ledger,
795            reset!(),
796            fg!(Some(Color::Green)),
797            self.ledger_closed_at,
798            reset!(),
799        )?;
800
801        colored!(
802            stdout,
803            "  Contract: {}{}{}\n",
804            fg!(Some(Color::Green)),
805            self.contract_id,
806            reset!(),
807        )?;
808
809        colored!(stdout, "  Topics:\n")?;
810        for topic in &self.topic {
811            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?;
812            colored!(
813                stdout,
814                "            {}{:?}{}\n",
815                fg!(Some(Color::Green)),
816                scval,
817                reset!(),
818            )?;
819        }
820
821        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?;
822        colored!(
823            stdout,
824            "  Value: {}{:?}{}\n\n",
825            fg!(Some(Color::Green)),
826            scval,
827            reset!(),
828        )?;
829
830        Ok(())
831    }
832}
833
834/// Defines non-root authorization for simulated transactions.
835pub enum AuthMode {
836    Enforce,
837    Record,
838    RecordAllowNonRoot,
839}
840
841#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
842pub enum EventType {
843    All,
844    Contract,
845    System,
846}
847
848#[derive(Clone, Debug, Eq, Hash, PartialEq)]
849pub enum LedgerStart {
850    Ledger(u32),
851    Cursor(String),
852}
853
854/// An inclusive ledger range. Construct via [`EventStart::ledger_range`].
855#[derive(Clone, Debug, Eq, Hash, PartialEq)]
856pub struct LedgerRange {
857    start: u32,
858    end: u32,
859}
860
861impl LedgerRange {
862    pub fn start(&self) -> u32 {
863        self.start
864    }
865
866    pub fn end(&self) -> u32 {
867        self.end
868    }
869}
870
871#[derive(Clone, Debug, Eq, Hash, PartialEq)]
872pub enum EventStart {
873    Ledger(u32),
874    /// A range of ledgers, inclusive. Use [`EventStart::ledger_range`] to
875    /// construct this variant with validation.
876    LedgerRange(LedgerRange),
877    Cursor(String),
878}
879
880impl EventStart {
881    /// Construct an [`EventStart::LedgerRange`] ensuring that `start <= end`.
882    ///
883    /// Returns an `Err` with a descriptive message if `start > end`.
884    pub fn ledger_range(start: u32, end: u32) -> Result<Self, String> {
885        if start > end {
886            return Err(format!(
887                "invalid ledger range: start ({start}) must be <= end ({end})"
888            ));
889        }
890        Ok(EventStart::LedgerRange(LedgerRange { start, end }))
891    }
892}
893
894#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
895pub struct FullLedgerEntry {
896    pub key: LedgerKey,
897    pub val: LedgerEntryData,
898    #[serde(rename = "lastModifiedLedgerSeq")]
899    pub last_modified_ledger: u32,
900    #[serde(
901        rename = "liveUntilLedgerSeq",
902        skip_serializing_if = "Option::is_none",
903        deserialize_with = "deserialize_option_number_from_string",
904        default
905    )]
906    pub live_until_ledger_seq: Option<u32>,
907}
908
909#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
910pub struct FullLedgerEntries {
911    pub entries: Vec<FullLedgerEntry>,
912    #[serde(rename = "latestLedger")]
913    pub latest_ledger: i64,
914}
915
916#[derive(Debug, Clone)]
917pub struct Client {
918    base_url: Arc<str>,
919    timeout_in_secs: u64,
920    http_client: Arc<HttpClient>,
921}
922
923#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
924/// Contains configuration for how resources will be calculated when simulating transactions.
925pub struct ResourceConfig {
926    /// Allow this many extra instructions when budgeting resources.
927    #[serde(rename = "instructionLeeway")]
928    pub instruction_leeway: u64,
929}
930
931#[allow(deprecated)] // Can be removed once Client doesn't have any code marked deprecated inside
932impl Client {
933    ///
934    /// # Errors
935    pub fn new(base_url: &str) -> Result<Self, Error> {
936        let headers = Self::default_http_headers();
937        let http_client = Arc::new(
938            HttpClientBuilder::default()
939                .set_headers(headers)
940                .build(base_url)?,
941        );
942
943        Ok(Self {
944            base_url: Arc::from(base_url),
945            timeout_in_secs: 30,
946            http_client,
947        })
948    }
949
950    /// Create a new client with a timeout in seconds
951    /// # Errors
952    #[deprecated(
953        note = "To be marked private in a future major release. Please use `new_with_headers` instead."
954    )]
955    pub fn new_with_timeout(base_url: &str, timeout: u64) -> Result<Self, Error> {
956        let mut client = Self::new(base_url)?;
957        client.timeout_in_secs = timeout;
958        Ok(client)
959    }
960
961    /// Create a new client with additional headers
962    /// # Errors
963    pub fn new_with_headers(base_url: &str, additional_headers: HeaderMap) -> Result<Self, Error> {
964        let mut client = Self::new(base_url)?;
965        let mut headers = Self::default_http_headers();
966
967        for (key, value) in additional_headers {
968            headers.insert(key.ok_or(Error::InvalidResponse)?, value);
969        }
970
971        let http_client = Arc::new(
972            HttpClientBuilder::default()
973                .set_headers(headers)
974                .build(base_url)?,
975        );
976
977        client.http_client = http_client;
978        Ok(client)
979    }
980
981    fn default_http_headers() -> HeaderMap {
982        let mut headers = HeaderMap::new();
983        headers.insert("X-Client-Name", unsafe {
984            "rs-stellar-rpc-client".parse().unwrap_unchecked()
985        });
986        let version = VERSION.unwrap_or("devel");
987        headers.insert("X-Client-Version", unsafe {
988            version.parse().unwrap_unchecked()
989        });
990
991        headers
992    }
993
994    #[must_use]
995    pub fn base_url(&self) -> &str {
996        &self.base_url
997    }
998
999    #[must_use]
1000    pub fn client(&self) -> &HttpClient {
1001        &self.http_client
1002    }
1003
1004    ///
1005    /// # Errors
1006    pub async fn friendbot_url(&self) -> Result<String, Error> {
1007        let network = self.get_network().await?;
1008        network.friendbot_url.ok_or_else(|| {
1009            Error::NotFound(
1010                "Friendbot".to_string(),
1011                "Friendbot is not available on this network".to_string(),
1012            )
1013        })
1014    }
1015    ///
1016    /// # Errors
1017    pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
1018        let server = self.get_network().await?.passphrase;
1019
1020        if let Some(expected) = expected {
1021            if expected != server {
1022                return Err(Error::InvalidNetworkPassphrase {
1023                    expected: expected.to_string(),
1024                    server,
1025                });
1026            }
1027        }
1028
1029        Ok(server)
1030    }
1031
1032    ///
1033    /// # Errors
1034    pub async fn get_network(&self) -> Result<GetNetworkResponse, Error> {
1035        Ok(self
1036            .client()
1037            .request("getNetwork", ObjectParams::new())
1038            .await?)
1039    }
1040
1041    ///
1042    /// # Errors
1043    pub async fn get_health(&self) -> Result<GetHealthResponse, Error> {
1044        Ok(self
1045            .client()
1046            .request("getHealth", ObjectParams::new())
1047            .await?)
1048    }
1049
1050    ///
1051    /// # Errors
1052    pub async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse, Error> {
1053        Ok(self
1054            .client()
1055            .request("getLatestLedger", ObjectParams::new())
1056            .await?)
1057    }
1058
1059    ///
1060    /// # Errors
1061    pub async fn get_ledgers(
1062        &self,
1063        start: LedgerStart,
1064        limit: Option<usize>,
1065        format: Option<String>,
1066    ) -> Result<GetLedgersResponse, Error> {
1067        let mut oparams = ObjectParams::new();
1068
1069        let mut pagination = serde_json::Map::new();
1070        if let Some(limit) = limit {
1071            pagination.insert("limit".to_string(), limit.into());
1072        }
1073
1074        match start {
1075            LedgerStart::Ledger(l) => oparams.insert("startLedger", l)?,
1076            LedgerStart::Cursor(c) => {
1077                pagination.insert("cursor".to_string(), c.into());
1078            }
1079        }
1080
1081        oparams.insert("pagination", pagination)?;
1082
1083        if let Some(f) = format {
1084            oparams.insert("xdrFormat", f)?;
1085        }
1086
1087        Ok(self.client().request("getLedgers", oparams).await?)
1088    }
1089
1090    ///
1091    /// # Errors
1092    pub async fn get_account(&self, address: &str) -> Result<AccountEntry, Error> {
1093        let key = LedgerKey::Account(LedgerKeyAccount {
1094            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
1095                stellar_strkey::ed25519::PublicKey::from_string(address)?.0,
1096            ))),
1097        });
1098        let keys = Vec::from([key]);
1099        let response = self.get_ledger_entries(&keys).await?;
1100        let entries = response.entries.unwrap_or_default();
1101
1102        if entries.is_empty() {
1103            return Err(Error::NotFound("Account".to_string(), address.to_owned()));
1104        }
1105
1106        let ledger_entry = &entries[0];
1107        let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none());
1108
1109        if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? {
1110            Ok(entry)
1111        } else {
1112            Err(Error::InvalidResponse)
1113        }
1114    }
1115
1116    /// Get network fee stats
1117    /// # Errors
1118    pub async fn get_fee_stats(&self) -> Result<GetFeeStatsResponse, Error> {
1119        Ok(self
1120            .client()
1121            .request("getFeeStats", ObjectParams::new())
1122            .await?)
1123    }
1124
1125    ///
1126    /// # Errors
1127    pub async fn get_version_info(&self) -> Result<GetVersionInfoResponse, Error> {
1128        Ok(self
1129            .client()
1130            .request("getVersionInfo", ObjectParams::new())
1131            .await?)
1132    }
1133
1134    /// Send a transaction to the network and get back the hash of the transaction.
1135    /// # Errors
1136    pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<Hash, Error> {
1137        let mut oparams = ObjectParams::new();
1138        oparams.insert("transaction", tx.to_xdr_base64(Limits::none())?)?;
1139        let SendTransactionResponse {
1140            hash,
1141            error_result_xdr,
1142            status,
1143            ..
1144        } = self
1145            .client()
1146            .request("sendTransaction", oparams)
1147            .await
1148            .map_err(|err| {
1149                Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}"))
1150            })?;
1151
1152        if status == "ERROR" {
1153            let error = error_result_xdr
1154                .ok_or(Error::MissingError)
1155                .and_then(|x| {
1156                    TransactionResult::read_xdr_base64(&mut Limited::new(
1157                        x.as_bytes(),
1158                        Limits::none(),
1159                    ))
1160                    .map_err(|_| Error::InvalidResponse)
1161                })
1162                .map(|r| r.result)?;
1163
1164            return Err(Error::TransactionSubmissionFailed(format!("{error:#?}")));
1165        }
1166
1167        Ok(Hash::from_str(&hash)?)
1168    }
1169
1170    ///
1171    /// # Errors
1172    pub async fn send_transaction_polling(
1173        &self,
1174        tx: &TransactionEnvelope,
1175    ) -> Result<GetTransactionResponse, Error> {
1176        let hash = self.send_transaction(tx).await?;
1177        self.get_transaction_polling(&hash, None).await
1178    }
1179
1180    ///
1181    /// # Errors
1182    pub async fn simulate_transaction_envelope(
1183        &self,
1184        tx: &TransactionEnvelope,
1185        auth_mode: Option<AuthMode>,
1186    ) -> Result<SimulateTransactionResponse, Error> {
1187        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1188        let mut params = ObjectParams::new();
1189
1190        params.insert("transaction", base64_tx)?;
1191
1192        match auth_mode {
1193            Some(AuthMode::Enforce) => {
1194                params.insert("authMode", "enforce")?;
1195            }
1196            Some(AuthMode::Record) => {
1197                params.insert("authMode", "record")?;
1198            }
1199            Some(AuthMode::RecordAllowNonRoot) => {
1200                params.insert("authMode", "record_allow_nonroot")?;
1201            }
1202            None => {}
1203        }
1204
1205        let sim_res = self.client().request("simulateTransaction", params).await?;
1206
1207        Ok(sim_res)
1208    }
1209
1210    /// Internal function, not to be used.
1211    /// # Errors
1212    pub async fn next_simulate_transaction_envelope(
1213        &self,
1214        tx: &TransactionEnvelope,
1215        auth_mode: Option<AuthMode>,
1216        resource_config: Option<ResourceConfig>,
1217    ) -> Result<SimulateTransactionResponse, Error> {
1218        let base64_tx = tx.to_xdr_base64(Limits::none())?;
1219        let mut params = ObjectParams::new();
1220
1221        params.insert("transaction", base64_tx)?;
1222
1223        match auth_mode {
1224            Some(AuthMode::Enforce) => {
1225                params.insert("authMode", "enforce")?;
1226            }
1227            Some(AuthMode::Record) => {
1228                params.insert("authMode", "record")?;
1229            }
1230            Some(AuthMode::RecordAllowNonRoot) => {
1231                params.insert("authMode", "record_allow_nonroot")?;
1232            }
1233            None => {}
1234        }
1235
1236        if let Some(ref config) = resource_config {
1237            let mut resource_config_params = ObjectParams::new();
1238            resource_config_params.insert("instructionLeeway", config.instruction_leeway)?;
1239            params.insert("resourceConfig", resource_config)?;
1240        }
1241
1242        let sim_res = self.client().request("simulateTransaction", params).await?;
1243
1244        Ok(sim_res)
1245    }
1246
1247    ///
1248    /// # Errors
1249    pub async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse, Error> {
1250        let mut oparams = ObjectParams::new();
1251        oparams.insert("hash", tx_id)?;
1252        let resp: GetTransactionResponseRaw =
1253            self.client().request("getTransaction", oparams).await?;
1254
1255        Ok(resp.try_into()?)
1256    }
1257
1258    ///
1259    /// # Errors
1260    pub async fn get_transactions(
1261        &self,
1262        request: GetTransactionsRequest,
1263    ) -> Result<GetTransactionsResponse, Error> {
1264        let mut oparams = ObjectParams::new();
1265
1266        if let Some(start_ledger) = request.start_ledger {
1267            oparams.insert("startLedger", start_ledger)?;
1268        }
1269
1270        if let Some(pagination_params) = request.pagination {
1271            let pagination = serde_json::json!(pagination_params);
1272            oparams.insert("pagination", pagination)?;
1273        }
1274
1275        let resp: GetTransactionsResponseRaw =
1276            self.client().request("getTransactions", oparams).await?;
1277
1278        Ok(resp.try_into()?)
1279    }
1280
1281    /// Poll the transaction status. Can provide a timeout in seconds, otherwise uses the default timeout.
1282    ///
1283    /// It uses exponential backoff with a base of 1 second and a maximum of 30 seconds.
1284    ///
1285    /// # Errors
1286    /// - `Error::TransactionSubmissionTimeout` if the transaction status is not found within the timeout
1287    /// - `Error::TransactionSubmissionFailed` if the transaction status is "FAILED"
1288    /// - `Error::UnexpectedTransactionStatus` if the transaction status is not one of "SUCCESS", "FAILED", or ``NOT_FOUND``
1289    /// - `json_rpsee` Errors
1290    pub async fn get_transaction_polling(
1291        &self,
1292        tx_id: &Hash,
1293        timeout_s: Option<Duration>,
1294    ) -> Result<GetTransactionResponse, Error> {
1295        // Poll the transaction status
1296        let start = Instant::now();
1297        let timeout = timeout_s.unwrap_or(Duration::from_secs(self.timeout_in_secs));
1298        // see https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=50731
1299        // Is optimimal exponent for expontial backoff
1300        let exponential_backoff: f64 = 1.0 / (1.0 - E.powf(-1.0));
1301        let mut sleep_time = Duration::from_secs(1);
1302        loop {
1303            let response = self.get_transaction(tx_id).await?;
1304            match response.status.as_str() {
1305                "SUCCESS" => return Ok(response),
1306
1307                "FAILED" => {
1308                    return Err(Error::TransactionSubmissionFailed(format!(
1309                        "{:#?}",
1310                        response.result
1311                    )))
1312                }
1313
1314                "NOT_FOUND" => (),
1315                _ => {
1316                    return Err(Error::UnexpectedTransactionStatus(response.status));
1317                }
1318            }
1319
1320            if start.elapsed() > timeout {
1321                return Err(Error::TransactionSubmissionTimeout);
1322            }
1323
1324            sleep(sleep_time).await;
1325            sleep_time = Duration::from_secs_f64(sleep_time.as_secs_f64() * exponential_backoff);
1326        }
1327    }
1328
1329    ///
1330    /// # Errors
1331    pub async fn get_ledger_entries(
1332        &self,
1333        keys: &[LedgerKey],
1334    ) -> Result<GetLedgerEntriesResponse, Error> {
1335        let mut base64_keys: Vec<String> = vec![];
1336
1337        for k in keys {
1338            let base64_result = k.to_xdr_base64(Limits::none());
1339            if base64_result.is_err() {
1340                return Err(Error::Xdr(XdrError::Invalid));
1341            }
1342            base64_keys.push(k.to_xdr_base64(Limits::none())?);
1343        }
1344
1345        let mut oparams = ObjectParams::new();
1346        oparams.insert("keys", base64_keys)?;
1347
1348        Ok(self.client().request("getLedgerEntries", oparams).await?)
1349    }
1350
1351    ///
1352    /// # Errors
1353    pub async fn get_full_ledger_entries(
1354        &self,
1355        ledger_keys: &[LedgerKey],
1356    ) -> Result<FullLedgerEntries, Error> {
1357        let keys = ledger_keys
1358            .iter()
1359            .filter(|key| !matches!(key, LedgerKey::Ttl(_)))
1360            .map(Clone::clone)
1361            .collect::<Vec<_>>();
1362        let GetLedgerEntriesResponse {
1363            entries,
1364            latest_ledger,
1365        } = self.get_ledger_entries(&keys).await?;
1366        let entries = entries
1367            .unwrap_or_default()
1368            .iter()
1369            .map(
1370                |LedgerEntryResult {
1371                     key,
1372                     xdr,
1373                     last_modified_ledger,
1374                     live_until_ledger_seq_ledger_seq,
1375                 }| {
1376                    Ok(FullLedgerEntry {
1377                        key: LedgerKey::from_xdr_base64(key, Limits::none())?,
1378                        val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?,
1379                        live_until_ledger_seq: *live_until_ledger_seq_ledger_seq,
1380                        last_modified_ledger: *last_modified_ledger,
1381                    })
1382                },
1383            )
1384            .collect::<Result<Vec<_>, Error>>()?;
1385        Ok(FullLedgerEntries {
1386            entries,
1387            latest_ledger,
1388        })
1389    }
1390
1391    ///
1392    /// # Errors
1393    pub async fn get_events(
1394        &self,
1395        start: EventStart,
1396        event_type: Option<EventType>,
1397        contract_ids: &[String],
1398        topics: &[TopicFilter],
1399        limit: Option<usize>,
1400    ) -> Result<GetEventsResponse, Error> {
1401        let mut filters = serde_json::Map::new();
1402
1403        event_type
1404            .and_then(|t| match t {
1405                EventType::All => None, // all is the default, so avoid incl. the param
1406                EventType::Contract => Some("contract"),
1407                EventType::System => Some("system"),
1408            })
1409            .map(|t| filters.insert("type".to_string(), t.into()));
1410
1411        filters.insert("topics".to_string(), topics.into());
1412        filters.insert("contractIds".to_string(), contract_ids.into());
1413
1414        let mut pagination = serde_json::Map::new();
1415        if let Some(limit) = limit {
1416            pagination.insert("limit".to_string(), limit.into());
1417        }
1418
1419        let mut oparams = ObjectParams::new();
1420        match start {
1421            EventStart::Ledger(l) => oparams.insert("startLedger", l)?,
1422            EventStart::LedgerRange(r) => {
1423                oparams.insert("startLedger", r.start())?;
1424                oparams.insert("endLedger", r.end())?;
1425            }
1426            EventStart::Cursor(c) => {
1427                pagination.insert("cursor".to_string(), c.into());
1428            }
1429        }
1430        oparams.insert("filters", vec![filters])?;
1431        oparams.insert("pagination", pagination)?;
1432
1433        Ok(self.client().request("getEvents", oparams).await?)
1434    }
1435
1436    ///
1437    /// # Errors
1438    pub async fn get_contract_data(
1439        &self,
1440        contract_id: &[u8; 32],
1441    ) -> Result<ContractDataEntry, Error> {
1442        // Get the contract from the network
1443        let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData {
1444            contract: xdr::ScAddress::Contract(ContractId(xdr::Hash(*contract_id))),
1445            key: xdr::ScVal::LedgerKeyContractInstance,
1446            durability: xdr::ContractDataDurability::Persistent,
1447        });
1448        let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
1449        let entries = contract_ref.entries.unwrap_or_default();
1450        if entries.is_empty() {
1451            let contract_address = stellar_strkey::Contract(*contract_id).to_string();
1452            return Err(Error::NotFound(
1453                "Contract".to_string(),
1454                contract_address.to_string(),
1455            ));
1456        }
1457        let contract_ref_entry = &entries[0];
1458        match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1459            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1460            scval => Err(Error::UnexpectedContractCodeDataType(Box::new(scval))),
1461        }
1462    }
1463
1464    ///
1465    /// # Errors
1466    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1467    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1468        match self.get_contract_data(contract_id).await? {
1469            xdr::ContractDataEntry {
1470                val:
1471                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1472                        executable: xdr::ContractExecutable::Wasm(hash),
1473                        ..
1474                    }),
1475                ..
1476            } => self.get_remote_wasm_from_hash(hash).await,
1477            scval => Err(Error::UnexpectedToken(Box::new(scval))),
1478        }
1479    }
1480
1481    ///
1482    /// # Errors
1483    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1484    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1485        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1486        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1487        let entries = contract_data.entries.unwrap_or_default();
1488        if entries.is_empty() {
1489            return Err(Error::NotFound(
1490                "Contract Code".to_string(),
1491                hex::encode(hash),
1492            ));
1493        }
1494        let contract_data_entry = &entries[0];
1495        match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1496            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1497            scval => Err(Error::UnexpectedContractCodeDataType(Box::new(scval))),
1498        }
1499    }
1500
1501    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1502    ///
1503    /// # Errors
1504    /// - Could fail to find contract or have a network error
1505    pub async fn get_contract_instance(
1506        &self,
1507        contract_id: &[u8; 32],
1508    ) -> Result<ScContractInstance, Error> {
1509        let contract_data = self.get_contract_data(contract_id).await?;
1510        match contract_data.val {
1511            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1512            scval => Err(Error::UnexpectedContractInstance(Box::new(scval))),
1513        }
1514    }
1515}
1516
1517pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1518    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1519    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1520    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1521    Ok((toid_part, start_index))
1522}
1523
1524fn deserialize_option_i64_from_string_or_number<'de, D>(
1525    deserializer: D,
1526) -> Result<Option<i64>, D::Error>
1527where
1528    D: serde::Deserializer<'de>,
1529{
1530    use serde::Deserialize;
1531
1532    #[derive(Deserialize)]
1533    #[serde(untagged)]
1534    enum StringOrNumber {
1535        String(String),
1536        Number(i64),
1537    }
1538
1539    match Option::<StringOrNumber>::deserialize(deserializer)? {
1540        None => Ok(None),
1541        Some(StringOrNumber::String(s)) => {
1542            s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
1543        }
1544        Some(StringOrNumber::Number(n)) => Ok(Some(n)),
1545    }
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550    use super::*;
1551    use std::env;
1552    use std::fs;
1553    use std::path::PathBuf;
1554
1555    // Determines whether or not a particular filter matches a topic based on
1556    // the same semantics as the RPC server:
1557    //
1558    //  - for an exact segment match, the filter is a base64-encoded ScVal
1559    //  - for a wildcard, single-segment match, the string "*" matches exactly
1560    //    one segment
1561    //  - for a wildcard, multi-segment match, the string "**" as the last
1562    //    element of the filter matches zero or more trailing segments
1563    //
1564    // [API Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx)
1565    // [Code Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203)
1566    fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
1567        if filter.is_empty() {
1568            return false;
1569        }
1570
1571        // "**" as the last filter element matches zero or more trailing segments.
1572        if let Some((last, prefix)) = filter.split_last() {
1573            if last == "**" {
1574                return topic.len() >= prefix.len()
1575                    && prefix
1576                        .iter()
1577                        .enumerate()
1578                        .all(|(i, s)| *s == "*" || topic[i] == *s);
1579            }
1580        }
1581
1582        filter.len() == topic.len()
1583            && filter
1584                .iter()
1585                .enumerate()
1586                .all(|(i, s)| *s == "*" || topic[i] == *s)
1587    }
1588
1589    fn get_repo_root() -> PathBuf {
1590        let mut path = env::current_exe().expect("Failed to get current executable path");
1591        // Navigate up the directory tree until we find the repository root
1592        while path.pop() {
1593            if path.join("Cargo.toml").exists() {
1594                return path;
1595            }
1596        }
1597        panic!("Could not find repository root");
1598    }
1599
1600    fn read_json_file(name: &str) -> String {
1601        let repo_root = get_repo_root();
1602        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1603        fs::read_to_string(fixture_path).unwrap_or_else(|_| panic!("Failed to read {name:?}"))
1604    }
1605
1606    #[test]
1607    fn simulation_transaction_response_parsing() {
1608        let s = r#"{
1609 "minResourceFee": "100000000",
1610 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1611 "transactionData": "",
1612 "latestLedger": 1234,
1613 "stateChanges": [{
1614    "type": "created",
1615    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1616    "before": null,
1617    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1618  }]
1619  }"#;
1620
1621        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1622        assert_eq!(
1623            resp.state_changes.unwrap()[0],
1624            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1625        );
1626        assert_eq!(resp.min_resource_fee, 100_000_000);
1627    }
1628
1629    #[test]
1630    fn simulation_transaction_response_parsing_mostly_empty() {
1631        let s = r#"{
1632 "latestLedger": 1234
1633        }"#;
1634
1635        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1636        assert_eq!(resp.latest_ledger, 1_234);
1637    }
1638
1639    #[test]
1640    fn test_parse_transaction_response_p23() {
1641        let response_content = read_json_file("transaction_response_p23.json");
1642        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1643            .expect("Failed to parse JSON from transaction_response_p23.json");
1644        let result = full_response["result"].clone();
1645        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1646            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1647        let response: GetTransactionResponse = raw_response
1648            .try_into()
1649            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1650
1651        assert_eq!(2, response.events.transaction_events.iter().len());
1652        assert_eq!(1, response.events.contract_events.len());
1653        assert_eq!(21, response.events.diagnostic_events.iter().len());
1654        assert_eq!(
1655            response.tx_hash.as_deref(),
1656            Some("bfe15f83ea850b7bf86fd7152f9074033f2aec2a045e40a8872ac56726a6e35c")
1657        );
1658        assert_eq!(response.created_at, Some(1_751_666_924));
1659        assert_eq!(response.application_order, Some(1));
1660        assert_eq!(response.fee_bump, Some(false));
1661    }
1662
1663    #[test]
1664    fn test_parse_transaction_response_p22() {
1665        let response_content = read_json_file("transaction_response_p22.json");
1666        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1667            .expect("Failed to parse JSON from transaction_response_p22.json");
1668        let result = full_response["result"].clone();
1669        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1670            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1671        let response: GetTransactionResponse = raw_response
1672            .try_into()
1673            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1674
1675        assert_eq!(23, response.events.diagnostic_events.iter().len());
1676        assert_eq!(
1677            response.tx_hash.as_deref(),
1678            Some("a738ccc7f8f457d4367b78c098569ebee23258c71f128d7a2c61585652345937")
1679        );
1680        assert_eq!(response.created_at, Some(1_751_747_980));
1681        assert_eq!(response.application_order, Some(1));
1682        assert_eq!(response.fee_bump, Some(false));
1683    }
1684
1685    #[test]
1686    fn test_parse_get_transactions_response() {
1687        let response_content = read_json_file("transactions_response.json");
1688
1689        // Parse the entire response
1690        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1691            .expect("Failed to parse JSON from transactions_response.json");
1692
1693        // Extract the "result" field
1694        let result = full_response["result"].clone();
1695        // Parse the "result" content as GetTransactionsResponseRaw
1696        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1697            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1698
1699        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1700        let response: GetTransactionsResponse = raw_response
1701            .try_into()
1702            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1703
1704        // Assertions
1705        assert_eq!(response.transactions.len(), 5);
1706        assert_eq!(response.latest_ledger, 556_962);
1707        assert_eq!(response.cursor, 2_379_420_471_922_689);
1708
1709        // Additional assertions for specific transaction attributes
1710        assert_eq!(response.transactions[0].status, "SUCCESS");
1711        //assert_eq!(response.transactions[0].application_order, 1);
1712        //assert_eq!(response.transactions[0].ledger, 554000);
1713    }
1714
1715    #[test]
1716    fn test_rpc_url_default_ports() {
1717        // Portless URLs are accepted; the underlying jsonrpsee transport
1718        // infers the default port from the scheme. The input is stored as-is.
1719        let client = Client::new("http://example.com").unwrap();
1720        assert_eq!(client.base_url(), "http://example.com");
1721        let client = Client::new("https://example.com").unwrap();
1722        assert_eq!(client.base_url(), "https://example.com");
1723
1724        // URLs with explicit ports work and are stored as-is.
1725        let client = Client::new("http://example.com:8080").unwrap();
1726        assert_eq!(client.base_url(), "http://example.com:8080");
1727        let client = Client::new("https://example.com:8080").unwrap();
1728        assert_eq!(client.base_url(), "https://example.com:8080");
1729
1730        // Paths are preserved verbatim.
1731        let client = Client::new("http://example.com/a/b/c").unwrap();
1732        assert_eq!(client.base_url(), "http://example.com/a/b/c");
1733        let client = Client::new("https://example.com/a/b/c/").unwrap();
1734        assert_eq!(client.base_url(), "https://example.com/a/b/c/");
1735    }
1736
1737    #[test]
1738    fn test_parse_events_response() {
1739        let response_content = read_json_file("events_response_p23.json");
1740        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1741            .expect("Failed to parse JSON from events_response_p23.json");
1742        let result = full_response["result"].clone();
1743
1744        // Deserialize
1745        let resp: GetEventsResponse = serde_json::from_value(result.clone())
1746            .expect("Failed to parse 'result' into GetEventsResponse");
1747
1748        // Verify specific field values from the fixture.
1749        assert_eq!(resp.events[0].operation_index, Some(0));
1750        assert_eq!(resp.events[0].transaction_index, Some(0));
1751        assert_eq!(
1752            resp.events[0].tx_hash.as_deref(),
1753            Some("e42da3c70c90cc319e2cfaa2f69a7bd04aefcc4159b12caa0df216fbb3ab43b4")
1754        );
1755        #[allow(deprecated)]
1756        {
1757            assert_eq!(resp.events[0].is_successful_contract_call, Some(true));
1758        }
1759
1760        // Re-serialize
1761        let reserialized = serde_json::to_value(&resp).expect("Failed to serialize response");
1762
1763        // Compare
1764        assert_eq!(
1765            result, reserialized,
1766            "Deserialization should preserve all data"
1767        );
1768    }
1769
1770    #[test]
1771    fn test_parse_events_response_p22() {
1772        // Ensure we can still deserialize Event from protocol 22 responses,
1773        // which do not include operationIndex or transactionIndex.
1774        let response_content = read_json_file("events_response_p22.json");
1775        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1776            .expect("Failed to parse JSON from events_response_p22.json");
1777        let first_event = full_response["result"]["events"][0].clone();
1778
1779        // Deserialize; this should succeed even though some fields are absent.
1780        let event: Event = serde_json::from_value(first_event)
1781            .expect("Failed to parse protocol 22 event into Event");
1782
1783        assert!(event.operation_index.is_none());
1784        assert!(event.transaction_index.is_none());
1785    }
1786
1787    #[test]
1788    fn test_ledger_range_valid() {
1789        let r = EventStart::ledger_range(10, 20).unwrap();
1790        assert_eq!(r, EventStart::ledger_range(10, 20).unwrap());
1791
1792        // equal start and end is valid
1793        assert!(EventStart::ledger_range(10, 10).is_ok());
1794    }
1795
1796    #[test]
1797    fn test_ledger_range_invalid() {
1798        let err = EventStart::ledger_range(100, 50).unwrap_err();
1799        assert!(err.contains("start (100)") && err.contains("end (50)"));
1800    }
1801
1802    #[test]
1803    // Taken from [RPC server
1804    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1805    fn test_does_topic_match() {
1806        struct TestCase<'a> {
1807            name: &'a str,
1808            filter: Vec<&'a str>,
1809            includes: Vec<Vec<&'a str>>,
1810            excludes: Vec<Vec<&'a str>>,
1811        }
1812
1813        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1814        let number = "AAAAAQB6Mcc=";
1815        let star = "*";
1816
1817        for tc in vec![
1818            // No filter means match nothing.
1819            TestCase {
1820                name: "<empty>",
1821                filter: vec![],
1822                includes: vec![],
1823                excludes: vec![vec![xfer]],
1824            },
1825            // "*" should match "transfer/" but not "transfer/transfer" or
1826            // "transfer/amount", because * is specified as a SINGLE segment
1827            // wildcard.
1828            TestCase {
1829                name: "*",
1830                filter: vec![star],
1831                includes: vec![vec![xfer]],
1832                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1833            },
1834            // "*/transfer" should match anything preceding "transfer", but
1835            // nothing that isn't exactly two segments long.
1836            TestCase {
1837                name: "*/transfer",
1838                filter: vec![star, xfer],
1839                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1840                excludes: vec![
1841                    vec![number],
1842                    vec![number, number],
1843                    vec![number, xfer, number],
1844                    vec![xfer],
1845                    vec![xfer, number],
1846                    vec![xfer, xfer, xfer],
1847                ],
1848            },
1849            // The inverse case of before: "transfer/*" should match any single
1850            // segment after a segment that is exactly "transfer", but no
1851            // additional segments.
1852            TestCase {
1853                name: "transfer/*",
1854                filter: vec![xfer, star],
1855                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1856                excludes: vec![
1857                    vec![number],
1858                    vec![number, number],
1859                    vec![number, xfer, number],
1860                    vec![xfer],
1861                    vec![number, xfer],
1862                    vec![xfer, xfer, xfer],
1863                ],
1864            },
1865            // Here, we extend to exactly two wild segments after transfer.
1866            TestCase {
1867                name: "transfer/*/*",
1868                filter: vec![xfer, star, star],
1869                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1870                excludes: vec![
1871                    vec![number],
1872                    vec![number, number],
1873                    vec![number, xfer],
1874                    vec![number, xfer, number, number],
1875                    vec![xfer],
1876                    vec![xfer, xfer, xfer, xfer],
1877                ],
1878            },
1879            // Here, we ensure wildcards can be in the middle of a filter: only
1880            // exact matches happen on the ends, while the middle can be
1881            // anything.
1882            TestCase {
1883                name: "transfer/*/number",
1884                filter: vec![xfer, star, number],
1885                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1886                excludes: vec![
1887                    vec![number],
1888                    vec![number, number],
1889                    vec![number, number, number],
1890                    vec![number, xfer, number],
1891                    vec![xfer],
1892                    vec![number, xfer],
1893                    vec![xfer, xfer, xfer],
1894                    vec![xfer, number, xfer],
1895                ],
1896            },
1897            // "**" as the sole filter element matches any topic (0+ segments).
1898            TestCase {
1899                name: "**",
1900                filter: vec!["**"],
1901                includes: vec![
1902                    vec![],
1903                    vec![xfer],
1904                    vec![xfer, number],
1905                    vec![xfer, number, number],
1906                ],
1907                excludes: vec![],
1908            },
1909            // "transfer/**" matches "transfer" followed by 0+ segments.
1910            TestCase {
1911                name: "transfer/**",
1912                filter: vec![xfer, "**"],
1913                includes: vec![
1914                    vec![xfer],
1915                    vec![xfer, number],
1916                    vec![xfer, number, number],
1917                    vec![xfer, xfer, xfer],
1918                ],
1919                excludes: vec![
1920                    vec![],
1921                    vec![number],
1922                    vec![number, xfer],
1923                    vec![number, number],
1924                ],
1925            },
1926            // "transfer/number/**" matches exactly "transfer/number" followed
1927            // by 0+ segments.
1928            TestCase {
1929                name: "transfer/number/**",
1930                filter: vec![xfer, number, "**"],
1931                includes: vec![
1932                    vec![xfer, number],
1933                    vec![xfer, number, number],
1934                    vec![xfer, number, xfer, number],
1935                ],
1936                excludes: vec![
1937                    vec![],
1938                    vec![xfer],
1939                    vec![number],
1940                    vec![number, xfer],
1941                    vec![xfer, xfer],
1942                ],
1943            },
1944        ] {
1945            for topic in tc.includes {
1946                assert!(
1947                    does_topic_match(
1948                        &topic
1949                            .iter()
1950                            .map(std::string::ToString::to_string)
1951                            .collect::<Vec<String>>(),
1952                        &tc.filter
1953                            .iter()
1954                            .map(std::string::ToString::to_string)
1955                            .collect::<Vec<String>>()
1956                    ),
1957                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
1958                    tc.name,
1959                    topic,
1960                    tc.filter
1961                );
1962            }
1963
1964            for topic in tc.excludes {
1965                assert!(
1966                    !does_topic_match(
1967                        // make deep copies of the vecs
1968                        &topic
1969                            .iter()
1970                            .map(std::string::ToString::to_string)
1971                            .collect::<Vec<String>>(),
1972                        &tc.filter
1973                            .iter()
1974                            .map(std::string::ToString::to_string)
1975                            .collect::<Vec<String>>()
1976                    ),
1977                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
1978                    tc.name,
1979                    topic,
1980                    tc.filter
1981                );
1982            }
1983        }
1984    }
1985}