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::ClientError),
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(
1484 "Contract".to_string(),
1485 contract_address.to_string(),
1486 ));
1487 }
1488 let contract_ref_entry = &entries[0];
1489 match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? {
1490 LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1491 scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1492 }
1493 }
1494
1495 #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1498 pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1499 match self.get_contract_data(contract_id).await? {
1500 xdr::ContractDataEntry {
1501 val:
1502 xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1503 executable: xdr::ContractExecutable::Wasm(hash),
1504 ..
1505 }),
1506 ..
1507 } => self.get_remote_wasm_from_hash(hash).await,
1508 scval => Err(Error::UnexpectedToken(scval)),
1509 }
1510 }
1511
1512 #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1515 pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1516 let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1517 let contract_data = self.get_ledger_entries(&[code_key]).await?;
1518 let entries = contract_data.entries.unwrap_or_default();
1519 if entries.is_empty() {
1520 return Err(Error::NotFound(
1521 "Contract Code".to_string(),
1522 hex::encode(hash),
1523 ));
1524 }
1525 let contract_data_entry = &entries[0];
1526 match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? {
1527 LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1528 scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1529 }
1530 }
1531
1532 pub async fn get_contract_instance(
1537 &self,
1538 contract_id: &[u8; 32],
1539 ) -> Result<ScContractInstance, Error> {
1540 let contract_data = self.get_contract_data(contract_id).await?;
1541 match contract_data.val {
1542 xdr::ScVal::ContractInstance(instance) => Ok(instance),
1543 scval => Err(Error::UnexpectedContractInstance(scval)),
1544 }
1545 }
1546}
1547
1548pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1549 let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1550 let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1551 let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1552 Ok((toid_part, start_index))
1553}
1554
1555fn deserialize_option_i64_from_string_or_number<'de, D>(
1556 deserializer: D,
1557) -> Result<Option<i64>, D::Error>
1558where
1559 D: serde::Deserializer<'de>,
1560{
1561 use serde::Deserialize;
1562
1563 #[derive(Deserialize)]
1564 #[serde(untagged)]
1565 enum StringOrNumber {
1566 String(String),
1567 Number(i64),
1568 }
1569
1570 match Option::<StringOrNumber>::deserialize(deserializer)? {
1571 None => Ok(None),
1572 Some(StringOrNumber::String(s)) => {
1573 s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
1574 }
1575 Some(StringOrNumber::Number(n)) => Ok(Some(n)),
1576 }
1577}
1578
1579#[cfg(test)]
1580mod tests {
1581 use super::*;
1582 use std::env;
1583 use std::fs;
1584 use std::path::PathBuf;
1585
1586 fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
1598 if filter.is_empty() {
1599 return false;
1600 }
1601
1602 if let Some((last, prefix)) = filter.split_last() {
1604 if last == "**" {
1605 return topic.len() >= prefix.len()
1606 && prefix
1607 .iter()
1608 .enumerate()
1609 .all(|(i, s)| *s == "*" || topic[i] == *s);
1610 }
1611 }
1612
1613 filter.len() == topic.len()
1614 && filter
1615 .iter()
1616 .enumerate()
1617 .all(|(i, s)| *s == "*" || topic[i] == *s)
1618 }
1619
1620 fn get_repo_root() -> PathBuf {
1621 let mut path = env::current_exe().expect("Failed to get current executable path");
1622 while path.pop() {
1624 if path.join("Cargo.toml").exists() {
1625 return path;
1626 }
1627 }
1628 panic!("Could not find repository root");
1629 }
1630
1631 fn read_json_file(name: &str) -> String {
1632 let repo_root = get_repo_root();
1633 let fixture_path = repo_root.join("src").join("fixtures").join(name);
1634 fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1635 }
1636
1637 #[test]
1638 fn simulation_transaction_response_parsing() {
1639 let s = r#"{
1640 "minResourceFee": "100000000",
1641 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1642 "transactionData": "",
1643 "latestLedger": 1234,
1644 "stateChanges": [{
1645 "type": "created",
1646 "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1647 "before": null,
1648 "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1649 }]
1650 }"#;
1651
1652 let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1653 assert_eq!(
1654 resp.state_changes.unwrap()[0],
1655 LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1656 );
1657 assert_eq!(resp.min_resource_fee, 100_000_000);
1658 }
1659
1660 #[test]
1661 fn simulation_transaction_response_parsing_mostly_empty() {
1662 let s = r#"{
1663 "latestLedger": 1234
1664 }"#;
1665
1666 let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1667 assert_eq!(resp.latest_ledger, 1_234);
1668 }
1669
1670 #[test]
1671 fn test_parse_transaction_response_p23() {
1672 let response_content = read_json_file("transaction_response_p23.json");
1673 let full_response: serde_json::Value = serde_json::from_str(&response_content)
1674 .expect("Failed to parse JSON from transaction_response_p23.json");
1675 let result = full_response["result"].clone();
1676 let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1677 .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1678 let response: GetTransactionResponse = raw_response
1679 .try_into()
1680 .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1681
1682 assert_eq!(2, response.events.transaction_events.iter().len());
1683 assert_eq!(1, response.events.contract_events.len());
1684 assert_eq!(21, response.events.diagnostic_events.iter().len());
1685 assert_eq!(
1686 response.tx_hash.as_deref(),
1687 Some("bfe15f83ea850b7bf86fd7152f9074033f2aec2a045e40a8872ac56726a6e35c")
1688 );
1689 assert_eq!(response.created_at, Some(1_751_666_924));
1690 assert_eq!(response.application_order, Some(1));
1691 assert_eq!(response.fee_bump, Some(false));
1692 }
1693
1694 #[test]
1695 fn test_parse_transaction_response_p22() {
1696 let response_content = read_json_file("transaction_response_p22.json");
1697 let full_response: serde_json::Value = serde_json::from_str(&response_content)
1698 .expect("Failed to parse JSON from transaction_response_p22.json");
1699 let result = full_response["result"].clone();
1700 let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1701 .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1702 let response: GetTransactionResponse = raw_response
1703 .try_into()
1704 .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1705
1706 assert_eq!(23, response.events.diagnostic_events.iter().len());
1707 assert_eq!(
1708 response.tx_hash.as_deref(),
1709 Some("a738ccc7f8f457d4367b78c098569ebee23258c71f128d7a2c61585652345937")
1710 );
1711 assert_eq!(response.created_at, Some(1_751_747_980));
1712 assert_eq!(response.application_order, Some(1));
1713 assert_eq!(response.fee_bump, Some(false));
1714 }
1715
1716 #[test]
1717 fn test_parse_get_transactions_response() {
1718 let response_content = read_json_file("transactions_response.json");
1719
1720 let full_response: serde_json::Value = serde_json::from_str(&response_content)
1722 .expect("Failed to parse JSON from transactions_response.json");
1723
1724 let result = full_response["result"].clone();
1726 let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1728 .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1729
1730 let response: GetTransactionsResponse = raw_response
1732 .try_into()
1733 .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1734
1735 assert_eq!(response.transactions.len(), 5);
1737 assert_eq!(response.latest_ledger, 556_962);
1738 assert_eq!(response.cursor, 2_379_420_471_922_689);
1739
1740 assert_eq!(response.transactions[0].status, "SUCCESS");
1742 }
1745
1746 #[test]
1747 fn test_rpc_url_default_ports() {
1748 let client = Client::new("http://example.com").unwrap();
1750 assert_eq!(client.base_url(), "http://example.com:80/");
1751 let client = Client::new("https://example.com").unwrap();
1752 assert_eq!(client.base_url(), "https://example.com:443/");
1753
1754 let client = Client::new("http://example.com:8080").unwrap();
1756 assert_eq!(client.base_url(), "http://example.com:8080/");
1757 let client = Client::new("https://example.com:8080").unwrap();
1758 assert_eq!(client.base_url(), "https://example.com:8080/");
1759
1760 let client = Client::new("http://example.com/a/b/c").unwrap();
1762 assert_eq!(client.base_url(), "http://example.com:80/a/b/c");
1763 let client = Client::new("https://example.com/a/b/c").unwrap();
1764 assert_eq!(client.base_url(), "https://example.com:443/a/b/c");
1765 let client = Client::new("http://example.com/a/b/c/").unwrap();
1766 assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1767 let client = Client::new("https://example.com/a/b/c/").unwrap();
1768 assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1769 let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1770 assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1771 let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1772 assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1773 }
1774
1775 #[test]
1776 fn test_parse_events_response() {
1777 let response_content = read_json_file("events_response_p23.json");
1778 let full_response: serde_json::Value = serde_json::from_str(&response_content)
1779 .expect("Failed to parse JSON from events_response_p23.json");
1780 let result = full_response["result"].clone();
1781
1782 let resp: GetEventsResponse = serde_json::from_value(result.clone())
1784 .expect("Failed to parse 'result' into GetEventsResponse");
1785
1786 assert_eq!(resp.events[0].operation_index, Some(0));
1788 assert_eq!(resp.events[0].transaction_index, Some(0));
1789 assert_eq!(
1790 resp.events[0].tx_hash.as_deref(),
1791 Some("e42da3c70c90cc319e2cfaa2f69a7bd04aefcc4159b12caa0df216fbb3ab43b4")
1792 );
1793 #[allow(deprecated)]
1794 {
1795 assert_eq!(resp.events[0].is_successful_contract_call, Some(true));
1796 }
1797
1798 let reserialized = serde_json::to_value(&resp).expect("Failed to serialize response");
1800
1801 assert_eq!(
1803 result, reserialized,
1804 "Deserialization should preserve all data"
1805 );
1806 }
1807
1808 #[test]
1809 fn test_parse_events_response_p22() {
1810 let response_content = read_json_file("events_response_p22.json");
1813 let full_response: serde_json::Value = serde_json::from_str(&response_content)
1814 .expect("Failed to parse JSON from events_response_p22.json");
1815 let first_event = full_response["result"]["events"][0].clone();
1816
1817 let event: Event = serde_json::from_value(first_event)
1819 .expect("Failed to parse protocol 22 event into Event");
1820
1821 assert!(event.operation_index.is_none());
1822 assert!(event.transaction_index.is_none());
1823 }
1824
1825 #[test]
1826 fn test_ledger_range_valid() {
1827 let r = EventStart::ledger_range(10, 20).unwrap();
1828 assert_eq!(r, EventStart::ledger_range(10, 20).unwrap());
1829
1830 assert!(EventStart::ledger_range(10, 10).is_ok());
1832 }
1833
1834 #[test]
1835 fn test_ledger_range_invalid() {
1836 let err = EventStart::ledger_range(100, 50).unwrap_err();
1837 assert!(err.contains("start (100)") && err.contains("end (50)"));
1838 }
1839
1840 #[test]
1841 fn test_does_topic_match() {
1844 struct TestCase<'a> {
1845 name: &'a str,
1846 filter: Vec<&'a str>,
1847 includes: Vec<Vec<&'a str>>,
1848 excludes: Vec<Vec<&'a str>>,
1849 }
1850
1851 let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1852 let number = "AAAAAQB6Mcc=";
1853 let star = "*";
1854
1855 for tc in vec![
1856 TestCase {
1858 name: "<empty>",
1859 filter: vec![],
1860 includes: vec![],
1861 excludes: vec![vec![xfer]],
1862 },
1863 TestCase {
1867 name: "*",
1868 filter: vec![star],
1869 includes: vec![vec![xfer]],
1870 excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1871 },
1872 TestCase {
1875 name: "*/transfer",
1876 filter: vec![star, xfer],
1877 includes: vec![vec![number, xfer], vec![xfer, xfer]],
1878 excludes: vec![
1879 vec![number],
1880 vec![number, number],
1881 vec![number, xfer, number],
1882 vec![xfer],
1883 vec![xfer, number],
1884 vec![xfer, xfer, xfer],
1885 ],
1886 },
1887 TestCase {
1891 name: "transfer/*",
1892 filter: vec![xfer, star],
1893 includes: vec![vec![xfer, number], vec![xfer, xfer]],
1894 excludes: vec![
1895 vec![number],
1896 vec![number, number],
1897 vec![number, xfer, number],
1898 vec![xfer],
1899 vec![number, xfer],
1900 vec![xfer, xfer, xfer],
1901 ],
1902 },
1903 TestCase {
1905 name: "transfer/*/*",
1906 filter: vec![xfer, star, star],
1907 includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1908 excludes: vec![
1909 vec![number],
1910 vec![number, number],
1911 vec![number, xfer],
1912 vec![number, xfer, number, number],
1913 vec![xfer],
1914 vec![xfer, xfer, xfer, xfer],
1915 ],
1916 },
1917 TestCase {
1921 name: "transfer/*/number",
1922 filter: vec![xfer, star, number],
1923 includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1924 excludes: vec![
1925 vec![number],
1926 vec![number, number],
1927 vec![number, number, number],
1928 vec![number, xfer, number],
1929 vec![xfer],
1930 vec![number, xfer],
1931 vec![xfer, xfer, xfer],
1932 vec![xfer, number, xfer],
1933 ],
1934 },
1935 TestCase {
1937 name: "**",
1938 filter: vec!["**"],
1939 includes: vec![
1940 vec![],
1941 vec![xfer],
1942 vec![xfer, number],
1943 vec![xfer, number, number],
1944 ],
1945 excludes: vec![],
1946 },
1947 TestCase {
1949 name: "transfer/**",
1950 filter: vec![xfer, "**"],
1951 includes: vec![
1952 vec![xfer],
1953 vec![xfer, number],
1954 vec![xfer, number, number],
1955 vec![xfer, xfer, xfer],
1956 ],
1957 excludes: vec![
1958 vec![],
1959 vec![number],
1960 vec![number, xfer],
1961 vec![number, number],
1962 ],
1963 },
1964 TestCase {
1967 name: "transfer/number/**",
1968 filter: vec![xfer, number, "**"],
1969 includes: vec![
1970 vec![xfer, number],
1971 vec![xfer, number, number],
1972 vec![xfer, number, xfer, number],
1973 ],
1974 excludes: vec![
1975 vec![],
1976 vec![xfer],
1977 vec![number],
1978 vec![number, xfer],
1979 vec![xfer, xfer],
1980 ],
1981 },
1982 ] {
1983 for topic in tc.includes {
1984 assert!(
1985 does_topic_match(
1986 &topic
1987 .iter()
1988 .map(std::string::ToString::to_string)
1989 .collect::<Vec<String>>(),
1990 &tc.filter
1991 .iter()
1992 .map(std::string::ToString::to_string)
1993 .collect::<Vec<String>>()
1994 ),
1995 "test: {}, topic ({:?}) should be matched by filter ({:?})",
1996 tc.name,
1997 topic,
1998 tc.filter
1999 );
2000 }
2001
2002 for topic in tc.excludes {
2003 assert!(
2004 !does_topic_match(
2005 &topic
2007 .iter()
2008 .map(std::string::ToString::to_string)
2009 .collect::<Vec<String>>(),
2010 &tc.filter
2011 .iter()
2012 .map(std::string::ToString::to_string)
2013 .collect::<Vec<String>>()
2014 ),
2015 "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
2016 tc.name,
2017 topic,
2018 tc.filter
2019 );
2020 }
2021 }
2022 }
2023}