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