fedimint_wallet_client/
api.rs1use anyhow::anyhow;
2use bitcoin::{Address, Amount};
3use fedimint_api_client::api::{
4 FederationApiExt, FederationError, FederationResult, IModuleFederationApi, PeerResult,
5};
6use fedimint_api_client::query::FilterMapThreshold;
7use fedimint_core::envs::BitcoinRpcConfig;
8use fedimint_core::module::{ApiAuth, ApiRequestErased, ModuleConsensusVersion};
9use fedimint_core::task::{MaybeSend, MaybeSync};
10use fedimint_core::{NumPeersExt, PeerId, apply, async_trait_maybe_send};
11use fedimint_wallet_common::endpoint_constants::{
12 ACTIVATE_CONSENSUS_VERSION_VOTING_ENDPOINT, BITCOIN_KIND_ENDPOINT, BITCOIN_RPC_CONFIG_ENDPOINT,
13 BLOCK_COUNT_ENDPOINT, BLOCK_COUNT_LOCAL_ENDPOINT, MODULE_CONSENSUS_VERSION_ENDPOINT,
14 PEG_OUT_FEES_ENDPOINT, UTXO_CONFIRMED_ENDPOINT, WALLET_SUMMARY_ENDPOINT,
15};
16use fedimint_wallet_common::{PegOutFees, WalletSummary};
17
18#[apply(async_trait_maybe_send!)]
19pub trait WalletFederationApi {
20 async fn module_consensus_version(&self) -> FederationResult<ModuleConsensusVersion>;
21
22 async fn fetch_consensus_block_count(&self) -> FederationResult<u64>;
23
24 async fn fetch_peg_out_fees(
25 &self,
26 address: &Address,
27 amount: Amount,
28 ) -> FederationResult<Option<PegOutFees>>;
29
30 async fn fetch_bitcoin_rpc_kind(&self, peer_id: PeerId) -> FederationResult<String>;
31
32 async fn fetch_bitcoin_rpc_config(&self, auth: ApiAuth) -> FederationResult<BitcoinRpcConfig>;
33
34 async fn fetch_wallet_summary(&self) -> FederationResult<WalletSummary>;
35
36 async fn fetch_block_count_local(&self) -> FederationResult<u32>;
37
38 async fn is_utxo_confirmed(&self, outpoint: bitcoin::OutPoint) -> FederationResult<bool>;
39
40 async fn activate_consensus_version_voting(&self, auth: ApiAuth) -> FederationResult<()>;
41}
42
43#[apply(async_trait_maybe_send!)]
44impl<T: ?Sized> WalletFederationApi for T
45where
46 T: IModuleFederationApi + MaybeSend + MaybeSync + 'static,
47{
48 async fn module_consensus_version(&self) -> FederationResult<ModuleConsensusVersion> {
49 let response = self
50 .request_current_consensus(
51 MODULE_CONSENSUS_VERSION_ENDPOINT.to_string(),
52 ApiRequestErased::default(),
53 )
54 .await;
55
56 if let Err(e) = &response
57 && e.any_peer_error_method_not_found()
58 {
59 return Ok(ModuleConsensusVersion::new(2, 0));
60 }
61
62 response
63 }
64
65 async fn is_utxo_confirmed(&self, outpoint: bitcoin::OutPoint) -> FederationResult<bool> {
66 let res = self
67 .request_current_consensus(
68 UTXO_CONFIRMED_ENDPOINT.to_string(),
69 ApiRequestErased::new(outpoint),
70 )
71 .await;
72
73 if let Err(e) = &res
74 && e.any_peer_error_method_not_found()
75 {
76 return Ok(false);
77 }
78
79 res
80 }
81
82 async fn fetch_consensus_block_count(&self) -> FederationResult<u64> {
83 self.request_current_consensus(
84 BLOCK_COUNT_ENDPOINT.to_string(),
85 ApiRequestErased::default(),
86 )
87 .await
88 }
89
90 async fn fetch_block_count_local(&self) -> FederationResult<u32> {
91 let filter_map = |_peer: PeerId, block_count: Option<u32>| -> PeerResult<Option<u32>> {
92 Ok(block_count)
93 };
94
95 let block_count_responses = self
96 .request_with_strategy(
97 FilterMapThreshold::<Option<u32>, Option<u32>>::new(
98 filter_map,
99 self.all_peers().to_num_peers().threshold().into(),
100 ),
101 BLOCK_COUNT_LOCAL_ENDPOINT.to_string(),
102 ApiRequestErased::default(),
103 )
104 .await?;
105
106 let mut response: Vec<u32> = block_count_responses.into_values().flatten().collect();
107
108 if response.is_empty() {
109 return Err(FederationError::general(
110 BLOCK_COUNT_LOCAL_ENDPOINT.to_string(),
111 ApiRequestErased::default(),
112 anyhow!("No valid block counts received"),
113 ));
114 }
115
116 response.sort_unstable();
117 let final_block_count = response[response.len() / 2];
118
119 Ok(final_block_count)
120 }
121
122 async fn fetch_peg_out_fees(
123 &self,
124 address: &Address,
125 amount: Amount,
126 ) -> FederationResult<Option<PegOutFees>> {
127 self.request_current_consensus(
128 PEG_OUT_FEES_ENDPOINT.to_string(),
129 ApiRequestErased::new((address, amount.to_sat())),
130 )
131 .await
132 }
133
134 async fn fetch_bitcoin_rpc_kind(&self, peer_id: PeerId) -> FederationResult<String> {
135 self.request_single_peer_federation(
136 BITCOIN_KIND_ENDPOINT.to_string(),
137 ApiRequestErased::default(),
138 peer_id,
139 )
140 .await
141 }
142
143 async fn fetch_bitcoin_rpc_config(&self, auth: ApiAuth) -> FederationResult<BitcoinRpcConfig> {
144 self.request_admin(
145 BITCOIN_RPC_CONFIG_ENDPOINT,
146 ApiRequestErased::default(),
147 auth,
148 )
149 .await
150 }
151
152 async fn fetch_wallet_summary(&self) -> FederationResult<WalletSummary> {
153 self.request_current_consensus(
154 WALLET_SUMMARY_ENDPOINT.to_string(),
155 ApiRequestErased::default(),
156 )
157 .await
158 }
159
160 async fn activate_consensus_version_voting(&self, auth: ApiAuth) -> FederationResult<()> {
161 self.request_admin(
162 ACTIVATE_CONSENSUS_VERSION_VOTING_ENDPOINT,
163 ApiRequestErased::default(),
164 auth,
165 )
166 .await
167 }
168}