-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Send and receive on-chain funds #1857
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,38 @@ | ||
use crate::subscribers::AppSubscribers; | ||
use anyhow::Result; | ||
use axum::extract::State; | ||
use axum::http::StatusCode; | ||
use axum::response::IntoResponse; | ||
use axum::response::Response; | ||
use axum::Json; | ||
use native::api; | ||
use native::api::Fee; | ||
use native::api::SendPayment; | ||
use native::ln_dlc; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
use std::sync::Arc; | ||
|
||
pub struct AppError(anyhow::Error); | ||
|
||
impl IntoResponse for AppError { | ||
fn into_response(self) -> Response { | ||
( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
format!("Something went wrong: {}", self.0), | ||
) | ||
.into_response() | ||
} | ||
} | ||
|
||
impl<E> From<E> for AppError | ||
where | ||
E: Into<anyhow::Error>, | ||
{ | ||
fn from(err: E) -> Self { | ||
Self(err.into()) | ||
} | ||
} | ||
|
||
#[derive(Serialize)] | ||
pub struct Version { | ||
|
@@ -17,3 +48,45 @@ pub async fn version() -> Json<Version> { | |
pub async fn get_unused_address() -> impl IntoResponse { | ||
api::get_unused_address().0 | ||
} | ||
|
||
#[derive(Serialize)] | ||
pub struct Balance { | ||
on_chain: u64, | ||
off_chain: u64, | ||
} | ||
|
||
pub async fn get_balance( | ||
State(subscribers): State<Arc<AppSubscribers>>, | ||
) -> Result<Json<Balance>, AppError> { | ||
ln_dlc::refresh_wallet_info().await?; | ||
let balance = subscribers | ||
.wallet_info() | ||
.map(|wallet_info| Balance { | ||
on_chain: wallet_info.balances.on_chain, | ||
off_chain: wallet_info.balances.off_chain, | ||
}) | ||
.unwrap_or(Balance { | ||
on_chain: 0, | ||
off_chain: 0, | ||
}); | ||
Comment on lines
+68
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably return an error to the caller instead of setting it to 0 here. Could be quite scary if the balance shows suddenly 0.
This comment was marked as outdated.
Sorry, something went wrong.
This comment was marked as outdated.
Sorry, something went wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we do that when we consider implementing polling or push. Otherwise, the user would simply get an error only because his request came in earlier than the wallet info event has been published. |
||
|
||
Ok(Json(balance)) | ||
} | ||
|
||
#[derive(Deserialize)] | ||
pub struct Payment { | ||
address: String, | ||
amount: u64, | ||
fee: u64, | ||
} | ||
|
||
pub async fn send_payment(params: Json<Payment>) -> Result<(), AppError> { | ||
ln_dlc::send_payment(SendPayment::OnChain { | ||
address: params.0.address, | ||
amount: params.0.amount, | ||
fee: Fee::FeeRate { sats: params.0.fee }, | ||
}) | ||
.await?; | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
use native::api::WalletInfo; | ||
use native::event::subscriber::Subscriber; | ||
use native::event::EventInternal; | ||
use native::event::EventType; | ||
use parking_lot::Mutex; | ||
use std::sync::Arc; | ||
use tokio::sync::watch; | ||
|
||
pub struct Senders { | ||
wallet_info: watch::Sender<Option<WalletInfo>>, | ||
} | ||
|
||
/// Subscribes to events destined for the frontend (typically Flutter app) and | ||
/// provides a convenient way to access the current state. | ||
pub struct AppSubscribers { | ||
wallet_info: watch::Receiver<Option<WalletInfo>>, | ||
} | ||
|
||
impl AppSubscribers { | ||
pub async fn new() -> (Self, ThreadSafeSenders) { | ||
let (wallet_info_tx, wallet_info_rx) = watch::channel(None); | ||
|
||
let senders = Senders { | ||
wallet_info: wallet_info_tx, | ||
}; | ||
|
||
let subscriber = Self { | ||
wallet_info: wallet_info_rx, | ||
}; | ||
(subscriber, ThreadSafeSenders(Arc::new(Mutex::new(senders)))) | ||
} | ||
|
||
pub fn wallet_info(&self) -> Option<WalletInfo> { | ||
self.wallet_info.borrow().as_ref().cloned() | ||
} | ||
} | ||
|
||
impl Subscriber for Senders { | ||
fn notify(&self, event: &EventInternal) { | ||
if let Err(e) = self.handle_event(event) { | ||
tracing::error!(?e, ?event, "Failed to handle event"); | ||
} | ||
} | ||
|
||
fn events(&self) -> Vec<EventType> { | ||
vec![ | ||
EventType::Init, | ||
EventType::WalletInfoUpdateNotification, | ||
EventType::OrderUpdateNotification, | ||
EventType::PositionUpdateNotification, | ||
EventType::PositionClosedNotification, | ||
EventType::PriceUpdateNotification, | ||
EventType::ServiceHealthUpdate, | ||
EventType::ChannelStatusUpdate, | ||
] | ||
} | ||
} | ||
|
||
impl Senders { | ||
fn handle_event(&self, event: &EventInternal) -> anyhow::Result<()> { | ||
tracing::trace!(?event, "Received event"); | ||
if let EventInternal::WalletInfoUpdateNotification(wallet_info) = event { | ||
self.wallet_info.send(Some(wallet_info.clone()))?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct ThreadSafeSenders(Arc<Mutex<Senders>>); | ||
|
||
impl Subscriber for ThreadSafeSenders { | ||
fn notify(&self, event: &EventInternal) { | ||
self.0.lock().notify(event) | ||
} | ||
|
||
fn events(&self) -> Vec<EventType> { | ||
self.0.lock().events() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When merged I'll see to make this look the same as the other input fields