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