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