Skip to content

Commit

Permalink
Merge pull request #17 from MutinyWallet/on-chain
Browse files Browse the repository at this point in the history
On-chain send and receive
  • Loading branch information
benthecarman authored May 14, 2024
2 parents fd3f341 + f6a48f8 commit 2acdcc7
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 23 deletions.
19 changes: 17 additions & 2 deletions src/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bitcoin::Txid;
use bitcoin::{Address, Txid};
use fedimint_core::api::InviteCode;
use fedimint_core::Amount;
use fedimint_ln_common::lightning_invoice::Bolt11Invoice;
Expand All @@ -8,6 +8,8 @@ use tokio::sync::mpsc;
pub enum UICoreMsg {
SendLightning(Bolt11Invoice),
ReceiveLightning(Amount),
SendOnChain { address: Address, amount_sats: u64 },
ReceiveOnChain,
AddFederation(InviteCode),
Unlock(String),
}
Expand All @@ -29,8 +31,9 @@ pub enum CoreUIMsg {
Sending,
SendSuccess(SendSuccessMsg),
SendFailure(String),
ReceiveInvoiceGenerating,
ReceiveGenerating,
ReceiveInvoiceGenerated(Bolt11Invoice),
ReceiveAddressGenerated(Address),
ReceiveSuccess(ReceiveSuccessMsg),
ReceiveFailed(String),
BalanceUpdated(Amount),
Expand Down Expand Up @@ -61,11 +64,23 @@ impl UIHandle {
self.msg_send(UICoreMsg::SendLightning(invoice)).await;
}

pub async fn send_onchain(&self, address: Address, amount_sats: u64) {
self.msg_send(UICoreMsg::SendOnChain {
address,
amount_sats,
})
.await;
}

pub async fn receive(&self, amount: u64) {
self.msg_send(UICoreMsg::ReceiveLightning(Amount::from_sats(amount)))
.await;
}

pub async fn receive_onchain(&self) {
self.msg_send(UICoreMsg::ReceiveOnChain).await;
}

pub async fn unlock(&self, password: String) {
self.msg_send(UICoreMsg::Unlock(password)).await;
}
Expand Down
78 changes: 72 additions & 6 deletions src/core.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use anyhow::anyhow;
use bip39::Mnemonic;
use bitcoin::Network;
use bitcoin::{Address, Network};
use fedimint_core::api::InviteCode;
use fedimint_core::config::FederationId;
use fedimint_core::Amount;
use fedimint_ln_client::{LightningClientModule, PayType};
use fedimint_ln_common::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
use fedimint_wallet_client::WalletClientModule;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use iced::{
futures::{channel::mpsc::Sender, SinkExt},
Expand All @@ -19,6 +21,9 @@ use iced::{
use log::error;
use tokio::sync::RwLock;

use crate::fedimint_client::{
spawn_onchain_payment_subscription, spawn_onchain_receive_subscription,
};
use crate::{
bridge::{self, CoreUIMsg, UICoreMsg},
conf::{self, get_mnemonic},
Expand All @@ -33,6 +38,8 @@ use crate::{
},
};

const PEG_IN_TIMEOUT_YEAR: Duration = Duration::from_secs(86400 * 365);

struct HarborCore {
balance: Amount,
network: Network,
Expand Down Expand Up @@ -130,6 +137,44 @@ impl HarborCore {
Ok(invoice)
}

async fn send_onchain(&self, address: Address, sats: u64) -> anyhow::Result<()> {
// todo go through all clients and select the first one that has enough balance
let client = self.get_client().await.fedimint_client;
let onchain = client.get_first_module::<WalletClientModule>();

let amount = bitcoin::Amount::from_sat(sats);

// todo add manual fee selection
let fees = onchain.get_withdraw_fees(address.clone(), amount).await?;

let op_id = onchain
.withdraw(address, bitcoin::Amount::from_sat(sats), fees, ())
.await?;

let sub = onchain.subscribe_withdraw_updates(op_id).await?;

spawn_onchain_payment_subscription(self.tx.clone(), client.clone(), sub).await;

Ok(())
}

async fn receive_onchain(&self) -> anyhow::Result<Address> {
// todo add federation id selection
let client = self.get_client().await.fedimint_client;
let onchain = client.get_first_module::<WalletClientModule>();

// expire the address in 1 year
let valid_until = SystemTime::now() + PEG_IN_TIMEOUT_YEAR;

let (op_id, address) = onchain.get_deposit_address(valid_until, ()).await?;

let sub = onchain.subscribe_deposit_updates(op_id).await?;

spawn_onchain_receive_subscription(self.tx.clone(), client.clone(), sub).await;

Ok(address)
}

async fn add_federation(&self, invite_code: InviteCode) -> anyhow::Result<()> {
let id = invite_code.federation_id();

Expand Down Expand Up @@ -245,16 +290,37 @@ pub fn run_core() -> Subscription<Message> {
}
}
UICoreMsg::ReceiveLightning(amount) => {
core.msg(CoreUIMsg::ReceiveInvoiceGenerating).await;
core.msg(CoreUIMsg::ReceiveGenerating).await;
match core.receive_lightning(amount).await {
Err(e) => {
core.msg(CoreUIMsg::ReceiveFailed(e.to_string())).await;
}
Ok(invoice) => {
core.msg(CoreUIMsg::ReceiveInvoiceGenerated(
invoice.clone(),
))
.await;
core.msg(CoreUIMsg::ReceiveInvoiceGenerated(invoice))
.await;
}
}
}
UICoreMsg::SendOnChain {
address,
amount_sats,
} => {
log::info!("Got UICoreMsg::SendOnChain");
core.msg(CoreUIMsg::Sending).await;
if let Err(e) = core.send_onchain(address, amount_sats).await {
error!("Error sending: {e}");
core.msg(CoreUIMsg::SendFailure(e.to_string())).await;
}
}
UICoreMsg::ReceiveOnChain => {
core.msg(CoreUIMsg::ReceiveGenerating).await;
match core.receive_onchain().await {
Err(e) => {
core.msg(CoreUIMsg::ReceiveFailed(e.to_string())).await;
}
Ok(address) => {
core.msg(CoreUIMsg::ReceiveAddressGenerated(address))
.await;
}
}
}
Expand Down
91 changes: 90 additions & 1 deletion src/fedimint_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use fedimint_ln_client::{
};
use fedimint_ln_common::LightningGateway;
use fedimint_mint_client::MintClientInit;
use fedimint_wallet_client::{WalletClientInit, WalletClientModule};
use fedimint_wallet_client::{DepositState, WalletClientInit, WalletClientModule, WithdrawState};
use iced::futures::channel::mpsc::Sender;
use iced::futures::{SinkExt, StreamExt};
use log::{debug, error, info, trace};
Expand Down Expand Up @@ -334,6 +334,95 @@ pub(crate) async fn spawn_internal_payment_subscription(
});
}

pub(crate) async fn spawn_onchain_payment_subscription(
mut sender: Sender<Message>,
client: ClientHandleArc,
subscription: UpdateStreamOrOutcome<WithdrawState>,
) {
spawn(async move {
let mut stream = subscription.into_stream();
while let Some(op_state) = stream.next().await {
match op_state {
WithdrawState::Created => {}
WithdrawState::Failed(error) => {
error!("Onchain payment failed: {error:?}");
sender
.send(Message::CoreMessage(CoreUIMsg::SendFailure(
error.to_string(),
)))
.await
.unwrap();
break;
}
WithdrawState::Succeeded(txid) => {
info!("Onchain payment success: {txid}");
let params = SendSuccessMsg::Onchain { txid };
sender
.send(Message::CoreMessage(CoreUIMsg::SendSuccess(params)))
.await
.unwrap();

let new_balance = client.get_balance().await;
sender
.send(Message::CoreMessage(CoreUIMsg::BalanceUpdated(new_balance)))
.await
.unwrap();

break;
}
}
}
});
}

pub(crate) async fn spawn_onchain_receive_subscription(
mut sender: Sender<Message>,
client: ClientHandleArc,
subscription: UpdateStreamOrOutcome<DepositState>,
) {
spawn(async move {
let mut stream = subscription.into_stream();
while let Some(op_state) = stream.next().await {
match op_state {
DepositState::WaitingForTransaction => {}
DepositState::Failed(error) => {
error!("Onchain receive failed: {error:?}");
sender
.send(Message::CoreMessage(CoreUIMsg::ReceiveFailed(
error.to_string(),
)))
.await
.unwrap();
break;
}
DepositState::WaitingForConfirmation(data) => {
info!("Onchain receive waiting for confirmation: {data:?}");
let params = ReceiveSuccessMsg::Onchain {
txid: data.btc_transaction.txid(),
};
sender
.send(Message::CoreMessage(CoreUIMsg::ReceiveSuccess(params)))
.await
.unwrap();
}
DepositState::Confirmed(data) => {
info!("Onchain receive confirmed: {data:?}");
}
DepositState::Claimed(data) => {
info!("Onchain receive claimed: {data:?}");
let new_balance = client.get_balance().await;
sender
.send(Message::CoreMessage(CoreUIMsg::BalanceUpdated(new_balance)))
.await
.unwrap();

break;
}
}
}
});
}

#[derive(Clone)]
pub struct FedimintStorage {
storage: Arc<dyn DBConnection + Send + Sync>,
Expand Down
Loading

0 comments on commit 2acdcc7

Please sign in to comment.