Skip to content

Commit

Permalink
Allow filtering anchors/delegations and make filtering infallible (#1568
Browse files Browse the repository at this point in the history
)

* Make it infallible

* Add unspent_anchor_output and unspent_delegation_output

* Add missing is_none checks

* Adapt comment
  • Loading branch information
thibault-martinez authored Nov 6, 2023
1 parent 3beca7b commit 1bf1a53
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 38 deletions.
4 changes: 2 additions & 2 deletions bindings/core/src/method_handler/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM
Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect())
}
WalletMethod::Outputs { filter_options } => {
let outputs = wallet.outputs(filter_options).await?;
let outputs = wallet.outputs(filter_options).await;
Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect())
}
WalletMethod::PendingTransactions => {
Expand Down Expand Up @@ -394,7 +394,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM
Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect())
}
WalletMethod::UnspentOutputs { filter_options } => {
let outputs = wallet.unspent_outputs(filter_options).await?;
let outputs = wallet.unspent_outputs(filter_options).await;
Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect())
}
};
Expand Down
8 changes: 4 additions & 4 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ pub async fn output_command(wallet: &Wallet, selector: OutputSelector) -> Result
let output = match selector {
OutputSelector::Id(id) => wallet.get_output(&id).await,
OutputSelector::Index(index) => {
let mut outputs = wallet.outputs(None).await?;
let mut outputs = wallet.outputs(None).await;
outputs.sort_unstable_by(outputs_ordering);
outputs.into_iter().nth(index)
}
Expand All @@ -671,7 +671,7 @@ pub async fn output_command(wallet: &Wallet, selector: OutputSelector) -> Result

/// `outputs` command
pub async fn outputs_command(wallet: &Wallet) -> Result<(), Error> {
print_outputs(wallet.outputs(None).await?, "Outputs:").await
print_outputs(wallet.outputs(None).await, "Outputs:").await
}

// `send` command
Expand Down Expand Up @@ -828,7 +828,7 @@ pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result

/// `unspent-outputs` command
pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> {
print_outputs(wallet.unspent_outputs(None).await?, "Unspent outputs:").await
print_outputs(wallet.unspent_outputs(None).await, "Unspent outputs:").await
}

pub async fn vote_command(wallet: &Wallet, event_id: ParticipationEventId, answers: Vec<u8>) -> Result<(), Error> {
Expand Down Expand Up @@ -917,7 +917,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> {
address.inner()
);

let unspent_outputs = wallet.unspent_outputs(None).await?;
let unspent_outputs = wallet.unspent_outputs(None).await;
let slot_index = wallet.client().get_slot_index().await?;

let mut output_ids = Vec::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn main() -> Result<()> {
// unlock condition and it is an `AddressUnlockCondition`, and so they are valid for consolidation. They have the
// same `AddressUnlockCondition`(the address of the wallet), so they will be consolidated into one
// output.
let outputs = wallet.unspent_outputs(None).await?;
let outputs = wallet.unspent_outputs(None).await;
println!("Outputs BEFORE consolidation:");
outputs.iter().enumerate().for_each(|(i, output_data)| {
println!("OUTPUT #{i}");
Expand Down Expand Up @@ -78,7 +78,7 @@ async fn main() -> Result<()> {
println!("Wallet synced");

// Outputs after consolidation
let outputs = wallet.unspent_outputs(None).await?;
let outputs = wallet.unspent_outputs(None).await;
println!("Outputs AFTER consolidation:");
outputs.iter().enumerate().for_each(|(i, output_data)| {
println!("OUTPUT #{i}");
Expand Down
4 changes: 2 additions & 2 deletions sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ async fn main() -> Result<()> {

// Print output ids
println!("Output ids:");
for output in wallet.outputs(None).await? {
for output in wallet.outputs(None).await {
println!("{}", output.output_id);
}

// Print unspent output ids
println!("Unspent output ids:");
for output in wallet.unspent_outputs(None).await? {
for output in wallet.unspent_outputs(None).await {
println!("{}", output.output_id);
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/examples/wallet/spammer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async fn main() -> Result<()> {
output_types: Some(vec![BasicOutput::KIND]),
..Default::default()
})
.await?
.await
.iter()
.filter(|data| data.output.amount() >= SEND_AMOUNT)
.count();
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ pub enum Output {
Basic(BasicOutput),
/// An account output.
Account(AccountOutput),
/// An anchor output.
Anchor(AnchorOutput),
/// A foundry output.
Foundry(FoundryOutput),
/// An NFT output.
Nft(NftOutput),
/// A delegation output.
Delegation(DelegationOutput),
/// An anchor output.
Anchor(AnchorOutput),
}

impl core::fmt::Debug for Output {
Expand Down
83 changes: 67 additions & 16 deletions sdk/src/wallet/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ use crate::{
types::{
block::{
address::{Address, Bech32Address, Hrp, ImplicitAccountCreationAddress},
output::{dto::FoundryOutputDto, AccountId, FoundryId, FoundryOutput, NftId, Output, OutputId, TokenId},
output::{
dto::FoundryOutputDto, AccountId, AnchorId, DelegationId, FoundryId, FoundryOutput, NftId, Output,
OutputId, TokenId,
},
payload::signed_transaction::{dto::TransactionDto, Transaction, TransactionId},
},
TryFromDto,
Expand Down Expand Up @@ -296,23 +299,32 @@ where
&self,
outputs: impl Iterator<Item = &'a OutputData>,
filter: impl Into<Option<FilterOptions>>,
) -> Result<Vec<OutputData>> {
) -> Vec<OutputData> {
let filter = filter.into();

if let Some(filter) = filter {
let mut filtered_outputs = Vec::new();

for output in outputs {
match &output.output {
Output::Account(alias) => {
Output::Account(account) => {
if let Some(account_ids) = &filter.account_ids {
let account_id = alias.account_id_non_null(&output.output_id);
let account_id = account.account_id_non_null(&output.output_id);
if account_ids.contains(&account_id) {
filtered_outputs.push(output.clone());
continue;
}
}
}
Output::Anchor(anchor) => {
if let Some(anchor_ids) = &filter.anchor_ids {
let anchor_id = anchor.anchor_id_non_null(&output.output_id);
if anchor_ids.contains(&anchor_id) {
filtered_outputs.push(output.clone());
continue;
}
}
}
Output::Foundry(foundry) => {
if let Some(foundry_ids) = &filter.foundry_ids {
let foundry_id = foundry.id();
Expand All @@ -331,6 +343,15 @@ where
}
}
}
Output::Delegation(delegation) => {
if let Some(delegation_ids) = &filter.delegation_ids {
let delegation_id = delegation.delegation_id_non_null(&output.output_id);
if delegation_ids.contains(&delegation_id) {
filtered_outputs.push(output.clone());
continue;
}
}
}
_ => {}
}

Expand All @@ -352,56 +373,86 @@ where
}
}

// If ids are provided, only return them and no other outputs.
if filter.account_ids.is_none() && filter.foundry_ids.is_none() && filter.nft_ids.is_none() {
// Include the output if we're not filtering by IDs.
if filter.account_ids.is_none()
&& filter.anchor_ids.is_none()
&& filter.foundry_ids.is_none()
&& filter.nft_ids.is_none()
&& filter.delegation_ids.is_none()
{
filtered_outputs.push(output.clone());
}
}

Ok(filtered_outputs)
filtered_outputs
} else {
Ok(outputs.cloned().collect())
outputs.cloned().collect()
}
}

/// Returns outputs of the wallet.
pub async fn outputs(&self, filter: impl Into<Option<FilterOptions>> + Send) -> Result<Vec<OutputData>> {
pub async fn outputs(&self, filter: impl Into<Option<FilterOptions>> + Send) -> Vec<OutputData> {
self.filter_outputs(self.data().await.outputs.values(), filter)
}

/// Returns unspent outputs of the wallet.
pub async fn unspent_outputs(&self, filter: impl Into<Option<FilterOptions>> + Send) -> Result<Vec<OutputData>> {
pub async fn unspent_outputs(&self, filter: impl Into<Option<FilterOptions>> + Send) -> Vec<OutputData> {
self.filter_outputs(self.data().await.unspent_outputs.values(), filter)
}

/// Gets the unspent account output matching the given ID.
pub async fn unspent_account_output(&self, account_id: &AccountId) -> Result<Option<OutputData>> {
pub async fn unspent_account_output(&self, account_id: &AccountId) -> Option<OutputData> {
self.unspent_outputs(FilterOptions {
account_ids: Some([*account_id].into()),
..Default::default()
})
.await
.map(|res| res.get(0).cloned())
.first()
.cloned()
}

/// Gets the unspent anchor output matching the given ID.
pub async fn unspent_anchor_output(&self, anchor_id: &AnchorId) -> Option<OutputData> {
self.unspent_outputs(FilterOptions {
anchor_ids: Some([*anchor_id].into()),
..Default::default()
})
.await
.first()
.cloned()
}

/// Gets the unspent foundry output matching the given ID.
pub async fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Result<Option<OutputData>> {
pub async fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Option<OutputData> {
self.unspent_outputs(FilterOptions {
foundry_ids: Some([*foundry_id].into()),
..Default::default()
})
.await
.map(|res| res.get(0).cloned())
.first()
.cloned()
}

/// Gets the unspent nft output matching the given ID.
pub async fn unspent_nft_output(&self, nft_id: &NftId) -> Result<Option<OutputData>> {
pub async fn unspent_nft_output(&self, nft_id: &NftId) -> Option<OutputData> {
self.unspent_outputs(FilterOptions {
nft_ids: Some([*nft_id].into()),
..Default::default()
})
.await
.map(|res| res.get(0).cloned())
.first()
.cloned()
}

/// Gets the unspent delegation output matching the given ID.
pub async fn unspent_delegation_output(&self, delegation_id: &DelegationId) -> Option<OutputData> {
self.unspent_outputs(FilterOptions {
delegation_ids: Some([*delegation_id].into()),
..Default::default()
})
.await
.first()
.cloned()
}

/// Returns all incoming transactions of the wallet
Expand Down
6 changes: 5 additions & 1 deletion sdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ use crate::{
types::{
api::core::OutputWithMetadataResponse,
block::{
output::{AccountId, FoundryId, NftId},
output::{AccountId, AnchorId, DelegationId, FoundryId, NftId},
payload::signed_transaction::{SignedTransactionPayload, TransactionId},
},
},
Expand All @@ -95,10 +95,14 @@ pub struct FilterOptions {
pub output_types: Option<Vec<u8>>,
/// Return all account outputs matching these IDs.
pub account_ids: Option<HashSet<AccountId>>,
/// Return all anchor outputs matching these IDs.
pub anchor_ids: Option<HashSet<AnchorId>>,
/// Return all foundry outputs matching these IDs.
pub foundry_ids: Option<HashSet<FoundryId>>,
/// Return all nft outputs matching these IDs.
pub nft_ids: Option<HashSet<NftId>>,
/// Return all delegation outputs matching these IDs.
pub delegation_ids: Option<HashSet<DelegationId>>,
}

pub(crate) fn build_transaction_from_payload_and_inputs(
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/wallet/operations/participation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ where
"[get_participation_overview] restored_spent_cached_outputs_len: {}",
restored_spent_cached_outputs_len
);
let outputs = self.outputs(None).await?;
let outputs = self.outputs(None).await;
let participation_outputs = outputs
.into_iter()
.filter(|output_data| {
Expand Down Expand Up @@ -230,7 +230,7 @@ where
log::debug!("[get_voting_output]");
Ok(self
.unspent_outputs(None)
.await?
.await
.iter()
.filter(|output_data| is_valid_participation_output(&output_data.output))
.max_by_key(|output_data| output_data.output.amount())
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/wallet/operations/syncing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ where
address: self.address().await,
output_ids: self
.unspent_outputs(None)
.await?
.await
.into_iter()
.map(|data| data.output_id)
.collect::<Vec<_>>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ where
{
log::debug!("[TRANSACTION] prepare_send_nft");

let unspent_outputs = self.unspent_outputs(None).await?;
let unspent_outputs = self.unspent_outputs(None).await;
let token_supply = self.client().get_token_supply().await?;

let mut outputs = Vec::new();
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/wallet/operations/transaction/prepare_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ where
)
} else {
// Transition an existing NFT output
let unspent_nft_output = self.unspent_nft_output(nft_id).await?;
let unspent_nft_output = self.unspent_nft_output(nft_id).await;

// Find nft output from the inputs
let mut first_output_builder = if let Some(nft_output_data) = &unspent_nft_output {
Expand Down
4 changes: 2 additions & 2 deletions sdk/tests/wallet/consolidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async fn consolidation() -> Result<()> {

let balance = wallet_1.sync(None).await.unwrap();
assert_eq!(balance.base_coin().available(), 10 * amount);
assert_eq!(wallet_1.unspent_outputs(None).await?.len(), 10);
assert_eq!(wallet_1.unspent_outputs(None).await.len(), 10);

let tx = wallet_1
.consolidate_outputs(ConsolidationParams::new().with_force(true))
Expand All @@ -44,7 +44,7 @@ async fn consolidation() -> Result<()> {
// Balance still the same
assert_eq!(balance.base_coin().available(), 10 * amount);
// Only one unspent output
assert_eq!(wallet_1.unspent_outputs(None).await?.len(), 1);
assert_eq!(wallet_1.unspent_outputs(None).await.len(), 1);

tear_down(storage_path_0)?;
tear_down(storage_path_1)?;
Expand Down
2 changes: 1 addition & 1 deletion sdk/tests/wallet/output_preparation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ async fn prepare_output_only_single_nft() -> Result<()> {
let balance = wallet_1.sync(None).await?;
assert_eq!(balance.nfts().len(), 1);

let nft_data = &wallet_1.unspent_outputs(None).await?[0];
let nft_data = &wallet_1.unspent_outputs(None).await[0];
let nft_id = *balance.nfts().first().unwrap();
// Send NFT back to first wallet
let output = wallet_1
Expand Down

0 comments on commit 1bf1a53

Please sign in to comment.