1use std::collections::HashMap;
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use crate::error::WalletError;
11use crate::types::Chain;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct BlockHeader {
25 pub version: u32,
27 pub previous_hash: String,
29 pub merkle_root: String,
31 pub time: u32,
33 pub bits: u32,
35 pub nonce: u32,
37 pub height: u32,
39 pub hash: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
49#[serde(rename_all = "camelCase")]
50pub struct GetMerklePathResult {
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub name: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub merkle_path: Option<Vec<u8>>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub header: Option<BlockHeader>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub error: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, Default)]
71#[serde(rename_all = "camelCase")]
72pub struct GetRawTxResult {
73 pub txid: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub name: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub raw_tx: Option<Vec<u8>>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub error: Option<String>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct PostTxResultForTxid {
94 pub txid: String,
96 pub status: String,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub already_known: Option<bool>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub double_spend: Option<bool>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub block_hash: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub block_height: Option<u32>,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub competing_txs: Option<Vec<String>>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub service_error: Option<bool>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct PostBeefResult {
122 pub name: String,
124 pub status: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub error: Option<String>,
129 pub txid_results: Vec<PostTxResultForTxid>,
131}
132
133impl PostBeefResult {
134 pub fn timeout(provider_name: &str, txids: &[String], timeout_ms: u64) -> Self {
136 PostBeefResult {
137 name: provider_name.to_string(),
138 status: "error".to_string(),
139 error: Some(format!("Service timeout after {}ms", timeout_ms)),
140 txid_results: txids
141 .iter()
142 .map(|txid| PostTxResultForTxid {
143 txid: txid.clone(),
144 status: "error".to_string(),
145 already_known: None,
146 double_spend: None,
147 block_hash: None,
148 block_height: None,
149 competing_txs: None,
150 service_error: Some(true),
151 })
152 .collect(),
153 }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub enum PostBeefMode {
161 UntilSuccess,
163 PromiseAll,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub enum GetUtxoStatusOutputFormat {
174 #[serde(rename = "hashLE")]
176 HashLE,
177 #[serde(rename = "hashBE")]
179 HashBE,
180 #[serde(rename = "script")]
182 Script,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct GetUtxoStatusDetails {
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub txid: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub index: Option<u32>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub satoshis: Option<u64>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub height: Option<u32>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct GetUtxoStatusResult {
207 pub name: String,
209 pub status: String,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub error: Option<String>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub is_utxo: Option<bool>,
217 pub details: Vec<GetUtxoStatusDetails>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct StatusForTxidResult {
229 pub txid: String,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub depth: Option<u32>,
234 pub status: String,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct GetStatusForTxidsResult {
242 pub name: String,
244 pub status: String,
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub error: Option<String>,
249 pub results: Vec<StatusForTxidResult>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct ScriptHashHistoryEntry {
261 pub txid: String,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub height: Option<u32>,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct GetScriptHashHistoryResult {
272 pub name: String,
274 pub status: String,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub error: Option<String>,
279 pub history: Vec<ScriptHashHistoryEntry>,
281}
282
283pub const MAX_CALL_HISTORY: usize = 32;
289
290pub const MAX_RESET_COUNTS: usize = 32;
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct ServiceCallError {
297 pub message: String,
299 pub code: String,
301}
302
303impl ServiceCallError {
304 pub fn from_wallet_error(err: &WalletError) -> Self {
306 ServiceCallError {
307 message: err.to_string(),
308 code: err.code().to_string(),
309 }
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315#[serde(rename_all = "camelCase")]
316pub struct ServiceCall {
317 pub when: DateTime<Utc>,
319 pub msecs: i64,
321 pub success: bool,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub result: Option<String>,
326 #[serde(skip_serializing_if = "Option::is_none")]
328 pub error: Option<ServiceCallError>,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334pub struct ServiceCallHistoryCounts {
335 pub success: u32,
337 pub failure: u32,
339 pub error: u32,
341 pub since: DateTime<Utc>,
343 pub until: DateTime<Utc>,
345}
346
347impl ServiceCallHistoryCounts {
348 pub fn new_now() -> Self {
350 let now = Utc::now();
351 Self {
352 success: 0,
353 failure: 0,
354 error: 0,
355 since: now,
356 until: now,
357 }
358 }
359
360 pub fn new_at(since: DateTime<Utc>) -> Self {
362 Self {
363 success: 0,
364 failure: 0,
365 error: 0,
366 since,
367 until: since,
368 }
369 }
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374#[serde(rename_all = "camelCase")]
375pub struct ProviderCallHistory {
376 pub service_name: String,
378 pub provider_name: String,
380 pub calls: Vec<ServiceCall>,
382 pub total_counts: ServiceCallHistoryCounts,
384 pub reset_counts: Vec<ServiceCallHistoryCounts>,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
391#[serde(rename_all = "camelCase")]
392pub struct ServiceCallHistory {
393 pub service_name: String,
395 pub history_by_provider: HashMap<String, ProviderCallHistory>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct ServicesCallHistory {
403 pub services: Vec<ServiceCallHistory>,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
413#[serde(rename_all = "camelCase")]
414pub struct FiatExchangeRates {
415 pub timestamp: DateTime<Utc>,
417 pub base: String,
419 pub rates: HashMap<String, f64>,
421}
422
423impl Default for FiatExchangeRates {
424 fn default() -> Self {
425 Self {
426 timestamp: Utc::now(),
427 base: "USD".to_string(),
428 rates: HashMap::new(),
429 }
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435#[serde(rename_all = "camelCase")]
436pub struct BsvExchangeRate {
437 pub timestamp: DateTime<Utc>,
439 pub rate_usd: f64,
441}
442
443impl Default for BsvExchangeRate {
444 fn default() -> Self {
445 Self {
446 timestamp: Utc::now(),
447 rate_usd: 0.0,
448 }
449 }
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct ArcSseEvent {
460 pub txid: String,
462 pub tx_status: String,
464 pub timestamp: String,
466}
467
468pub enum NLockTimeInput {
474 Raw(u32),
476 Transaction(bsv::transaction::Transaction),
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct ArcConfig {
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub api_key: Option<String>,
491 pub deployment_id: String,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub callback_url: Option<String>,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub callback_token: Option<String>,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub http_client: Option<String>,
502}
503
504impl Default for ArcConfig {
505 fn default() -> Self {
506 Self {
507 api_key: None,
508 deployment_id: "wallet-toolbox".to_string(),
509 callback_url: None,
510 callback_token: None,
511 http_client: None,
512 }
513 }
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct ServicesConfig {
520 pub chain: Chain,
522 pub arc_url: String,
524 pub arc_config: ArcConfig,
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub arc_gorilla_pool_url: Option<String>,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub arc_gorilla_pool_config: Option<ArcConfig>,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub whats_on_chain_api_key: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none")]
537 pub bitails_api_key: Option<String>,
538 #[serde(skip_serializing_if = "Option::is_none")]
540 pub chaintracks_url: Option<String>,
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub chaintracks_fiat_exchange_rates_url: Option<String>,
544 #[serde(skip_serializing_if = "Option::is_none")]
546 pub exchangeratesapi_key: Option<String>,
547 pub bsv_exchange_rate: BsvExchangeRate,
549 pub bsv_update_msecs: u64,
551 pub fiat_exchange_rates: FiatExchangeRates,
553 pub fiat_update_msecs: u64,
555 pub post_beef_soft_timeout_ms: u64,
557 pub post_beef_soft_timeout_per_kb_ms: u64,
559 pub post_beef_soft_timeout_max_ms: u64,
561}
562
563impl From<Chain> for ServicesConfig {
564 fn from(chain: Chain) -> Self {
565 let (arc_url, gorilla_pool_url, chaintracks_url) = match chain {
566 Chain::Main => (
567 "https://arc.taal.com".to_string(),
568 Some("https://arc.gorillapool.io".to_string()),
569 "https://mainnet-chaintracks.babbage.systems".to_string(),
570 ),
571 Chain::Test => (
572 "https://arc-test.taal.com".to_string(),
573 None,
574 "https://testnet-chaintracks.babbage.systems".to_string(),
575 ),
576 };
577
578 let gorilla_pool_config = gorilla_pool_url.as_ref().map(|_| ArcConfig::default());
579
580 Self {
581 chain,
582 arc_url,
583 arc_config: ArcConfig::default(),
584 arc_gorilla_pool_url: gorilla_pool_url,
585 arc_gorilla_pool_config: gorilla_pool_config,
586 whats_on_chain_api_key: None,
587 bitails_api_key: None,
588 chaintracks_url: Some(chaintracks_url),
589 chaintracks_fiat_exchange_rates_url: None,
590 exchangeratesapi_key: None,
591 bsv_exchange_rate: BsvExchangeRate::default(),
592 bsv_update_msecs: 15 * 60 * 1000, fiat_exchange_rates: FiatExchangeRates::default(),
594 fiat_update_msecs: 24 * 60 * 60 * 1000, post_beef_soft_timeout_ms: 5000,
596 post_beef_soft_timeout_per_kb_ms: 50,
597 post_beef_soft_timeout_max_ms: 30000,
598 }
599 }
600}
601
602impl ServicesConfig {
603 pub fn get_post_beef_soft_timeout_ms(&self, beef_bytes_len: usize) -> u64 {
605 let base_ms = self.post_beef_soft_timeout_ms;
606 let per_kb_ms = self.post_beef_soft_timeout_per_kb_ms;
607 let max_ms = self.post_beef_soft_timeout_max_ms.max(base_ms);
608 if per_kb_ms == 0 {
609 return base_ms.min(max_ms);
610 }
611 let extra_ms = ((beef_bytes_len as f64 / 1024.0) * per_kb_ms as f64).ceil() as u64;
612 (base_ms + extra_ms).min(max_ms)
613 }
614}