Sender or Recipient pays fee
This commit is contained in:
parent
a817b05259
commit
c269eafe36
|
@ -110,11 +110,15 @@ struct CResult_____c_char transfer_pools(uint8_t coin,
|
|||
uint8_t from_pool,
|
||||
uint8_t to_pool,
|
||||
uint64_t amount,
|
||||
bool fee_included,
|
||||
char *memo,
|
||||
uint64_t split_amount,
|
||||
uint32_t confirmations);
|
||||
|
||||
struct CResult_____c_char shield_taddr(uint8_t coin, uint32_t account, uint32_t confirmations);
|
||||
struct CResult_____c_char shield_taddr(uint8_t coin,
|
||||
uint32_t account,
|
||||
uint64_t amount,
|
||||
uint32_t confirmations);
|
||||
|
||||
void scan_transparent_accounts(uint32_t gap_limit);
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ async fn save_contacts_tx(memos: &[Memo], anchor_offset: u32) -> anyhow::Result<
|
|||
.map(|m| RecipientMemo {
|
||||
address: address.clone(),
|
||||
amount: 0,
|
||||
fee_included: false,
|
||||
memo: m.clone(),
|
||||
max_amount_per_note: 0,
|
||||
})
|
||||
|
|
|
@ -12,7 +12,6 @@ use std::path::Path;
|
|||
use std::sync::Mutex;
|
||||
use tokio::sync::Semaphore;
|
||||
use zcash_primitives::transaction::builder::Progress;
|
||||
use crate::api::payment_v2::AmountOrMax;
|
||||
|
||||
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
|
||||
|
||||
|
@ -424,6 +423,7 @@ pub async unsafe extern "C" fn transfer_pools(
|
|||
account: u32,
|
||||
from_pool: u8, to_pool: u8,
|
||||
amount: u64,
|
||||
fee_included: bool,
|
||||
memo: *mut c_char,
|
||||
split_amount: u64,
|
||||
confirmations: u32,
|
||||
|
@ -431,7 +431,7 @@ pub async unsafe extern "C" fn transfer_pools(
|
|||
from_c_str!(memo);
|
||||
let res = async move {
|
||||
let tx_plan = crate::api::payment_v2::transfer_pools(coin, account, from_pool, to_pool,
|
||||
AmountOrMax::Amount(amount),
|
||||
amount, fee_included,
|
||||
&memo, split_amount, confirmations).await?;
|
||||
let tx_plan = serde_json::to_string(&tx_plan)?;
|
||||
Ok::<_, anyhow::Error>(tx_plan)
|
||||
|
@ -444,9 +444,10 @@ pub async unsafe extern "C" fn transfer_pools(
|
|||
pub async unsafe extern "C" fn shield_taddr(
|
||||
coin: u8,
|
||||
account: u32,
|
||||
amount: u64,
|
||||
confirmations: u32,
|
||||
) -> CResult<*mut c_char> {
|
||||
let res = crate::api::payment_v2::shield_taddr(coin, account, confirmations).await;
|
||||
let res = crate::api::payment_v2::shield_taddr(coin, account, amount, confirmations).await;
|
||||
to_cresult_str(res)
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,15 @@ pub async fn build_tx_plan(
|
|||
) -> note_selection::Result<TransactionPlan> {
|
||||
let c = CoinConfig::get(coin);
|
||||
let network = c.chain.network();
|
||||
|
||||
let mut recipient_fee = false;
|
||||
for r in recipients {
|
||||
if r.fee_included {
|
||||
if recipient_fee { return Err(TransactionBuilderError::DuplicateRecipientFee) }
|
||||
recipient_fee = true;
|
||||
}
|
||||
}
|
||||
|
||||
let (fvk, checkpoint_height) = {
|
||||
let db = c.db()?;
|
||||
let AccountData { fvk, .. } = db.get_account_info(account)?;
|
||||
|
@ -49,7 +58,7 @@ pub async fn build_tx_plan(
|
|||
loop {
|
||||
let a = min(amount, max_amount_per_note);
|
||||
let memo_bytes: MemoBytes = r.memo.clone().into();
|
||||
let order = Order::new(network, id_order, &r.address, a, memo_bytes);
|
||||
let order = Order::new(network, id_order, &r.address, a, false, memo_bytes);
|
||||
orders.push(order);
|
||||
amount -= a;
|
||||
id_order += 1;
|
||||
|
@ -57,6 +66,7 @@ pub async fn build_tx_plan(
|
|||
break;
|
||||
} // at least one note even when amount = 0
|
||||
}
|
||||
orders.last_mut().unwrap().take_fee = r.fee_included;
|
||||
}
|
||||
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
|
||||
|
||||
|
@ -144,36 +154,25 @@ pub async fn build_max_tx(
|
|||
Err(TransactionBuilderError::TxTooComplex)
|
||||
}
|
||||
|
||||
pub enum AmountOrMax {
|
||||
Amount(u64),
|
||||
Max
|
||||
}
|
||||
|
||||
pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: AmountOrMax,
|
||||
memo: &str, split_amount: u64, confirmations: u32) -> anyhow::Result<TransactionPlan> {
|
||||
pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: u64,
|
||||
fee_included: bool, memo: &str, split_amount: u64, confirmations: u32) -> anyhow::Result<TransactionPlan> {
|
||||
let address = get_unified_address(coin, account, to_pool)?; // get our own unified address
|
||||
let a = match amount {
|
||||
AmountOrMax::Amount(a) => a,
|
||||
AmountOrMax::Max => 0,
|
||||
};
|
||||
let recipient = RecipientMemo {
|
||||
address,
|
||||
amount: a,
|
||||
amount,
|
||||
fee_included,
|
||||
memo: Memo::from_str(memo)?,
|
||||
max_amount_per_note: split_amount,
|
||||
};
|
||||
let last_height = get_latest_height().await?;
|
||||
let tx_plan = match amount {
|
||||
AmountOrMax::Amount(_) => build_tx_plan(coin, account, last_height, slice::from_ref(&recipient),
|
||||
!from_pool, confirmations).await?,
|
||||
AmountOrMax::Max => build_max_tx(coin, account, last_height, &recipient, !from_pool, confirmations).await?,
|
||||
};
|
||||
let tx_plan = build_tx_plan(coin, account, last_height, slice::from_ref(&recipient),
|
||||
!from_pool, confirmations).await?;
|
||||
Ok(tx_plan)
|
||||
}
|
||||
|
||||
/// Make a transaction that shields the transparent balance
|
||||
pub async fn shield_taddr(coin: u8, account: u32, confirmations: u32) -> anyhow::Result<String> {
|
||||
let tx_plan = transfer_pools(coin, account, 1, 6, AmountOrMax::Max, "Shield Transparent Balance", 0, confirmations).await?;
|
||||
pub async fn shield_taddr(coin: u8, account: u32, amount: u64, confirmations: u32) -> anyhow::Result<String> {
|
||||
let tx_plan = transfer_pools(coin, account, 1, 6, amount, true, "Shield Transparent Balance", 0, confirmations).await?;
|
||||
let tx_id = sign_and_broadcast(coin, account, &tx_plan).await?;
|
||||
log::info!("TXID: {}", tx_id);
|
||||
Ok(tx_id)
|
||||
|
|
|
@ -8,6 +8,7 @@ use zcash_primitives::memo::Memo;
|
|||
pub struct Recipient {
|
||||
pub address: String,
|
||||
pub amount: u64,
|
||||
pub fee_included: bool,
|
||||
pub reply_to: bool,
|
||||
pub subject: String,
|
||||
pub memo: String,
|
||||
|
@ -24,6 +25,7 @@ pub struct RecipientShort {
|
|||
pub struct RecipientMemo {
|
||||
pub address: String,
|
||||
pub amount: u64,
|
||||
pub fee_included: bool,
|
||||
pub memo: Memo,
|
||||
pub max_amount_per_note: u64,
|
||||
}
|
||||
|
@ -38,6 +40,7 @@ impl RecipientMemo {
|
|||
Ok(RecipientMemo {
|
||||
address: r.address.clone(),
|
||||
amount: r.amount,
|
||||
fee_included: r.fee_included,
|
||||
memo: Memo::from_str(&memo)?,
|
||||
max_amount_per_note: r.max_amount_per_note,
|
||||
})
|
||||
|
@ -49,6 +52,7 @@ impl From<RecipientShort> for RecipientMemo {
|
|||
RecipientMemo {
|
||||
address: r.address,
|
||||
amount: r.amount,
|
||||
fee_included: false,
|
||||
memo: Memo::Empty,
|
||||
max_amount_per_note: 0,
|
||||
}
|
||||
|
|
22
src/key2.rs
22
src/key2.rs
|
@ -1,10 +1,7 @@
|
|||
use crate::coinconfig::CoinConfig;
|
||||
use anyhow::anyhow;
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use zcash_client_backend::encoding::{
|
||||
decode_extended_full_viewing_key, decode_extended_spending_key,
|
||||
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
|
||||
};
|
||||
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address, decode_transparent_address, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address};
|
||||
use zcash_client_backend::keys::UnifiedFullViewingKey;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
|
||||
|
@ -80,8 +77,21 @@ pub fn is_valid_key(coin: u8, key: &str) -> i8 {
|
|||
pub fn is_valid_address(coin: u8, address: &str) -> bool {
|
||||
let c = CoinConfig::get(coin);
|
||||
let network = c.chain.network();
|
||||
let recipient = zcash_client_backend::address::RecipientAddress::decode(network, address);
|
||||
recipient.is_some()
|
||||
if decode_payment_address(network.hrp_sapling_payment_address(), address).is_ok() {
|
||||
true
|
||||
}
|
||||
else if let Ok(Some(_)) = decode_transparent_address(
|
||||
&network.b58_pubkey_address_prefix(),
|
||||
&network.b58_script_address_prefix(),
|
||||
address) {
|
||||
true
|
||||
}
|
||||
else if zcash_client_backend::address::RecipientAddress::decode(network, address).is_some() {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_secret_key(
|
||||
|
|
|
@ -19,6 +19,10 @@ use zcash_primitives::memo::Memo;
|
|||
pub enum TransactionBuilderError {
|
||||
#[error("Not enough funds: Missing {0} zats")]
|
||||
NotEnoughFunds(u64),
|
||||
#[error("Only one recipient can pay for the fees")]
|
||||
DuplicateRecipientFee,
|
||||
#[error("Recipient amount too low for the fees")]
|
||||
RecipientCannotPayFee,
|
||||
#[error("Tx too complex")]
|
||||
TxTooComplex,
|
||||
#[error(transparent)]
|
||||
|
@ -47,7 +51,8 @@ pub fn recipients_to_orders(network: &Network, recipients: &[Recipient]) -> Resu
|
|||
Ok::<_, TransactionBuilderError>(Order {
|
||||
id: i as u32,
|
||||
destinations,
|
||||
amount: r.amount,
|
||||
raw_amount: r.amount,
|
||||
take_fee: r.fee_included, // Caller must make sure that at most one recipient pays for the fees
|
||||
memo: Memo::from_str(&r.memo).unwrap().into(),
|
||||
})
|
||||
})
|
||||
|
|
|
@ -34,9 +34,10 @@ pub fn group_orders(orders: &[Order], fee: u64) -> Result<(Vec<OrderInfo>, Order
|
|||
group_type |= 1 << i;
|
||||
}
|
||||
}
|
||||
let amount = order.amount(fee)?;
|
||||
order_info.push(OrderInfo {
|
||||
group_type,
|
||||
amount: order.amount,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -189,6 +190,7 @@ pub fn fill(
|
|||
order_infos: &[OrderInfo],
|
||||
amounts: &OrderGroupAmounts,
|
||||
allocation: &FundAllocation,
|
||||
fee: u64
|
||||
) -> Result<Vec<Fill>> {
|
||||
assert_eq!(orders.len(), order_infos.len());
|
||||
let mut fills = vec![];
|
||||
|
@ -202,7 +204,7 @@ pub fn fill(
|
|||
let fill = Fill {
|
||||
id_order: Some(order.id),
|
||||
destination: order.destinations[ilog2(info.group_type)].unwrap(),
|
||||
amount: order.amount,
|
||||
amount: order.amount(fee)?,
|
||||
memo: order.memo.clone(),
|
||||
};
|
||||
fills.push(fill);
|
||||
|
@ -211,13 +213,13 @@ pub fn fill(
|
|||
let fill1 = Fill {
|
||||
id_order: Some(order.id),
|
||||
destination: order.destinations[1].unwrap(),
|
||||
amount: (order.amount as f64 * f).round() as u64,
|
||||
amount: (order.amount(fee)? as f64 * f).round() as u64,
|
||||
memo: order.memo.clone(),
|
||||
};
|
||||
let fill2 = Fill {
|
||||
id_order: Some(order.id),
|
||||
destination: order.destinations[2].unwrap(),
|
||||
amount: order.amount - fill1.amount,
|
||||
amount: order.amount(fee)? - fill1.amount,
|
||||
memo: order.memo.clone(),
|
||||
};
|
||||
if fill1.amount != 0 {
|
||||
|
@ -310,7 +312,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
|
|||
);
|
||||
let net_chg = [s0 + s1 - s2, o0 + o1 - o2];
|
||||
|
||||
let mut fills = fill(&orders, &groups, &amounts, &allocation)?;
|
||||
let mut fills = fill(&orders, &groups, &amounts, &allocation, fee)?;
|
||||
|
||||
let (notes, change) = select_inputs(&utxos, &allocation)?;
|
||||
let change_destinations = decode(network, &config.change_address)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::ser::MemoBytesProxy;
|
||||
use crate::note_selection::ua::decode;
|
||||
use crate::unified::orchard_as_unified;
|
||||
use crate::Hash;
|
||||
use crate::{Hash, TransactionBuilderError};
|
||||
use orchard::Address;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_hex::{SerHex, Strict};
|
||||
|
@ -103,7 +103,8 @@ pub struct UTXO {
|
|||
pub struct Order {
|
||||
pub id: u32,
|
||||
pub destinations: [Option<Destination>; 3],
|
||||
pub amount: u64,
|
||||
pub raw_amount: u64,
|
||||
pub take_fee: bool,
|
||||
#[serde(with = "MemoBytesProxy")]
|
||||
pub memo: MemoBytes,
|
||||
}
|
||||
|
@ -200,13 +201,24 @@ impl Destination {
|
|||
}
|
||||
|
||||
impl Order {
|
||||
pub fn new(network: &Network, id: u32, address: &str, amount: u64, memo: MemoBytes) -> Self {
|
||||
pub fn new(network: &Network, id: u32, address: &str, amount: u64, take_fee: bool, memo: MemoBytes) -> Self {
|
||||
let destinations = decode(network, address).unwrap();
|
||||
Order {
|
||||
id,
|
||||
destinations,
|
||||
amount,
|
||||
raw_amount: amount,
|
||||
take_fee,
|
||||
memo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(&self, fee: u64) -> Result<u64, TransactionBuilderError> {
|
||||
if self.take_fee {
|
||||
if self.raw_amount < fee { return Err(TransactionBuilderError::RecipientCannotPayFee) }
|
||||
Ok(self.raw_amount - fee)
|
||||
}
|
||||
else {
|
||||
Ok(self.raw_amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue