-
Notifications
You must be signed in to change notification settings - Fork 1
Payjoin v4 #3
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
base: master
Are you sure you want to change the base?
Payjoin v4 #3
Changes from 56 commits
05f1c6c
2e4c035
adad5df
097f4a1
cb6cb4b
40c0500
b664083
8e8b0a2
b1c446a
82c2287
7d69acc
b275f7b
df4d285
b7e55e4
4cded07
a9da817
64a490b
50981d7
7465e63
357138b
afdbacd
c949090
1aec1a9
cabadb1
a907cb2
c9ba5cb
1e358b3
abf9c87
cf9ad77
ed22c01
04a7511
2bcdefe
2f2b64d
164b834
3eb48ee
7186747
69aa6f0
5b16466
309ba07
de8da92
dc9aefd
a99c8da
18b597b
429d16f
d6cc409
1f0cf93
d560626
d74a605
6167b0f
e03b9d1
5848d9e
a0dd289
24013d7
61e1e4c
c673fa6
056049e
872b1fb
f1d726e
d206772
fbd916e
58757e1
27663ad
690bfff
69a6dec
69b6743
eec4ea3
39ee2e7
6f74743
2534935
7b5896b
d7f1bff
e62a806
b62fe49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ use liana::miniscript::bitcoin::{ | |
| Address, Txid, | ||
| }; | ||
| use lianad::config::Config as DaemonConfig; | ||
| use payjoin::Url; | ||
|
|
||
| use crate::{ | ||
| app::{cache::Cache, error::Error, view, wallet::Wallet}, | ||
|
|
@@ -25,7 +26,7 @@ pub enum Message { | |
| DaemonConfigLoaded(Result<(), Error>), | ||
| LoadWallet(Wallet), | ||
| Info(Result<GetInfoResult, Error>), | ||
| ReceiveAddress(Result<(Address, ChildNumber), Error>), | ||
| ReceiveAddress(Result<(Address, ChildNumber, Option<Url>), Error>), | ||
| /// Revealed addresses. The second element contains the start index used for the request. | ||
| RevealedAddresses( | ||
| Result<ListRevealedAddressesResult, Error>, | ||
|
|
@@ -55,6 +56,8 @@ pub enum Message { | |
| BroadcastModal(Result<HashSet<Txid>, Error>), | ||
| RbfModal(Box<HistoryTransaction>, bool, Result<HashSet<Txid>, Error>), | ||
| Export(ImportExportMessage), | ||
| SendPayjoin(Result<(), Error>), | ||
| PayjoinInitiated(Result<String, Error>), | ||
|
Comment on lines
+59
to
+60
Collaborator
Author
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. Could have better names to differenciate between these
Collaborator
Author
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 think the former could be sentPayjoin |
||
| } | ||
|
|
||
| impl From<ImportExportMessage> for Message { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,7 @@ pub enum PsbtModal { | |
| Broadcast(BroadcastModal), | ||
| Delete(DeleteModal), | ||
| Export(ExportModal), | ||
| SendPayjoin(SendPayjoinModal), | ||
| } | ||
|
|
||
| impl<'a> AsRef<dyn Modal + 'a> for PsbtModal { | ||
|
|
@@ -68,6 +69,7 @@ impl<'a> AsRef<dyn Modal + 'a> for PsbtModal { | |
| Self::Broadcast(a) => a, | ||
| Self::Delete(a) => a, | ||
| Self::Export(a) => a, | ||
| Self::SendPayjoin(a) => a, | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -80,6 +82,7 @@ impl<'a> AsMut<dyn Modal + 'a> for PsbtModal { | |
| Self::Broadcast(a) => a, | ||
| Self::Delete(a) => a, | ||
| Self::Export(a) => a, | ||
| Self::SendPayjoin(a) => a, | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -88,21 +91,23 @@ pub struct PsbtState { | |
| pub wallet: Arc<Wallet>, | ||
| pub desc_policy: LianaPolicy, | ||
| pub tx: SpendTx, | ||
| pub bip21: Option<String>, | ||
| pub saved: bool, | ||
| pub warning: Option<Error>, | ||
| pub labels_edited: LabelsEdited, | ||
| pub modal: Option<PsbtModal>, | ||
| } | ||
|
|
||
| impl PsbtState { | ||
| pub fn new(wallet: Arc<Wallet>, tx: SpendTx, saved: bool) -> Self { | ||
| pub fn new(wallet: Arc<Wallet>, tx: SpendTx, saved: bool, bip21: Option<String>) -> Self { | ||
| Self { | ||
| desc_policy: wallet.main_descriptor.policy(), | ||
| wallet, | ||
| labels_edited: LabelsEdited::default(), | ||
| warning: None, | ||
| modal: None, | ||
| tx, | ||
| bip21, | ||
| saved, | ||
| } | ||
| } | ||
|
|
@@ -182,6 +187,30 @@ impl PsbtState { | |
| Message::View(view::Message::Spend(view::SpendTxMessage::Delete)) => { | ||
| self.modal = Some(PsbtModal::Delete(DeleteModal::default())); | ||
| } | ||
| Message::View(view::Message::Spend(view::SpendTxMessage::SendPayjoin)) => { | ||
| let modal = SendPayjoinModal::new(); | ||
| let cmd = modal.load(daemon); | ||
| self.modal = Some(PsbtModal::SendPayjoin(modal)); | ||
| return cmd; | ||
| } | ||
| Message::View(view::Message::Spend(view::SpendTxMessage::PayjoinInitiated)) => { | ||
| self.tx.status = SpendStatus::PayjoinInitiated; | ||
| self.modal = None; | ||
| if let Some(_payjoin_info) = self.tx.payjoin_status.clone() { | ||
| let psbt = self.tx.psbt.clone(); | ||
| // TODO: should this be an error? | ||
| let bip21 = self.bip21.clone().expect("bip21 should be set"); | ||
| return Task::perform( | ||
| async move { | ||
| daemon | ||
| .send_payjoin(bip21, &psbt) | ||
| .await | ||
| .map_err(|e| e.into()) | ||
| }, | ||
| Message::SendPayjoin, | ||
| ); | ||
| } | ||
| } | ||
| Message::View(view::Message::Spend(view::SpendTxMessage::Sign)) => { | ||
| if let Some(PsbtModal::Sign(SignModal { display_modal, .. })) = &mut self.modal { | ||
| *display_modal = true; | ||
|
|
@@ -301,6 +330,28 @@ impl PsbtState { | |
| } | ||
| } | ||
|
|
||
| #[derive(Default)] | ||
| pub struct SendPayjoinModal { | ||
| _error: Option<Error>, | ||
|
Collaborator
Author
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. If errors cannot occur lets remove this |
||
| } | ||
|
|
||
| impl SendPayjoinModal { | ||
| pub fn new() -> Self { | ||
| Self { _error: None } | ||
| } | ||
| } | ||
|
|
||
| impl Modal for SendPayjoinModal { | ||
| fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> { | ||
| modal::Modal::new(content, view::psbt::payjoin_send_success_view()) | ||
| // On blur, show the psbts view | ||
| .on_blur(Some(view::Message::Spend( | ||
| view::SpendTxMessage::PayjoinInitiated, | ||
| ))) | ||
| .into() | ||
| } | ||
| } | ||
|
|
||
| #[derive(Default)] | ||
| pub struct SaveModal { | ||
| saved: bool, | ||
|
|
@@ -537,6 +588,7 @@ impl Modal for SignModal { | |
| self.signed.insert(fingerprint); | ||
| let daemon = daemon.clone(); | ||
| merge_signatures(&mut tx.psbt, &psbt); | ||
|
|
||
| if self.is_saved { | ||
| return Task::perform( | ||
| async move { daemon.update_spend_tx(&psbt).await.map_err(|e| e.into()) }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ use liana::miniscript::bitcoin::{ | |
| Address, Network, | ||
| }; | ||
| use liana_ui::{component::modal, widget::*}; | ||
| use payjoin::Url; | ||
|
|
||
| use crate::daemon::model::LabelsLoader; | ||
| use crate::dir::LianaDirectory; | ||
|
|
@@ -33,12 +34,14 @@ const PREV_ADDRESSES_PAGE_SIZE: usize = 20; | |
| pub enum Modal { | ||
| VerifyAddress(VerifyAddressModal), | ||
| ShowQrCode(ShowQrCodeModal), | ||
| ShowBip21QrCode(ShowBip21QrCodeModal), | ||
| None, | ||
| } | ||
|
|
||
| #[derive(Debug, Default)] | ||
| pub struct Addresses { | ||
| list: Vec<Address>, | ||
| bip21s: HashMap<Address, Url>, | ||
| derivation_indexes: Vec<ChildNumber>, | ||
| labels: HashMap<String, String>, | ||
| } | ||
|
|
@@ -121,6 +124,7 @@ impl State for ReceivePanel { | |
| self.warning.as_ref(), | ||
| view::receive::receive( | ||
| &self.addresses.list, | ||
| &self.addresses.bip21s, | ||
| &self.addresses.labels, | ||
| &self.prev_addresses.list, | ||
| &self.prev_addresses.labels, | ||
|
|
@@ -139,6 +143,9 @@ impl State for ReceivePanel { | |
| Modal::ShowQrCode(m) => modal::Modal::new(content, m.view()) | ||
| .on_blur(Some(view::Message::Close)) | ||
| .into(), | ||
| Modal::ShowBip21QrCode(m) => modal::Modal::new(content, m.view()) | ||
| .on_blur(Some(view::Message::Close)) | ||
| .into(), | ||
| Modal::None => content, | ||
| } | ||
| } | ||
|
|
@@ -175,10 +182,13 @@ impl State for ReceivePanel { | |
| } | ||
| Message::ReceiveAddress(res) => { | ||
| match res { | ||
| Ok((address, derivation_index)) => { | ||
| Ok((address, derivation_index, bip21)) => { | ||
| self.warning = None; | ||
| self.addresses.list.push(address); | ||
| self.addresses.list.push(address.clone()); | ||
| self.addresses.derivation_indexes.push(derivation_index); | ||
| if let Some(bip21) = bip21 { | ||
| self.addresses.bip21s.insert(address, bip21); | ||
| } | ||
| } | ||
| Err(e) => self.warning = Some(e), | ||
| } | ||
|
|
@@ -209,7 +219,7 @@ impl State for ReceivePanel { | |
| daemon | ||
| .get_new_address() | ||
| .await | ||
| .map(|res| (res.address, res.derivation_index)) | ||
| .map(|res| (res.address, res.derivation_index, res.bip21)) | ||
| .map_err(|e| e.into()) | ||
| }, | ||
| Message::ReceiveAddress, | ||
|
|
@@ -294,6 +304,33 @@ impl State for ReceivePanel { | |
| } | ||
| Task::none() | ||
| } | ||
| Message::View(view::Message::ShowBip21QrCode(i)) => { | ||
|
Collaborator
Author
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. Feature needed: add amount field. Currently the bip21 is missing this and our refrence implementation will complain |
||
| if let (Some(bip21), Some(index)) = ( | ||
| &self | ||
| .addresses | ||
| .bip21s | ||
| .get(self.address(i).expect("Address should be in bip21")), | ||
| self.derivation_index(i), | ||
| ) { | ||
| if let Some(modal) = ShowBip21QrCodeModal::new(bip21, *index) { | ||
| self.modal = Modal::ShowBip21QrCode(modal); | ||
| } | ||
| } | ||
| Task::none() | ||
| } | ||
| Message::View(view::Message::PayjoinInitiate) => { | ||
| let daemon = daemon.clone(); | ||
| Task::perform( | ||
| async move { | ||
| daemon | ||
| .receive_payjoin() | ||
| .await | ||
| .map(|res| (res.address, res.derivation_index, res.bip21)) | ||
| .map_err(|e| e.into()) | ||
| }, | ||
| Message::ReceiveAddress, | ||
| ) | ||
| } | ||
| _ => { | ||
| if let Modal::VerifyAddress(ref mut m) = self.modal { | ||
| m.update(daemon, cache, message) | ||
|
|
@@ -434,6 +471,26 @@ impl ShowQrCodeModal { | |
| } | ||
| } | ||
|
|
||
| pub struct ShowBip21QrCodeModal { | ||
| qr_code: qr_code::Data, | ||
| bip21: String, | ||
| } | ||
|
|
||
| impl ShowBip21QrCodeModal { | ||
| pub fn new(bip21: &payjoin::Url, _index: ChildNumber) -> Option<Self> { | ||
| qr_code::Data::new(format!("{}", bip21)) | ||
| .ok() | ||
| .map(|qr_code| Self { | ||
| qr_code, | ||
| bip21: bip21.to_string(), | ||
| }) | ||
| } | ||
|
|
||
| fn view(&self) -> Element<view::Message> { | ||
| view::receive::qr_modal(&self.qr_code, &self.bip21) | ||
| } | ||
| } | ||
|
|
||
| async fn verify_address( | ||
| hw: std::sync::Arc<dyn async_hwi::HWI + Send + Sync>, | ||
| index: ChildNumber, | ||
|
|
@@ -484,7 +541,8 @@ mod tests { | |
| Some(json!({"method": "getnewaddress", "params": Option::<Request>::None})), | ||
| Ok(json!(GetAddressResult::new( | ||
| addr.clone(), | ||
| ChildNumber::from_normal_idx(0).unwrap() | ||
| ChildNumber::from_normal_idx(0).unwrap(), | ||
| None, | ||
| ))), | ||
| ), | ||
| ]); | ||
|
|
||
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.
If bip21 is get shipped as its own PR. We may want to use bitcoin_uri for this type instead