2022-10-19 23:24:36 -07:00
|
|
|
//! Payments
|
|
|
|
|
2022-09-04 04:19:49 -07:00
|
|
|
use anyhow::anyhow;
|
2022-06-08 05:48:16 -07:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use secp256k1::SecretKey;
|
|
|
|
|
|
|
|
use crate::api::sync::get_latest_height;
|
|
|
|
use crate::coinconfig::{get_prover, CoinConfig};
|
|
|
|
use crate::pay::TxBuilder;
|
2022-06-10 11:41:05 -07:00
|
|
|
pub use crate::{broadcast_tx, Tx};
|
2022-06-08 05:48:16 -07:00
|
|
|
use zcash_client_backend::encoding::{
|
|
|
|
decode_extended_full_viewing_key, decode_extended_spending_key,
|
|
|
|
};
|
|
|
|
use zcash_primitives::consensus::Parameters;
|
|
|
|
use zcash_primitives::transaction::builder::Progress;
|
|
|
|
|
2022-09-04 04:19:49 -07:00
|
|
|
use crate::db::{AccountData, ZMessage};
|
2022-06-08 05:48:16 -07:00
|
|
|
use crate::taddr::get_utxos;
|
|
|
|
use serde::Deserialize;
|
|
|
|
use zcash_primitives::memo::Memo;
|
|
|
|
// use crate::wallet::Recipient;
|
|
|
|
|
|
|
|
type PaymentProgressCallback = Box<dyn Fn(Progress) + Send + Sync>;
|
|
|
|
|
|
|
|
async fn prepare_multi_payment(
|
|
|
|
last_height: u32,
|
|
|
|
recipients: &[RecipientMemo],
|
|
|
|
use_transparent: bool,
|
|
|
|
anchor_offset: u32,
|
|
|
|
) -> anyhow::Result<(Tx, Vec<u32>)> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let mut tx_builder = TxBuilder::new(c.coin_type, last_height);
|
|
|
|
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { fvk, .. } = c.db()?.get_account_info(c.id_account)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let fvk = decode_extended_full_viewing_key(
|
|
|
|
c.chain.network().hrp_sapling_extended_full_viewing_key(),
|
|
|
|
&fvk,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let utxos = if use_transparent {
|
|
|
|
let mut client = c.connect_lwd().await?;
|
|
|
|
let t_address = c.db()?.get_taddr(c.id_account)?;
|
|
|
|
if let Some(t_address) = t_address {
|
|
|
|
get_utxos(&mut client, &t_address, c.id_account).await?
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
|
|
|
|
|
|
|
let target_amount: u64 = recipients.iter().map(|r| r.amount).sum();
|
|
|
|
let anchor_height = last_height.saturating_sub(anchor_offset);
|
|
|
|
let spendable_notes = c
|
|
|
|
.db()?
|
|
|
|
.get_spendable_notes(c.id_account, anchor_height, &fvk)?;
|
|
|
|
let note_ids = tx_builder.select_inputs(&fvk, &spendable_notes, &utxos, target_amount)?;
|
|
|
|
tx_builder.select_outputs(&fvk, recipients)?;
|
|
|
|
Ok((tx_builder.tx, note_ids))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sign(tx: &Tx, progress_callback: PaymentProgressCallback) -> anyhow::Result<Vec<u8>> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let prover = get_prover();
|
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { sk: zsk, .. } = db.get_account_info(c.id_account)?;
|
|
|
|
let zsk = zsk.ok_or(anyhow!("Cannot sign without secret key"))?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let tsk = db
|
|
|
|
.get_tsk(c.id_account)?
|
|
|
|
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
|
|
|
|
let extsk =
|
|
|
|
decode_extended_spending_key(c.chain.network().hrp_sapling_extended_spending_key(), &zsk)
|
|
|
|
.unwrap();
|
|
|
|
let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?;
|
|
|
|
Ok(raw_tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Build a multi payment for offline signing
|
2022-10-19 23:24:36 -07:00
|
|
|
/// # Arguments
|
|
|
|
/// * `last_height`: current block height
|
|
|
|
/// * `recipients`: list of recipients
|
|
|
|
/// * `use_transparent`: include transparent balance
|
|
|
|
/// * `anchor_offset`: minimum number of confirmations for note selection
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn build_only_multi_payment(
|
|
|
|
last_height: u32,
|
|
|
|
recipients: &[RecipientMemo],
|
|
|
|
use_transparent: bool,
|
|
|
|
anchor_offset: u32,
|
2022-06-10 22:40:32 -07:00
|
|
|
) -> anyhow::Result<Tx> {
|
2022-06-08 05:48:16 -07:00
|
|
|
let (tx, _) =
|
|
|
|
prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?;
|
2022-06-10 22:40:32 -07:00
|
|
|
// let tx_str = serde_json::to_string(&tx)?;
|
|
|
|
Ok(tx)
|
2022-06-08 05:48:16 -07:00
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Sign a transaction
|
|
|
|
/// # Arguments
|
|
|
|
/// * `tx`: transaction to sign
|
|
|
|
/// * `progress_callback`: function callback during transaction building
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn sign_only_multi_payment(
|
2022-06-10 22:40:32 -07:00
|
|
|
tx: &Tx,
|
2022-06-08 05:48:16 -07:00
|
|
|
progress_callback: PaymentProgressCallback,
|
|
|
|
) -> anyhow::Result<Vec<u8>> {
|
2022-06-10 22:40:32 -07:00
|
|
|
// let tx = serde_json::from_str::<Tx>(tx_string)?;
|
2022-07-21 18:08:29 -07:00
|
|
|
let raw_tx = sign(tx, progress_callback)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
Ok(raw_tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Build, sign and broadcast a multi payment
|
2022-10-19 23:24:36 -07:00
|
|
|
/// # Arguments
|
|
|
|
/// * `last_height`: current block height
|
|
|
|
/// * `recipients`: list of recipients
|
|
|
|
/// * `use_transparent`: include transparent balance
|
|
|
|
/// * `anchor_offset`: minimum number of confirmations for note selection
|
|
|
|
/// * `progress_callback`: function callback during transaction building
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn build_sign_send_multi_payment(
|
|
|
|
last_height: u32,
|
|
|
|
recipients: &[RecipientMemo],
|
|
|
|
use_transparent: bool,
|
|
|
|
anchor_offset: u32,
|
|
|
|
progress_callback: PaymentProgressCallback,
|
|
|
|
) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let (tx, note_ids) =
|
|
|
|
prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?;
|
|
|
|
let raw_tx = sign(&tx, progress_callback)?;
|
|
|
|
let tx_id = broadcast_tx(&raw_tx).await?;
|
|
|
|
|
|
|
|
c.db()?.tx_mark_spend(¬e_ids)?;
|
2022-09-27 22:13:35 -07:00
|
|
|
let mut mempool = c.mempool.lock().unwrap();
|
|
|
|
mempool.clear()?;
|
2022-06-08 05:48:16 -07:00
|
|
|
Ok(tx_id)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Make a transaction that shields the transparent balance
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn shield_taddr() -> anyhow::Result<String> {
|
|
|
|
let last_height = get_latest_height().await?;
|
|
|
|
let tx_id = build_sign_send_multi_payment(last_height, &[], true, 0, Box::new(|_| {})).await?;
|
|
|
|
Ok(tx_id)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Parse a json document that contains a list of recipients
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn parse_recipients(recipients: &str) -> anyhow::Result<Vec<RecipientMemo>> {
|
|
|
|
let c = CoinConfig::get_active();
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { address, .. } = c.db()?.get_account_info(c.id_account)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let recipients: Vec<Recipient> = serde_json::from_str(recipients)?;
|
|
|
|
let recipient_memos: Vec<_> = recipients
|
|
|
|
.iter()
|
|
|
|
.map(|r| RecipientMemo::from_recipient(&address, r))
|
|
|
|
.collect();
|
|
|
|
Ok(recipient_memos)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Encode a message into a memo
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn encode_memo(from: &str, include_from: bool, subject: &str, body: &str) -> String {
|
|
|
|
let from = if include_from { from } else { "" };
|
|
|
|
let msg = format!("\u{1F6E1}MSG\n{}\n{}\n{}", from, subject, body);
|
|
|
|
msg
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Decode a memo into a message
|
2022-09-05 08:05:55 -07:00
|
|
|
pub fn decode_memo(
|
|
|
|
id_tx: u32,
|
|
|
|
memo: &str,
|
|
|
|
recipient: &str,
|
|
|
|
timestamp: u32,
|
|
|
|
height: u32,
|
|
|
|
) -> ZMessage {
|
2022-06-08 05:48:16 -07:00
|
|
|
let memo_lines: Vec<_> = memo.splitn(4, '\n').collect();
|
|
|
|
let msg = if memo_lines[0] == "\u{1F6E1}MSG" {
|
|
|
|
ZMessage {
|
2022-09-05 08:05:55 -07:00
|
|
|
id_tx,
|
2022-06-08 05:48:16 -07:00
|
|
|
sender: if memo_lines[1].is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(memo_lines[1].to_string())
|
|
|
|
},
|
|
|
|
recipient: recipient.to_string(),
|
|
|
|
subject: memo_lines[2].to_string(),
|
|
|
|
body: memo_lines[3].to_string(),
|
|
|
|
timestamp,
|
|
|
|
height,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ZMessage {
|
2022-09-05 08:05:55 -07:00
|
|
|
id_tx,
|
2022-06-08 05:48:16 -07:00
|
|
|
sender: None,
|
|
|
|
recipient: recipient.to_string(),
|
|
|
|
subject: memo_lines[0].chars().take(20).collect(),
|
|
|
|
body: memo.to_string(),
|
|
|
|
timestamp,
|
|
|
|
height,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct Recipient {
|
|
|
|
pub address: String,
|
|
|
|
pub amount: u64,
|
|
|
|
pub reply_to: bool,
|
|
|
|
pub subject: String,
|
|
|
|
pub memo: String,
|
|
|
|
pub max_amount_per_note: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct RecipientMemo {
|
|
|
|
pub address: String,
|
|
|
|
pub amount: u64,
|
|
|
|
pub memo: Memo,
|
|
|
|
pub max_amount_per_note: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RecipientMemo {
|
|
|
|
pub fn from_recipient(from: &str, r: &Recipient) -> Self {
|
|
|
|
let memo = if !r.reply_to && r.subject.is_empty() {
|
|
|
|
r.memo.clone()
|
|
|
|
} else {
|
|
|
|
encode_memo(from, r.reply_to, &r.subject, &r.memo)
|
|
|
|
};
|
|
|
|
RecipientMemo {
|
|
|
|
address: r.address.clone(),
|
|
|
|
amount: r.amount,
|
|
|
|
memo: Memo::from_str(&memo).unwrap(),
|
|
|
|
max_amount_per_note: r.max_amount_per_note,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|