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