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