use std::num::NonZeroU32;
use zcash_client_backend::{
data_api::wallet::input_selection::GreedyInputSelector,
fees::{DustAction, DustOutputPolicy},
zip321::TransactionRequest,
};
use zcash_protocol::{
ShieldedProtocol,
consensus::Parameters,
memo::{Memo, MemoBytes},
value::Zatoshis,
};
use super::{
LightWallet,
error::{ProposeSendError, ProposeShieldError, WalletError},
};
use crate::config::ChainType;
use pepper_sync::keys::transparent::TransparentScope;
impl LightWallet {
pub(crate) async fn create_send_proposal(
&mut self,
request: TransactionRequest,
) -> Result<crate::data::proposal::ProportionalFeeProposal, ProposeSendError> {
let refund_address_count = self
.transparent_addresses
.keys()
.filter(|&address_id| address_id.scope() == TransparentScope::Refund)
.count() as u32;
let memo = self.change_memo_from_transaction_request(&request, refund_address_count);
let input_selector = GreedyInputSelector::new();
let change_strategy = zcash_client_backend::fees::zip317::SingleOutputChangeStrategy::new(
zcash_primitives::transaction::fees::zip317::FeeRule::standard(),
Some(memo),
ShieldedProtocol::Orchard,
DustOutputPolicy::new(DustAction::AllowDustChange, None),
);
let network = self.network;
zcash_client_backend::data_api::wallet::propose_transfer::<
LightWallet,
ChainType,
GreedyInputSelector<LightWallet>,
zcash_client_backend::fees::zip317::SingleOutputChangeStrategy<
zcash_primitives::transaction::fees::zip317::FeeRule,
LightWallet,
>,
WalletError,
>(
self,
&network,
zcash_primitives::zip32::AccountId::ZERO,
&input_selector,
&change_strategy,
request,
NonZeroU32::MIN,
)
.map_err(ProposeSendError::Proposal)
}
pub(crate) async fn create_shield_proposal(
&mut self,
) -> Result<crate::data::proposal::ProportionalFeeShieldProposal, ProposeShieldError> {
let input_selector = GreedyInputSelector::new();
let change_strategy = zcash_client_backend::fees::zip317::SingleOutputChangeStrategy::new(
zcash_primitives::transaction::fees::zip317::FeeRule::standard(),
None,
ShieldedProtocol::Orchard,
DustOutputPolicy::new(DustAction::AllowDustChange, None),
);
let network = self.network;
let proposed_shield = zcash_client_backend::data_api::wallet::propose_shielding::<
LightWallet,
ChainType,
GreedyInputSelector<LightWallet>,
zcash_client_backend::fees::zip317::SingleOutputChangeStrategy<
zcash_primitives::transaction::fees::zip317::FeeRule,
LightWallet,
>,
WalletError,
>(
self,
&network,
&input_selector,
&change_strategy,
Zatoshis::const_from_u64(10_000),
&self.get_transparent_addresses()?,
zip32::AccountId::ZERO,
1,
)
.map_err(ProposeShieldError::Component)?;
for step in proposed_shield.steps().iter() {
if step
.balance()
.proposed_change()
.iter()
.fold(0, |total_out, output| total_out + output.value().into_u64())
== 0
{
return Err(ProposeShieldError::Insufficient);
}
}
Ok(proposed_shield)
}
fn change_memo_from_transaction_request(
&self,
request: &TransactionRequest,
mut refund_address_count: u32,
) -> MemoBytes {
let mut recipient_uas = Vec::new();
let mut refund_address_indexes = Vec::new();
for payment in request.payments().values() {
if let Ok(address) = payment
.recipient_address()
.clone()
.convert_if_network::<zcash_keys::address::Address>(self.network.network_type())
{
match address {
zcash_keys::address::Address::Unified(unified_address) => {
recipient_uas.push(unified_address)
}
zcash_keys::address::Address::Tex(_) => {
refund_address_indexes.push(refund_address_count);
refund_address_count += 1;
}
_ => (),
}
}
}
let uas_bytes = match zingo_memo::create_wallet_internal_memo_version_1(
recipient_uas.as_slice(),
refund_address_indexes.as_slice(),
) {
Ok(bytes) => bytes,
Err(e) => {
log::error!(
"Could not write uas to memo field: {e}\n\
Your wallet will display an incorrect sent-to address. This is a visual error only.\n\
The correct address was sent to."
);
[0; 511]
}
};
MemoBytes::from(Memo::Arbitrary(Box::new(uas_bytes)))
}
}
#[cfg(test)]
mod test {
use zcash_protocol::{PoolType, ShieldedProtocol};
use crate::{
testutils::lightclient::from_inputs::transaction_request_from_send_inputs,
wallet::disk::testing::examples,
};
#[ignore = "for some reason this is does not work without network, even though it should be possible"]
#[tokio::test]
async fn example_mainnet_hhcclaltpcckcsslpcnetblr_80b5594ac_propose_100_000_to_self() {
let client = examples::NetworkSeedVersion::Mainnet(
examples::MainnetSeedVersion::HotelHumor(examples::HotelHumorVersion::Latest),
)
.load_example_wallet_with_client()
.await;
let mut wallet = client.wallet.lock().await;
let pool = PoolType::Shielded(ShieldedProtocol::Orchard);
let self_address = wallet.get_first_address(pool).unwrap();
let receivers = vec![(self_address.as_str(), 100_000, None)];
let request = transaction_request_from_send_inputs(receivers)
.expect("actually all of this logic oughta be internal to propose");
wallet
.create_send_proposal(request)
.await
.expect("can propose from existing data");
}
}