fedimint_walletv2_common/
config.rs1use std::collections::BTreeMap;
2
3use bitcoin::Network;
4use bitcoin::hashes::{Hash, sha256};
5use fedimint_core::config::ExcessiveRelativeFeeError;
6use fedimint_core::core::ModuleKind;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::{Amount, PeerId, plugin_types_trait_impl_config, weight_to_vbytes};
9use secp256k1::{PublicKey, SecretKey};
10use serde::{Deserialize, Serialize};
11
12use crate::{WalletCommonInit, descriptor};
13
14plugin_types_trait_impl_config!(
15 WalletCommonInit,
16 WalletConfig,
17 WalletConfigPrivate,
18 WalletConfigConsensus,
19 WalletClientConfig
20);
21
22#[derive(Clone, Debug, Serialize, Deserialize)]
23pub struct WalletConfig {
24 pub private: WalletConfigPrivate,
25 pub consensus: WalletConfigConsensus,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct WalletConfigPrivate {
30 pub bitcoin_sk: SecretKey,
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
34pub struct WalletConfigConsensus {
35 pub bitcoin_pks: BTreeMap<PeerId, PublicKey>,
37 pub descriptor: WalletDescriptor,
39 pub send_tx_vbytes: u64,
41 pub receive_tx_vbytes: u64,
43 pub feerate_base: u64,
46 pub dust_limit: bitcoin::Amount,
48 pub fee_consensus: FeeConsensus,
50 pub network: Network,
52}
53
54impl WalletConfigConsensus {
55 pub fn new(
79 bitcoin_pks: BTreeMap<PeerId, PublicKey>,
80 fee_consensus: FeeConsensus,
81 network: Network,
82 ) -> Self {
83 let tx_overhead_weight = 4 * 4 + 1 + 1 + 4 + 4 + 4 * 4; let change_witness_weight = descriptor(&bitcoin_pks, &sha256::Hash::all_zeros())
91 .max_weight_to_satisfy()
92 .expect("Cannot satisfy the change descriptor.")
93 .to_wu();
94
95 let change_input_weight = 32 * 4 + 4 * 4 + 4 + 4 * 4 + change_witness_weight;
100
101 let change_output_weight = 8 * 4 + 4 + 34 * 4; let destination_output_weight = 8 * 4 + 4 + 34 * 4; Self {
110 bitcoin_pks,
111 descriptor: WalletDescriptor::Wsh,
112 send_tx_vbytes: weight_to_vbytes(
113 tx_overhead_weight
114 + change_input_weight
115 + change_output_weight
116 + destination_output_weight,
117 ),
118 receive_tx_vbytes: weight_to_vbytes(
119 tx_overhead_weight
120 + change_input_weight
121 + change_input_weight
122 + change_output_weight,
123 ),
124 feerate_base: 250,
129 dust_limit: bitcoin::Amount::from_sat(10_000),
130 fee_consensus,
131 network,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
137pub struct FeeConsensus {
138 pub base: Amount,
139 pub parts_per_million: u64,
140}
141
142impl FeeConsensus {
143 pub fn new(parts_per_million: u64) -> Result<Self, ExcessiveRelativeFeeError> {
154 const MAX_PARTS_PER_MILLION: u64 = 10_000;
155
156 if parts_per_million > MAX_PARTS_PER_MILLION {
157 return Err(ExcessiveRelativeFeeError {
158 parts_per_million,
159 max: MAX_PARTS_PER_MILLION,
160 });
161 }
162
163 Ok(Self {
164 base: Amount::from_sats(100),
165 parts_per_million,
166 })
167 }
168
169 pub fn fee(&self, amount: Amount) -> Amount {
170 Amount::from_msats(self.fee_msats(amount.msats))
171 }
172
173 fn fee_msats(&self, msats: u64) -> u64 {
174 msats
175 .saturating_mul(self.parts_per_million)
176 .saturating_div(1_000_000)
177 .checked_add(self.base.msats)
178 .expect("The division creates sufficient headroom to add the base fee")
179 }
180}
181
182#[test]
183fn a_relative_fee_over_the_limit_is_rejected() {
184 let err: ExcessiveRelativeFeeError =
185 FeeConsensus::new(10_001).expect_err("A fee over one percent is excessive");
186
187 assert_eq!(err.parts_per_million, 10_001);
188 assert_eq!(err.max, 10_000);
189}
190
191#[test]
192fn test_fee_consensus() {
193 let fee_consensus = FeeConsensus::new(10_000).expect("Relative fee is within range");
194
195 assert_eq!(
196 fee_consensus.fee(Amount::from_msats(99)),
197 Amount::from_sats(100)
198 );
199
200 assert_eq!(
201 fee_consensus.fee(Amount::from_sats(1)),
202 Amount::from_msats(10) + Amount::from_sats(100)
203 );
204
205 assert_eq!(
206 fee_consensus.fee(Amount::from_sats(1000)),
207 Amount::from_sats(10) + Amount::from_sats(100)
208 );
209
210 assert_eq!(
211 fee_consensus.fee(Amount::from_bitcoins(1)),
212 Amount::from_sats(1_000_000) + Amount::from_sats(100)
213 );
214
215 assert_eq!(
216 fee_consensus.fee(Amount::from_bitcoins(10_000)),
217 Amount::from_bitcoins(100) + Amount::from_sats(100)
218 );
219}
220
221#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
224pub enum WalletDescriptor {
225 Wsh,
226}
227
228#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
229pub struct WalletClientConfig {
230 pub bitcoin_pks: BTreeMap<PeerId, PublicKey>,
232 pub descriptor: WalletDescriptor,
234 pub send_tx_vbytes: u64,
236 pub receive_tx_vbytes: u64,
238 pub feerate_base: u64,
241 pub dust_limit: bitcoin::Amount,
243 pub fee_consensus: FeeConsensus,
245 pub network: Network,
247}
248
249impl std::fmt::Display for WalletClientConfig {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 write!(f, "WalletClientConfig {self:?}")
252 }
253}