Skip to content

Commit cea366b

Browse files
authored
Merge pull request #43 from nodetec/leave_federation
chore: users can leave federations
2 parents f5b0860 + 2d83f73 commit cea366b

File tree

2 files changed

+101
-3
lines changed

2 files changed

+101
-3
lines changed

src/fedimint.rs

+38-2
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl Wallet {
175175

176176
pub async fn connect_to_joined_federations(&self) -> anyhow::Result<()> {
177177
// Note: We're intentionally locking the clients mutex earlier than
178-
// necessary so that the lock is held while we're reading the data directory.
178+
// necessary so that the lock is held while we're accessing the data directory.
179179
let mut clients = self.clients.lock().await;
180180

181181
// List all files in the data directory.
@@ -217,7 +217,7 @@ impl Wallet {
217217

218218
pub async fn join_federation(&self, invite_code: InviteCode) -> anyhow::Result<()> {
219219
// Note: We're intentionally locking the clients mutex earlier than
220-
// necessary so that the lock is held while we're reading the data directory.
220+
// necessary so that the lock is held while we're accessing the data directory.
221221
let mut clients = self.clients.lock().await;
222222

223223
let federation_id = invite_code.federation_id();
@@ -242,6 +242,42 @@ impl Wallet {
242242
Ok(())
243243
}
244244

245+
// TODO: Call `ClientModule::leave()` for every module.
246+
// https://docs.rs/fedimint-client/0.4.2/fedimint_client/module/trait.ClientModule.html#method.leave
247+
// Currently it isn't implemented for the `LightningClientModule`, so for now we're just checking
248+
// that the client has a zero balance.
249+
pub async fn leave_federation(&self, federation_id: FederationId) -> anyhow::Result<()> {
250+
// Note: We're intentionally locking the clients mutex earlier than
251+
// necessary so that the lock is held while we're accessing the data directory.
252+
let mut clients = self.clients.lock().await;
253+
254+
if let Some(client) = clients.remove(&federation_id) {
255+
if client.get_balance().await.msats != 0 {
256+
// Re-insert the client back into the clients map.
257+
clients.insert(federation_id, client);
258+
259+
return Err(anyhow::anyhow!(
260+
"Cannot leave federation with non-zero balance: {}",
261+
federation_id
262+
));
263+
}
264+
265+
client.shutdown().await;
266+
267+
let federation_data_dir = self
268+
.fedimint_clients_data_dir
269+
.join(federation_id.to_string());
270+
271+
if federation_data_dir.is_dir() {
272+
std::fs::remove_dir_all(federation_data_dir)?;
273+
}
274+
}
275+
276+
self.force_update_view(clients).await;
277+
278+
Ok(())
279+
}
280+
245281
/// Constructs the current view of the wallet.
246282
/// SHOULD ONLY BE CALLED FROM THE `view_update_task`.
247283
/// This way, `view_update_task` can only yield values

src/routes/bitcoin_wallet.rs

+63-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::str::FromStr;
22

33
use fedimint_core::{
4-
config::{ClientConfig, META_FEDERATION_NAME_KEY},
4+
config::{ClientConfig, FederationId, META_FEDERATION_NAME_KEY},
55
invite_code::InviteCode,
66
Amount,
77
};
@@ -42,6 +42,9 @@ pub enum Message {
4242
JoinFederation(InviteCode),
4343
JoinedFederation(InviteCode),
4444

45+
LeaveFederation(FederationId),
46+
LeftFederation(FederationId),
47+
4548
Send(send::Message),
4649
Receive(receive::Message),
4750

@@ -196,6 +199,44 @@ impl Page {
196199

197200
Task::none()
198201
}
202+
Message::LeaveFederation(federation_id) => {
203+
let wallet = self.connected_state.wallet.clone();
204+
205+
Task::stream(async_stream::stream! {
206+
match wallet.leave_federation(federation_id).await {
207+
Ok(()) => {
208+
yield app::Message::AddToast(Toast {
209+
title: "Left federation".to_string(),
210+
body: "You have successfully left the federation.".to_string(),
211+
status: ToastStatus::Good,
212+
});
213+
214+
yield app::Message::Routes(super::Message::BitcoinWalletPage(
215+
Message::LeftFederation(federation_id)
216+
));
217+
}
218+
Err(err) => {
219+
yield app::Message::AddToast(Toast {
220+
title: "Failed to leave federation".to_string(),
221+
body: format!("Failed to leave the federation: {err}"),
222+
status: ToastStatus::Bad,
223+
});
224+
}
225+
}
226+
})
227+
}
228+
Message::LeftFederation(federation_id) => {
229+
// A verbose way of saying "if the user is currently on the FederationDetails page and the federation ID matches the one that was just left, navigate back to the List page".
230+
if let Subroute::FederationDetails(federation_details) = &self.subroute {
231+
if federation_details.view.federation_id == federation_id {
232+
return Task::done(app::Message::Routes(super::Message::Navigate(
233+
RouteName::BitcoinWallet(SubrouteName::List),
234+
)));
235+
}
236+
}
237+
238+
Task::none()
239+
}
199240
Message::Send(send_message) => {
200241
if let Subroute::Send(send_page) = &mut self.subroute {
201242
send_page.update(send_message)
@@ -444,6 +485,27 @@ impl FederationDetails {
444485
);
445486
}
446487

488+
// TODO: Add a function to `Wallet` to check whether we can safely leave a federation.
489+
// Call it here rather and get rid of `has_zero_balance`.
490+
let has_zero_balance = self.view.balance.msats == 0;
491+
492+
if !has_zero_balance {
493+
container = container.push(
494+
Text::new("Must have a zero balance in this federation in order to leave.")
495+
.size(20),
496+
);
497+
}
498+
499+
container = container.push(
500+
icon_button("Leave Federation", SvgIcon::Delete, PaletteColor::Danger).on_press_maybe(
501+
has_zero_balance.then(|| {
502+
app::Message::Routes(super::Message::BitcoinWalletPage(
503+
Message::LeaveFederation(self.view.federation_id),
504+
))
505+
}),
506+
),
507+
);
508+
447509
container = container.push(
448510
icon_button("Back", SvgIcon::ArrowBack, PaletteColor::Background).on_press(
449511
app::Message::Routes(super::Message::Navigate(RouteName::BitcoinWallet(

0 commit comments

Comments
 (0)