Merge #1704: feat: List unused addresses on Receive panel
f8e702b65ea894131b212fdfed89b16068699fe9 feat: list unused addresses already revealed (Michael Mallan) d84c75c1ce548c2c7dfecf98960e9a19409cb9e7 refactor: extract address card logic to function (Michael Mallan) daa3daa938567e6d76e7dce02bddd725b97a72d9 refactor: add message for next receive address (Michael Mallan) 8b8b48142f60ede2b8ce702c89a61f3fd32a1842 feat: add list addresses method to gui daemon interface (Michael Mallan) Pull request description: This implements #809 by listing unused addresses on the Receive panel. ACKs for top commit: edouardparis: ACK f8e702b65ea894131b212fdfed89b16068699fe9 Tree-SHA512: 05538d099433bad33c947694e91bc082147159bebd4607ac2d37f9127013656c928a2a332f1212fc5f9d6a2d68e3047e7c3da5523233b92d47b2802811a706d8
This commit is contained in:
commit
e910c06027
@ -26,6 +26,11 @@ pub enum Message {
|
||||
LoadWallet(Wallet),
|
||||
Info(Result<GetInfoResult, Error>),
|
||||
ReceiveAddress(Result<(Address, ChildNumber), Error>),
|
||||
/// Revealed addresses. The second element contains the start index used for the request.
|
||||
RevealedAddresses(
|
||||
Result<ListRevealedAddressesResult, Error>,
|
||||
Option<ChildNumber>, // start_index
|
||||
),
|
||||
Coins(Result<Vec<Coin>, Error>),
|
||||
/// When we want both coins and tip height together.
|
||||
CoinsTipHeight(Result<Vec<Coin>, Error>, Result<i32, Error>),
|
||||
|
||||
@ -28,6 +28,8 @@ use crate::daemon::{
|
||||
Daemon,
|
||||
};
|
||||
|
||||
const PREV_ADDRESSES_PAGE_SIZE: usize = 20;
|
||||
|
||||
pub enum Modal {
|
||||
VerifyAddress(VerifyAddressModal),
|
||||
ShowQrCode(ShowQrCodeModal),
|
||||
@ -41,6 +43,12 @@ pub struct Addresses {
|
||||
labels: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Addresses {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.list.is_empty() && self.derivation_indexes.is_empty() && self.labels.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Labelled for Addresses {
|
||||
fn labelled(&self) -> Vec<LabelItem> {
|
||||
self.list
|
||||
@ -57,9 +65,14 @@ pub struct ReceivePanel {
|
||||
data_dir: LianaDirectory,
|
||||
wallet: Arc<Wallet>,
|
||||
addresses: Addresses,
|
||||
prev_addresses: Addresses,
|
||||
prev_continue_from: Option<ChildNumber>,
|
||||
show_prev_addresses: bool,
|
||||
selected: HashSet<Address>,
|
||||
labels_edited: LabelsEdited,
|
||||
modal: Modal,
|
||||
warning: Option<Error>,
|
||||
processing: bool,
|
||||
}
|
||||
|
||||
impl ReceivePanel {
|
||||
@ -68,9 +81,34 @@ impl ReceivePanel {
|
||||
data_dir,
|
||||
wallet,
|
||||
addresses: Addresses::default(),
|
||||
prev_addresses: Addresses::default(),
|
||||
prev_continue_from: None,
|
||||
show_prev_addresses: false,
|
||||
selected: HashSet::new(),
|
||||
labels_edited: LabelsEdited::default(),
|
||||
modal: Modal::None,
|
||||
warning: None,
|
||||
processing: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(&self, i: usize) -> Option<&Address> {
|
||||
if i < self.addresses.list.len() {
|
||||
self.addresses.list.get(i)
|
||||
} else {
|
||||
// i >= self.addresses.list.len()
|
||||
self.prev_addresses.list.get(i - self.addresses.list.len())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derivation_index(&self, i: usize) -> Option<&ChildNumber> {
|
||||
if i < self.addresses.list.len() {
|
||||
self.addresses.derivation_indexes.get(i)
|
||||
} else {
|
||||
// i >= self.addresses.list.len()
|
||||
self.prev_addresses
|
||||
.derivation_indexes
|
||||
.get(i - self.addresses.list.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,7 +122,13 @@ impl State for ReceivePanel {
|
||||
view::receive::receive(
|
||||
&self.addresses.list,
|
||||
&self.addresses.labels,
|
||||
&self.prev_addresses.list,
|
||||
&self.prev_addresses.labels,
|
||||
self.show_prev_addresses,
|
||||
&self.selected,
|
||||
self.labels_edited.cache(),
|
||||
self.prev_continue_from.is_none(),
|
||||
self.processing,
|
||||
),
|
||||
);
|
||||
|
||||
@ -118,7 +162,9 @@ impl State for ReceivePanel {
|
||||
match self.labels_edited.update(
|
||||
daemon,
|
||||
message,
|
||||
std::iter::once(&mut self.addresses).map(|a| a as &mut dyn LabelsLoader),
|
||||
std::iter::once(&mut self.addresses)
|
||||
.chain(std::iter::once(&mut self.prev_addresses))
|
||||
.map(|a| a as &mut dyn LabelsLoader),
|
||||
) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => {
|
||||
@ -143,20 +189,20 @@ impl State for ReceivePanel {
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Select(i)) => {
|
||||
let (address, index) = (
|
||||
self.address(i).expect("Must be present"),
|
||||
self.derivation_index(i).expect("Must be present"),
|
||||
);
|
||||
self.modal = Modal::VerifyAddress(VerifyAddressModal::new(
|
||||
self.data_dir.clone(),
|
||||
self.wallet.clone(),
|
||||
cache.network,
|
||||
self.addresses.list.get(i).expect("Must be present").clone(),
|
||||
*self
|
||||
.addresses
|
||||
.derivation_indexes
|
||||
.get(i)
|
||||
.expect("Must be present"),
|
||||
address.clone(),
|
||||
*index,
|
||||
));
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Next) => {
|
||||
Message::View(view::Message::NextReceiveAddress) => {
|
||||
let daemon = daemon.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
@ -169,11 +215,74 @@ impl State for ReceivePanel {
|
||||
Message::ReceiveAddress,
|
||||
)
|
||||
}
|
||||
Message::View(view::Message::ToggleShowPreviousAddresses) => {
|
||||
self.show_prev_addresses = !self.show_prev_addresses;
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::SelectAddress(addr)) => {
|
||||
if self.selected.contains(&addr) {
|
||||
self.selected.remove(&addr);
|
||||
} else {
|
||||
self.selected.insert(addr);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::RevealedAddresses(res, start_index) => {
|
||||
self.processing = false;
|
||||
match res {
|
||||
Ok(revealed) => {
|
||||
self.warning = None;
|
||||
// Make sure these results are for the expected continuation.
|
||||
// The start index can only be None for the first request when there are no prev addresses saved.
|
||||
if self.prev_continue_from == start_index
|
||||
&& (start_index.is_some() || self.prev_addresses.is_empty())
|
||||
{
|
||||
for entry in revealed.addresses.iter() {
|
||||
self.prev_addresses.list.push(entry.address.clone());
|
||||
self.prev_addresses.derivation_indexes.push(entry.index);
|
||||
if let Some(label) = &entry.label {
|
||||
self.prev_addresses.labels.insert(
|
||||
LabelItem::from(entry.address.clone()).to_string(),
|
||||
label.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.prev_continue_from = revealed.continue_from;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.warning = Some(e);
|
||||
}
|
||||
};
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Next) => {
|
||||
if self.prev_continue_from.is_some() {
|
||||
self.processing = true;
|
||||
let start_index = self.prev_continue_from;
|
||||
Task::perform(
|
||||
async move {
|
||||
(
|
||||
daemon
|
||||
.list_revealed_addresses(
|
||||
false,
|
||||
true,
|
||||
PREV_ADDRESSES_PAGE_SIZE,
|
||||
start_index,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.into()),
|
||||
start_index,
|
||||
)
|
||||
},
|
||||
|(res, start_index)| Message::RevealedAddresses(res, start_index),
|
||||
)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ShowQrCode(i)) => {
|
||||
if let (Some(address), Some(index)) = (
|
||||
self.addresses.list.get(i),
|
||||
self.addresses.derivation_indexes.get(i),
|
||||
) {
|
||||
if let (Some(address), Some(index)) = (self.address(i), self.derivation_index(i)) {
|
||||
if let Some(modal) = ShowQrCodeModal::new(address, *index) {
|
||||
self.modal = Modal::ShowQrCode(modal);
|
||||
}
|
||||
@ -192,12 +301,20 @@ impl State for ReceivePanel {
|
||||
|
||||
fn reload(
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
wallet: Arc<Wallet>,
|
||||
) -> Task<Message> {
|
||||
self.wallet = wallet;
|
||||
self.addresses = Addresses::default();
|
||||
Task::none()
|
||||
let data_dir = self.data_dir.clone();
|
||||
*self = Self::new(data_dir, wallet);
|
||||
Task::perform(
|
||||
async move {
|
||||
daemon
|
||||
.list_revealed_addresses(false, true, PREV_ADDRESSES_PAGE_SIZE, None)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
|res| Message::RevealedAddresses(res, None),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,13 +465,24 @@ mod tests {
|
||||
Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj")
|
||||
.unwrap()
|
||||
.assume_checked();
|
||||
let daemon = Daemon::new(vec![(
|
||||
Some(json!({"method": "getnewaddress", "params": Option::<Request>::None})),
|
||||
Ok(json!(GetAddressResult::new(
|
||||
addr.clone(),
|
||||
ChildNumber::from_normal_idx(0).unwrap()
|
||||
))),
|
||||
)]);
|
||||
let daemon = Daemon::new(vec![
|
||||
(
|
||||
Some(
|
||||
json!({"method": "listrevealedaddresses", "params": [false, true, 20, Option::<ChildNumber>::None]}),
|
||||
),
|
||||
Ok(json!(ListRevealedAddressesResult {
|
||||
addresses: vec![],
|
||||
continue_from: None,
|
||||
})),
|
||||
),
|
||||
(
|
||||
Some(json!({"method": "getnewaddress", "params": Option::<Request>::None})),
|
||||
Ok(json!(GetAddressResult::new(
|
||||
addr.clone(),
|
||||
ChildNumber::from_normal_idx(0).unwrap()
|
||||
))),
|
||||
),
|
||||
]);
|
||||
let wallet = Arc::new(Wallet::new(LianaDescriptor::from_str(DESC).unwrap()));
|
||||
let sandbox: Sandbox<ReceivePanel> = Sandbox::new(ReceivePanel::new(
|
||||
LianaDirectory::new(PathBuf::new()),
|
||||
@ -364,7 +492,11 @@ mod tests {
|
||||
let cache = Cache::default();
|
||||
let sandbox = sandbox.load(client.clone(), &cache, wallet).await;
|
||||
let sandbox = sandbox
|
||||
.update(client, &cache, Message::View(viewMessage::Next))
|
||||
.update(
|
||||
client,
|
||||
&cache,
|
||||
Message::View(viewMessage::NextReceiveAddress),
|
||||
)
|
||||
.await;
|
||||
|
||||
let panel = sandbox.state();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::{app::menu::Menu, export::ImportExportMessage, node::bitcoind::RpcAuthType};
|
||||
use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint};
|
||||
use liana::miniscript::bitcoin::{bip32::Fingerprint, Address, OutPoint};
|
||||
|
||||
pub trait Close {
|
||||
fn close() -> Self;
|
||||
@ -14,6 +14,9 @@ pub enum Message {
|
||||
Select(usize),
|
||||
SelectPayment(OutPoint),
|
||||
Label(Vec<String>, LabelMessage),
|
||||
NextReceiveAddress,
|
||||
ToggleShowPreviousAddresses,
|
||||
SelectAddress(Address),
|
||||
Settings(SettingsMessage),
|
||||
CreateSpend(CreateSpendMessage),
|
||||
ImportSpend(ImportSpendMessage),
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use iced::{
|
||||
alignment::Horizontal,
|
||||
widget::{
|
||||
qr_code::{self, QRCode},
|
||||
scrollable, Space,
|
||||
scrollable, Button, Space,
|
||||
},
|
||||
Alignment, Length,
|
||||
};
|
||||
@ -33,11 +34,75 @@ use crate::{
|
||||
|
||||
use super::message::Message;
|
||||
|
||||
fn address_card<'a>(
|
||||
row_index: usize,
|
||||
address: &'a bitcoin::Address,
|
||||
labels: &'a HashMap<String, String>,
|
||||
labels_editing: &'a HashMap<String, form::Value<String>>,
|
||||
) -> Container<'a, Message> {
|
||||
let addr = address.to_string();
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(if let Some(label) = labels_editing.get(&addr) {
|
||||
label::label_editing(vec![addr.clone()], label, text::P1_SIZE)
|
||||
} else {
|
||||
label::label_editable(vec![addr.clone()], labels.get(&addr), text::P1_SIZE)
|
||||
})
|
||||
.push(
|
||||
Row::new()
|
||||
.push(
|
||||
Container::new(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(Space::with_height(Length::Fixed(10.0)))
|
||||
.push(p2_regular(address).small().style(theme::text::secondary))
|
||||
// Space between the address and the scrollbar
|
||||
.push(Space::with_height(Length::Fixed(10.0))),
|
||||
)
|
||||
.direction(
|
||||
scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new().width(2).scroller_width(2),
|
||||
),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
Button::new(icon::clipboard_icon().style(theme::text::secondary))
|
||||
.on_press(Message::Clipboard(addr))
|
||||
.style(theme::button::transparent_border),
|
||||
)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.push(
|
||||
button::secondary(None, "Verify on hardware device")
|
||||
.on_press(Message::Select(row_index)),
|
||||
)
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(None, "Show QR Code")
|
||||
.on_press(Message::ShowQrCode(row_index)),
|
||||
),
|
||||
)
|
||||
.spacing(10),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn receive<'a>(
|
||||
addresses: &'a [bitcoin::Address],
|
||||
labels: &'a HashMap<String, String>,
|
||||
prev_addresses: &'a [bitcoin::Address],
|
||||
prev_labels: &'a HashMap<String, String>,
|
||||
show_prev_addresses: bool,
|
||||
selected: &'a HashSet<bitcoin::Address>,
|
||||
labels_editing: &'a HashMap<String, form::Value<String>>,
|
||||
is_last_page: bool,
|
||||
processing: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let mut addresses_count = 0; // for counting number of new addresses generated
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
@ -45,92 +110,146 @@ pub fn receive<'a>(
|
||||
.push(Container::new(h3("Receive")).width(Length::Fill))
|
||||
.push(
|
||||
button::primary(Some(icon::plus_icon()), "Generate address")
|
||||
.on_press(Message::Next),
|
||||
.on_press(Message::NextReceiveAddress),
|
||||
),
|
||||
)
|
||||
.push(p1_bold("New and never used reception addresses"))
|
||||
.push(text("Always generate a new address for each deposit."))
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.push(addresses.iter().enumerate().rev().fold(
|
||||
// iterate starting from most recently generated
|
||||
Column::new().spacing(10).width(Length::Fill),
|
||||
|col, (i, address)| {
|
||||
let addr = address.to_string();
|
||||
col.push(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(if let Some(label) = labels_editing.get(&addr) {
|
||||
label::label_editing(
|
||||
vec![addr.clone()],
|
||||
label,
|
||||
text::P1_SIZE,
|
||||
)
|
||||
} else {
|
||||
label::label_editable(
|
||||
vec![addr.clone()],
|
||||
labels.get(&addr),
|
||||
text::P1_SIZE,
|
||||
)
|
||||
})
|
||||
.push(
|
||||
Row::new()
|
||||
.push(
|
||||
Container::new(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(Space::with_height(
|
||||
Length::Fixed(10.0),
|
||||
))
|
||||
.push(
|
||||
p2_regular(addr)
|
||||
.small()
|
||||
.style(theme::text::secondary),
|
||||
)
|
||||
// Space between the address and the scrollbar
|
||||
.push(Space::with_height(
|
||||
Length::Fixed(10.0),
|
||||
)),
|
||||
)
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new()
|
||||
.width(2)
|
||||
.scroller_width(2),
|
||||
)),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
Button::new(
|
||||
icon::clipboard_icon()
|
||||
.style(theme::text::secondary),
|
||||
)
|
||||
.on_press(Message::Clipboard(address.to_string()))
|
||||
.style(theme::button::transparent_border),
|
||||
)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.push(
|
||||
button::secondary(
|
||||
None,
|
||||
"Verify on hardware device",
|
||||
)
|
||||
.on_press(Message::Select(i)),
|
||||
)
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(None, "Show QR Code")
|
||||
.on_press(Message::ShowQrCode(i)),
|
||||
),
|
||||
)
|
||||
.spacing(10),
|
||||
)
|
||||
.padding(20),
|
||||
)
|
||||
addresses_count += 1;
|
||||
col.push(address_card(i, address, labels, labels_editing))
|
||||
},
|
||||
)),
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
Button::new(
|
||||
Row::new()
|
||||
.align_y(Alignment::Center)
|
||||
.push(
|
||||
p1_bold("Previously generated addresses still awaiting deposit")
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(if show_prev_addresses {
|
||||
icon::collapsed_icon()
|
||||
} else {
|
||||
icon::collapse_icon()
|
||||
}),
|
||||
)
|
||||
.on_press(Message::ToggleShowPreviousAddresses)
|
||||
.padding(20)
|
||||
.width(Length::Fill)
|
||||
.style(theme::button::transparent_border),
|
||||
)
|
||||
.style(theme::card::simple),
|
||||
)
|
||||
.push_maybe(show_prev_addresses.then_some(Row::new().spacing(10).push(
|
||||
prev_addresses.iter().enumerate().fold(
|
||||
// prev addresses are already ordered in descending order
|
||||
Column::new().spacing(10).width(Length::Fill),
|
||||
|col, (i, address)| {
|
||||
col.push(if !selected.contains(address) {
|
||||
Button::new(
|
||||
Row::new()
|
||||
.spacing(30)
|
||||
.push(
|
||||
Container::new(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(Space::with_height(Length::Fixed(10.0)))
|
||||
.push(
|
||||
p2_regular(address)
|
||||
.small()
|
||||
.style(theme::text::secondary),
|
||||
)
|
||||
// Space between the address and the scrollbar
|
||||
.push(Space::with_height(Length::Fixed(10.0))),
|
||||
)
|
||||
.direction(
|
||||
scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new()
|
||||
.width(2)
|
||||
.scroller_width(2),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::FillPortion(13)),
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(Space::with_height(Length::Fixed(10.0)))
|
||||
.push(
|
||||
text(
|
||||
prev_labels
|
||||
.get(&address.to_string())
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.small()
|
||||
.style(theme::text::secondary),
|
||||
)
|
||||
// Space between the label and the scrollbar
|
||||
.push(Space::with_height(Length::Fixed(10.0))),
|
||||
)
|
||||
.direction(
|
||||
scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new()
|
||||
.width(2)
|
||||
.scroller_width(2),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::FillPortion(7)),
|
||||
)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.on_press(Message::SelectAddress(address.clone()))
|
||||
.style(theme::button::secondary)
|
||||
} else {
|
||||
// Continue the row index from those of generated addresses above.
|
||||
Button::new(address_card(
|
||||
addresses_count + i,
|
||||
address,
|
||||
prev_labels,
|
||||
labels_editing,
|
||||
))
|
||||
.padding(0) // so that button & card borders match
|
||||
.on_press(Message::SelectAddress(address.clone()))
|
||||
.style(theme::button::transparent_border)
|
||||
})
|
||||
},
|
||||
),
|
||||
)))
|
||||
.push_maybe(
|
||||
(!is_last_page && show_prev_addresses).then_some(
|
||||
Container::new(
|
||||
Button::new(
|
||||
text(if processing {
|
||||
"Fetching ..."
|
||||
} else {
|
||||
"See more"
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.align_x(Horizontal::Center),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding(15)
|
||||
.style(theme::button::transparent_border)
|
||||
.on_press_maybe((!processing).then_some(Message::Next)),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.style(theme::card::simple),
|
||||
),
|
||||
)
|
||||
.spacing(20)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -13,7 +13,9 @@ use tracing::{error, info};
|
||||
pub mod error;
|
||||
pub mod jsonrpc;
|
||||
|
||||
use liana::miniscript::bitcoin::{address, psbt::Psbt, Address, Network, OutPoint, Txid};
|
||||
use liana::miniscript::bitcoin::{
|
||||
address, bip32::ChildNumber, psbt::Psbt, Address, Network, OutPoint, Txid,
|
||||
};
|
||||
use lianad::{
|
||||
commands::{CoinStatus, CreateRecoveryResult, LabelItem},
|
||||
config::Config,
|
||||
@ -87,6 +89,24 @@ impl<C: Client + Send + Sync + Debug> Daemon for Lianad<C> {
|
||||
self.call("getnewaddress", Option::<Request>::None)
|
||||
}
|
||||
|
||||
async fn list_revealed_addresses(
|
||||
&self,
|
||||
is_change: bool,
|
||||
exclude_used: bool,
|
||||
limit: usize,
|
||||
start_index: Option<ChildNumber>,
|
||||
) -> Result<ListRevealedAddressesResult, DaemonError> {
|
||||
self.call(
|
||||
"listrevealedaddresses",
|
||||
Some(vec![
|
||||
json!(is_change),
|
||||
json!(exclude_used),
|
||||
json!(limit),
|
||||
json!(start_index), // a `null` argument is parsed as `None` by the command
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_deriv_indexes(
|
||||
&self,
|
||||
receive: Option<u32>,
|
||||
|
||||
@ -6,7 +6,9 @@ use tokio::sync::Mutex;
|
||||
use super::{model::*, node, Daemon, DaemonBackend, DaemonError};
|
||||
use crate::dir::LianaDirectory;
|
||||
use async_trait::async_trait;
|
||||
use liana::miniscript::bitcoin::{address, psbt::Psbt, Address, Network, OutPoint, Txid};
|
||||
use liana::miniscript::bitcoin::{
|
||||
address, bip32::ChildNumber, psbt::Psbt, Address, Network, OutPoint, Txid,
|
||||
};
|
||||
use lianad::{
|
||||
commands::{CoinStatus, LabelItem},
|
||||
config::Config,
|
||||
@ -103,6 +105,21 @@ impl Daemon for EmbeddedDaemon {
|
||||
self.command(|daemon| Ok(daemon.get_new_address())).await
|
||||
}
|
||||
|
||||
async fn list_revealed_addresses(
|
||||
&self,
|
||||
is_change: bool,
|
||||
exclude_used: bool,
|
||||
limit: usize,
|
||||
start_index: Option<ChildNumber>,
|
||||
) -> Result<ListRevealedAddressesResult, DaemonError> {
|
||||
self.command(|daemon| {
|
||||
daemon
|
||||
.list_revealed_addresses(is_change, exclude_used, limit, start_index)
|
||||
.map_err(|e| DaemonError::Unexpected(e.to_string()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_deriv_indexes(
|
||||
&self,
|
||||
receive: Option<u32>,
|
||||
|
||||
@ -11,7 +11,10 @@ use std::iter::FromIterator;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use liana::miniscript::bitcoin::{
|
||||
address, bip32::Fingerprint, psbt::Psbt, secp256k1, Address, Network, OutPoint, Txid,
|
||||
address,
|
||||
bip32::{ChildNumber, Fingerprint},
|
||||
psbt::Psbt,
|
||||
secp256k1, Address, Network, OutPoint, Txid,
|
||||
};
|
||||
use lianad::bip329::Labels;
|
||||
use lianad::commands::UpdateDerivIndexesResult;
|
||||
@ -104,6 +107,13 @@ pub trait Daemon: Debug {
|
||||
async fn stop(&self) -> Result<(), DaemonError>;
|
||||
async fn get_info(&self) -> Result<model::GetInfoResult, DaemonError>;
|
||||
async fn get_new_address(&self) -> Result<model::GetAddressResult, DaemonError>;
|
||||
async fn list_revealed_addresses(
|
||||
&self,
|
||||
is_change: bool,
|
||||
exclude_used: bool,
|
||||
limit: usize,
|
||||
start_index: Option<ChildNumber>,
|
||||
) -> Result<model::ListRevealedAddressesResult, DaemonError>;
|
||||
async fn update_deriv_indexes(
|
||||
&self,
|
||||
receive: Option<u32>,
|
||||
|
||||
@ -12,7 +12,8 @@ pub use liana::{
|
||||
};
|
||||
pub use lianad::commands::{
|
||||
CreateSpendResult, GetAddressResult, GetInfoResult, GetLabelsResult, LabelItem, ListCoinsEntry,
|
||||
ListCoinsResult, ListSpendEntry, ListSpendResult, ListTransactionsResult, TransactionInfo,
|
||||
ListCoinsResult, ListRevealedAddressesEntry, ListRevealedAddressesResult, ListSpendEntry,
|
||||
ListSpendResult, ListTransactionsResult, TransactionInfo,
|
||||
};
|
||||
|
||||
pub type Coin = ListCoinsEntry;
|
||||
|
||||
@ -347,6 +347,21 @@ pub struct Address {
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RevealedAddress {
|
||||
#[serde(deserialize_with = "deser_addr_assume_checked")]
|
||||
pub address: bitcoin::Address,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
pub label: Option<String>,
|
||||
pub used_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListRevealedAddresses {
|
||||
pub addresses: Vec<RevealedAddress>,
|
||||
pub continue_from: Option<bip32::ChildNumber>,
|
||||
}
|
||||
|
||||
pub mod payload {
|
||||
use liana::{descriptors::LianaDescriptor, miniscript::bitcoin};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
@ -9,7 +9,9 @@ use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use liana::{
|
||||
descriptors::LianaDescriptor,
|
||||
miniscript::bitcoin::{address, psbt::Psbt, Address, Network, OutPoint, Txid},
|
||||
miniscript::bitcoin::{
|
||||
address, bip32::ChildNumber, psbt::Psbt, Address, Network, OutPoint, Txid,
|
||||
},
|
||||
};
|
||||
use lianad::{
|
||||
bip329::Labels,
|
||||
@ -610,6 +612,57 @@ impl Daemon for BackendWalletClient {
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_revealed_addresses(
|
||||
&self,
|
||||
is_change: bool,
|
||||
exclude_used: bool,
|
||||
limit: usize,
|
||||
start_index: Option<ChildNumber>,
|
||||
) -> Result<ListRevealedAddressesResult, DaemonError> {
|
||||
let mut query = Vec::<(&str, String)>::new();
|
||||
query.push(("is_change_address", is_change.to_string()));
|
||||
query.push(("exclude_used", exclude_used.to_string()));
|
||||
query.push(("limit", limit.to_string()));
|
||||
if let Some(start) = start_index {
|
||||
query.push(("start_derivation_index", start.to_string()));
|
||||
}
|
||||
let response: Response = self
|
||||
.inner
|
||||
.request(
|
||||
Method::GET,
|
||||
&format!(
|
||||
"{}/v1/wallets/{}/addresses",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)
|
||||
.await
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(DaemonError::Http(
|
||||
Some(response.status().into()),
|
||||
response.text().await?,
|
||||
));
|
||||
}
|
||||
|
||||
let res: api::ListRevealedAddresses = response.json().await?;
|
||||
Ok(ListRevealedAddressesResult {
|
||||
addresses: res
|
||||
.addresses
|
||||
.into_iter()
|
||||
.map(|addr| ListRevealedAddressesEntry {
|
||||
index: addr.derivation_index,
|
||||
address: addr.address,
|
||||
label: addr.label,
|
||||
used_count: addr.used_count,
|
||||
})
|
||||
.collect(),
|
||||
continue_from: res.continue_from,
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_deriv_indexes(
|
||||
&self,
|
||||
_receive: Option<u32>,
|
||||
|
||||
@ -1412,11 +1412,12 @@ impl ListAddressesResult {
|
||||
}
|
||||
|
||||
/// A revealed address entry in the list returned by [`DaemonControl::list_revealed_addresses`].
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ListRevealedAddressesEntry {
|
||||
/// The address's derivation index.
|
||||
pub index: ChildNumber,
|
||||
/// The address.
|
||||
#[serde(deserialize_with = "deser_addr_assume_checked")]
|
||||
pub address: bitcoin::Address,
|
||||
/// Label assigned to the address, if any.
|
||||
pub label: Option<String>,
|
||||
@ -1428,7 +1429,7 @@ pub struct ListRevealedAddressesEntry {
|
||||
}
|
||||
|
||||
/// Result of a [`DaemonControl::list_revealed_addresses`] request.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ListRevealedAddressesResult {
|
||||
/// Revealed addresses in order of descending derivation index.
|
||||
pub addresses: Vec<ListRevealedAddressesEntry>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user