1#![deny(clippy::pedantic, clippy::nursery)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::cast_precision_loss)]
5#![allow(clippy::cast_sign_loss)]
6#![allow(clippy::cognitive_complexity)]
7#![allow(clippy::doc_markdown)]
8#![allow(clippy::future_not_send)]
9#![allow(clippy::missing_const_for_fn)]
10#![allow(clippy::missing_errors_doc)]
11#![allow(clippy::missing_panics_doc)]
12#![allow(clippy::module_name_repetitions)]
13#![allow(clippy::must_use_candidate)]
14#![allow(clippy::needless_lifetimes)]
15#![allow(clippy::redundant_pub_crate)]
16#![allow(clippy::return_self_not_must_use)]
17#![allow(clippy::similar_names)]
18#![allow(clippy::transmute_ptr_to_ptr)]
19#![allow(clippy::unsafe_derive_deserialize)]
20
21extern crate self as fedimint_core;
40
41use std::fmt::{self, Debug};
42use std::io::Error;
43use std::str::FromStr;
44
45pub use amount::*;
46pub use anyhow;
48use bitcoin::address::NetworkUnchecked;
49pub use bitcoin::hashes::Hash as BitcoinHash;
50use bitcoin::{Address, Network};
51use envs::BitcoinRpcConfig;
52use lightning::util::ser::Writeable;
53use lightning_types::features::Bolt11InvoiceFeatures;
54pub use macro_rules_attribute::apply;
55pub use peer_id::*;
56use serde::{Deserialize, Serialize};
57use thiserror::Error;
58pub use tiered::Tiered;
59pub use tiered_multi::*;
60use util::SafeUrl;
61pub use {bitcoin, hex, secp256k1};
62
63use crate::encoding::{Decodable, DecodeError, Encodable};
64use crate::module::registry::ModuleDecoderRegistry;
65
66pub mod admin_client;
68mod amount;
70pub mod backup;
72pub mod bls12_381_serde;
74pub mod config;
76pub mod core;
78pub mod db;
80pub mod encoding;
82pub mod endpoint_constants;
83pub mod envs;
85pub mod epoch;
86pub mod fmt_utils;
88pub mod invite_code;
90pub mod iroh_prod;
91pub mod log;
92#[macro_use]
94pub mod macros;
95pub mod base32;
97pub mod module;
99pub mod net;
101mod peer_id;
103pub mod runtime;
105pub mod rustls;
107pub mod setup_code;
109pub mod task;
111pub mod tiered;
113pub mod tiered_multi;
115pub mod time;
117pub mod timing;
119pub mod transaction;
121pub mod txoproof;
123pub mod util;
125pub mod version;
127
128pub mod session_outcome;
130
131mod txid {
135 use bitcoin::hashes::hash_newtype;
136 use bitcoin::hashes::sha256::Hash as Sha256;
137
138 hash_newtype!(
139 pub struct TransactionId(Sha256);
141 );
142}
143pub use txid::TransactionId;
144
145#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone, Serialize, Deserialize)]
147#[serde(rename_all = "snake_case")]
148pub enum BitcoinAmountOrAll {
149 All,
150 #[serde(untagged)]
151 Amount(#[serde(with = "bitcoin::amount::serde::as_sat")] bitcoin::Amount),
152}
153
154impl std::fmt::Display for BitcoinAmountOrAll {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 Self::All => write!(f, "all"),
158 Self::Amount(amount) => write!(f, "{amount}"),
159 }
160 }
161}
162
163impl FromStr for BitcoinAmountOrAll {
164 type Err = anyhow::Error;
165
166 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
167 if s == "all" {
168 Ok(Self::All)
169 } else {
170 let amount = Amount::from_str(s)?;
171 Ok(Self::Amount(amount.try_into()?))
172 }
173 }
174}
175
176#[derive(
180 Debug,
181 Clone,
182 Copy,
183 Eq,
184 PartialEq,
185 PartialOrd,
186 Ord,
187 Hash,
188 Deserialize,
189 Serialize,
190 Encodable,
191 Decodable,
192)]
193pub struct InPoint {
194 pub txid: TransactionId,
196 pub in_idx: u64,
199}
200
201impl std::fmt::Display for InPoint {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "{}:{}", self.txid, self.in_idx)
204 }
205}
206
207#[derive(
211 Debug,
212 Clone,
213 Copy,
214 Eq,
215 PartialEq,
216 PartialOrd,
217 Ord,
218 Hash,
219 Deserialize,
220 Serialize,
221 Encodable,
222 Decodable,
223)]
224pub struct OutPoint {
225 pub txid: TransactionId,
227 pub out_idx: u64,
230}
231
232impl std::fmt::Display for OutPoint {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 write!(f, "{}:{}", self.txid, self.out_idx)
235 }
236}
237
238impl Encodable for TransactionId {
239 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
240 let bytes = &self[..];
241 writer.write_all(bytes)?;
242 Ok(())
243 }
244}
245
246impl Decodable for TransactionId {
247 fn consensus_decode_partial<D: std::io::Read>(
248 d: &mut D,
249 _modules: &ModuleDecoderRegistry,
250 ) -> Result<Self, DecodeError> {
251 let mut bytes = [0u8; 32];
252 d.read_exact(&mut bytes).map_err(DecodeError::from_err)?;
253 Ok(Self::from_byte_array(bytes))
254 }
255}
256
257#[derive(
258 Copy,
259 Clone,
260 Debug,
261 PartialEq,
262 Ord,
263 PartialOrd,
264 Eq,
265 Hash,
266 Serialize,
267 Deserialize,
268 Encodable,
269 Decodable,
270)]
271pub struct Feerate {
272 pub sats_per_kvb: u64,
273}
274
275impl fmt::Display for Feerate {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 f.write_fmt(format_args!("{}sat/kvb", self.sats_per_kvb))
278 }
279}
280
281impl Feerate {
282 pub fn calculate_fee(&self, weight: u64) -> bitcoin::Amount {
283 let sats = weight_to_vbytes(weight) * self.sats_per_kvb / 1000;
284 bitcoin::Amount::from_sat(sats)
285 }
286}
287
288const WITNESS_SCALE_FACTOR: u64 = bitcoin::constants::WITNESS_SCALE_FACTOR as u64;
289
290pub fn weight_to_vbytes(weight: u64) -> u64 {
295 weight.div_ceil(WITNESS_SCALE_FACTOR)
296}
297
298#[derive(Debug, Error)]
299pub enum CoreError {
300 #[error("Mismatching outcome variant: expected {0}, got {1}")]
301 MismatchingVariant(&'static str, &'static str),
302}
303
304pub fn encode_bolt11_invoice_features_without_length(features: &Bolt11InvoiceFeatures) -> Vec<u8> {
310 let mut feature_bytes = vec![];
311 for f in features.le_flags().iter().rev() {
312 f.write(&mut feature_bytes)
313 .expect("Writing to byte vec can't fail");
314 }
315 feature_bytes
316}
317
318pub fn format_hex(data: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
323 let prec = f.precision().unwrap_or(2 * data.len());
324 let width = f.width().unwrap_or(2 * data.len());
325 for _ in (2 * data.len())..width {
326 f.write_str("0")?;
327 }
328 for ch in data.iter().take(prec / 2) {
329 write!(f, "{:02x}", *ch)?;
330 }
331 if prec < 2 * data.len() && prec % 2 == 1 {
332 write!(f, "{:x}", data[prec / 2] / 16)?;
333 }
334 Ok(())
335}
336
337pub fn get_network_for_address(address: &Address<NetworkUnchecked>) -> Network {
350 if address.is_valid_for_network(Network::Bitcoin) {
351 Network::Bitcoin
352 } else if address.is_valid_for_network(Network::Testnet) {
353 Network::Testnet
354 } else if address.is_valid_for_network(Network::Regtest) {
355 Network::Regtest
356 } else {
357 panic!("Address is not valid for any network");
358 }
359}
360
361pub fn default_esplora_server(network: Network, port: Option<String>) -> BitcoinRpcConfig {
363 BitcoinRpcConfig {
364 kind: "esplora".to_string(),
365 url: match network {
366 Network::Bitcoin => SafeUrl::parse("https://mempool.space/api/"),
367 Network::Testnet => SafeUrl::parse("https://mempool.space/testnet/api/"),
368 Network::Testnet4 => SafeUrl::parse("https://mempool.space/testnet4/api/"),
369 Network::Signet => SafeUrl::parse("https://mutinynet.com/api/"),
370 Network::Regtest => SafeUrl::parse(&format!(
371 "http://127.0.0.1:{}/",
372 port.unwrap_or_else(|| String::from("50002"))
373 )),
374 _ => panic!("Failed to parse default esplora server"),
375 }
376 .expect("Failed to parse default esplora server"),
377 }
378}
379
380#[cfg(test)]
381mod tests;