use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::error::WalletError;
use crate::types::Chain;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockHeader {
pub version: u32,
pub previous_hash: String,
pub merkle_root: String,
pub time: u32,
pub bits: u32,
pub nonce: u32,
pub height: u32,
pub hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetMerklePathResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merkle_path: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<BlockHeader>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetRawTxResult {
pub txid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_tx: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostTxResultForTxid {
pub txid: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub already_known: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub double_spend: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_height: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub competing_txs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_error: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub orphan_mempool: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostBeefResult {
pub name: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub txid_results: Vec<PostTxResultForTxid>,
}
impl PostBeefResult {
pub fn timeout(provider_name: &str, txids: &[String], timeout_ms: u64) -> Self {
PostBeefResult {
name: provider_name.to_string(),
status: "error".to_string(),
error: Some(format!("Service timeout after {}ms", timeout_ms)),
txid_results: txids
.iter()
.map(|txid| PostTxResultForTxid {
txid: txid.clone(),
status: "error".to_string(),
already_known: None,
double_spend: None,
block_hash: None,
block_height: None,
competing_txs: None,
service_error: Some(true),
orphan_mempool: None,
})
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PostBeefMode {
UntilSuccess,
PromiseAll,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum GetUtxoStatusOutputFormat {
#[serde(rename = "hashLE")]
HashLE,
#[serde(rename = "hashBE")]
HashBE,
#[serde(rename = "script")]
Script,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetUtxoStatusDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub txid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub satoshis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetUtxoStatusResult {
pub name: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_utxo: Option<bool>,
pub details: Vec<GetUtxoStatusDetails>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatusForTxidResult {
pub txid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub depth: Option<u32>,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetStatusForTxidsResult {
pub name: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub results: Vec<StatusForTxidResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScriptHashHistoryEntry {
pub txid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetScriptHashHistoryResult {
pub name: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub history: Vec<ScriptHashHistoryEntry>,
}
pub const MAX_CALL_HISTORY: usize = 32;
pub const MAX_RESET_COUNTS: usize = 32;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceCallError {
pub message: String,
pub code: String,
}
impl ServiceCallError {
pub fn from_wallet_error(err: &WalletError) -> Self {
ServiceCallError {
message: err.to_string(),
code: err.code().to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceCall {
pub when: DateTime<Utc>,
pub msecs: i64,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ServiceCallError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceCallHistoryCounts {
pub success: u32,
pub failure: u32,
pub error: u32,
pub since: DateTime<Utc>,
pub until: DateTime<Utc>,
}
impl ServiceCallHistoryCounts {
pub fn new_now() -> Self {
let now = Utc::now();
Self {
success: 0,
failure: 0,
error: 0,
since: now,
until: now,
}
}
pub fn new_at(since: DateTime<Utc>) -> Self {
Self {
success: 0,
failure: 0,
error: 0,
since,
until: since,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderCallHistory {
pub service_name: String,
pub provider_name: String,
pub calls: Vec<ServiceCall>,
pub total_counts: ServiceCallHistoryCounts,
pub reset_counts: Vec<ServiceCallHistoryCounts>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceCallHistory {
pub service_name: String,
pub history_by_provider: HashMap<String, ProviderCallHistory>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServicesCallHistory {
pub services: Vec<ServiceCallHistory>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiatExchangeRates {
pub timestamp: DateTime<Utc>,
pub base: String,
pub rates: HashMap<String, f64>,
}
impl Default for FiatExchangeRates {
fn default() -> Self {
Self {
timestamp: Utc::now(),
base: "USD".to_string(),
rates: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BsvExchangeRate {
pub timestamp: DateTime<Utc>,
pub rate_usd: f64,
}
impl Default for BsvExchangeRate {
fn default() -> Self {
Self {
timestamp: Utc::now(),
rate_usd: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArcSseEvent {
pub txid: String,
pub tx_status: String,
pub timestamp: String,
}
pub enum NLockTimeInput {
Raw(u32),
Transaction(bsv::transaction::Transaction),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArcConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
pub deployment_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub callback_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_client: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<std::collections::HashMap<String, String>>,
}
fn default_deployment_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let pid = std::process::id() as u128;
let mixed = nanos.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(pid);
format!("rust-sdk-{:016x}", mixed as u64)
}
impl Default for ArcConfig {
fn default() -> Self {
Self {
api_key: None,
deployment_id: default_deployment_id(),
callback_url: None,
callback_token: None,
http_client: None,
headers: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServicesConfig {
pub chain: Chain,
pub arc_url: String,
pub arc_config: ArcConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub arc_gorilla_pool_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arc_gorilla_pool_config: Option<ArcConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub whats_on_chain_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bitails_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaintracks_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaintracks_fiat_exchange_rates_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exchangeratesapi_key: Option<String>,
pub bsv_exchange_rate: BsvExchangeRate,
pub bsv_update_msecs: u64,
pub fiat_exchange_rates: FiatExchangeRates,
pub fiat_update_msecs: u64,
pub post_beef_soft_timeout_ms: u64,
pub post_beef_soft_timeout_per_kb_ms: u64,
pub post_beef_soft_timeout_max_ms: u64,
}
impl From<Chain> for ServicesConfig {
fn from(chain: Chain) -> Self {
let (arc_url, gorilla_pool_url, chaintracks_url) = match chain {
Chain::Main => (
"https://arc.taal.com".to_string(),
Some("https://arc.gorillapool.io".to_string()),
"https://mainnet-chaintracks.babbage.systems".to_string(),
),
Chain::Test => (
"https://arc-test.taal.com".to_string(),
None,
"https://testnet-chaintracks.babbage.systems".to_string(),
),
};
let gorilla_pool_config = gorilla_pool_url.as_ref().map(|_| ArcConfig::default());
Self {
chain,
arc_url,
arc_config: ArcConfig::default(),
arc_gorilla_pool_url: gorilla_pool_url,
arc_gorilla_pool_config: gorilla_pool_config,
whats_on_chain_api_key: None,
bitails_api_key: None,
chaintracks_url: Some(chaintracks_url),
chaintracks_fiat_exchange_rates_url: None,
exchangeratesapi_key: None,
bsv_exchange_rate: BsvExchangeRate::default(),
bsv_update_msecs: 15 * 60 * 1000, fiat_exchange_rates: FiatExchangeRates::default(),
fiat_update_msecs: 24 * 60 * 60 * 1000, post_beef_soft_timeout_ms: 5000,
post_beef_soft_timeout_per_kb_ms: 50,
post_beef_soft_timeout_max_ms: 30000,
}
}
}
impl ServicesConfig {
pub fn get_post_beef_soft_timeout_ms(&self, beef_bytes_len: usize) -> u64 {
let base_ms = self.post_beef_soft_timeout_ms;
let per_kb_ms = self.post_beef_soft_timeout_per_kb_ms;
let max_ms = self.post_beef_soft_timeout_max_ms.max(base_ms);
if per_kb_ms == 0 {
return base_ms.min(max_ms);
}
let extra_ms = ((beef_bytes_len as f64 / 1024.0) * per_kb_ms as f64).ceil() as u64;
(base_ms + extra_ms).min(max_ms)
}
}