2021-08-05 06:38:48 -07:00
|
|
|
use crate::db::SpendableNote;
|
2022-06-08 05:48:16 -07:00
|
|
|
// use crate::wallet::RecipientMemo;
|
|
|
|
use crate::api::payment::RecipientMemo;
|
|
|
|
use crate::coinconfig::CoinConfig;
|
|
|
|
use crate::{get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction};
|
2022-06-07 09:58:24 -07:00
|
|
|
use anyhow::anyhow;
|
2021-08-16 06:07:04 -07:00
|
|
|
use jubjub::Fr;
|
2021-11-11 17:39:50 -08:00
|
|
|
use rand::prelude::SliceRandom;
|
2021-08-16 06:07:04 -07:00
|
|
|
use rand::rngs::OsRng;
|
2021-11-11 17:39:50 -08:00
|
|
|
use secp256k1::SecretKey;
|
2021-08-16 06:07:04 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-11-11 17:39:50 -08:00
|
|
|
use std::sync::mpsc;
|
2021-08-16 06:07:04 -07:00
|
|
|
use tonic::Request;
|
|
|
|
use zcash_client_backend::address::RecipientAddress;
|
|
|
|
use zcash_client_backend::encoding::{
|
2021-11-11 17:39:50 -08:00
|
|
|
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
|
|
|
|
encode_payment_address,
|
2021-08-16 06:07:04 -07:00
|
|
|
};
|
2022-06-07 09:58:24 -07:00
|
|
|
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
|
2021-11-11 17:39:50 -08:00
|
|
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
2022-06-07 09:58:24 -07:00
|
|
|
use zcash_primitives::keys::OutgoingViewingKey;
|
2021-11-11 17:39:50 -08:00
|
|
|
use zcash_primitives::legacy::Script;
|
|
|
|
use zcash_primitives::memo::{Memo, MemoBytes};
|
2021-08-16 06:07:04 -07:00
|
|
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
2021-11-11 17:39:50 -08:00
|
|
|
use zcash_primitives::sapling::prover::TxProver;
|
|
|
|
use zcash_primitives::sapling::{Diversifier, Node, PaymentAddress, Rseed};
|
|
|
|
use zcash_primitives::transaction::builder::{Builder, Progress};
|
|
|
|
use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY};
|
|
|
|
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut as ZTxOut};
|
2021-08-16 06:07:04 -07:00
|
|
|
use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
|
2021-08-05 06:38:48 -07:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Tx {
|
2022-03-07 06:47:06 -08:00
|
|
|
pub coin_type: CoinType,
|
2021-11-11 17:39:50 -08:00
|
|
|
pub height: u32,
|
|
|
|
pub t_inputs: Vec<TTxIn>,
|
|
|
|
pub inputs: Vec<TxIn>,
|
|
|
|
pub outputs: Vec<TxOut>,
|
|
|
|
pub change: String,
|
|
|
|
pub ovk: String,
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Tx {
|
2022-03-07 06:47:06 -08:00
|
|
|
pub fn new(coin_type: CoinType, height: u32) -> Self {
|
2021-08-05 06:38:48 -07:00
|
|
|
Tx {
|
2022-03-07 06:47:06 -08:00
|
|
|
coin_type,
|
2021-08-05 06:38:48 -07:00
|
|
|
height,
|
2021-11-11 17:39:50 -08:00
|
|
|
t_inputs: vec![],
|
2021-08-05 06:38:48 -07:00
|
|
|
inputs: vec![],
|
|
|
|
outputs: vec![],
|
2021-11-11 17:39:50 -08:00
|
|
|
change: "".to_string(),
|
|
|
|
ovk: "".to_string(),
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct TxIn {
|
2021-11-11 17:39:50 -08:00
|
|
|
pub diversifier: String,
|
|
|
|
pub fvk: String,
|
|
|
|
pub amount: u64,
|
|
|
|
pub rseed: String,
|
|
|
|
pub witness: String,
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
2021-11-11 17:39:50 -08:00
|
|
|
pub struct TTxIn {
|
|
|
|
pub op: String,
|
|
|
|
pub n: u32,
|
|
|
|
pub amount: u64,
|
|
|
|
pub script: String,
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
|
|
|
pub struct TxOut {
|
|
|
|
pub addr: String,
|
|
|
|
pub amount: u64,
|
|
|
|
pub ovk: String,
|
|
|
|
pub memo: String,
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2022-06-21 17:18:47 -07:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
pub struct TxSummary {
|
|
|
|
pub recipients: Vec<RecipientSummary>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
pub struct RecipientSummary {
|
|
|
|
pub address: String,
|
|
|
|
pub amount: u64,
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
pub struct TxBuilder {
|
2021-08-05 06:38:48 -07:00
|
|
|
pub tx: Tx,
|
2022-03-07 06:47:06 -08:00
|
|
|
coin_type: CoinType,
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
impl TxBuilder {
|
2022-03-07 06:47:06 -08:00
|
|
|
pub fn new(coin_type: CoinType, height: u32) -> Self {
|
2021-11-11 17:39:50 -08:00
|
|
|
TxBuilder {
|
2022-03-07 06:47:06 -08:00
|
|
|
coin_type,
|
|
|
|
tx: Tx::new(coin_type, height),
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
fn add_t_input(&mut self, op: OutPoint, amount: u64, script: &[u8]) {
|
|
|
|
self.tx.t_inputs.push(TTxIn {
|
|
|
|
op: hex::encode(op.hash()),
|
|
|
|
n: op.n(),
|
|
|
|
amount,
|
|
|
|
script: hex::encode(script),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_z_input(
|
2021-08-16 06:07:04 -07:00
|
|
|
&mut self,
|
|
|
|
diversifier: &Diversifier,
|
|
|
|
fvk: &ExtendedFullViewingKey,
|
|
|
|
amount: Amount,
|
|
|
|
rseed: &[u8],
|
|
|
|
witness: &[u8],
|
|
|
|
) -> anyhow::Result<()> {
|
2021-08-05 06:38:48 -07:00
|
|
|
let tx_in = TxIn {
|
|
|
|
diversifier: hex::encode(diversifier.0),
|
2021-08-16 06:07:04 -07:00
|
|
|
fvk: encode_extended_full_viewing_key(
|
2022-06-07 09:58:24 -07:00
|
|
|
self.chain()
|
|
|
|
.network()
|
|
|
|
.hrp_sapling_extended_full_viewing_key(),
|
2022-06-08 05:48:16 -07:00
|
|
|
fvk,
|
2021-08-16 06:07:04 -07:00
|
|
|
),
|
2021-08-05 06:38:48 -07:00
|
|
|
amount: u64::from(amount),
|
|
|
|
rseed: hex::encode(rseed),
|
|
|
|
witness: hex::encode(witness),
|
|
|
|
};
|
|
|
|
self.tx.inputs.push(tx_in);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> {
|
|
|
|
let tx_out = TxOut {
|
|
|
|
addr: address.to_string(),
|
|
|
|
amount: u64::from(amount),
|
|
|
|
ovk: String::new(),
|
|
|
|
memo: String::new(),
|
|
|
|
};
|
|
|
|
self.tx.outputs.push(tx_out);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-08-16 06:07:04 -07:00
|
|
|
fn add_z_output(
|
|
|
|
&mut self,
|
|
|
|
address: &str,
|
|
|
|
ovk: &OutgoingViewingKey,
|
|
|
|
amount: Amount,
|
2021-09-08 07:10:22 -07:00
|
|
|
memo: &Memo,
|
2021-08-16 06:07:04 -07:00
|
|
|
) -> anyhow::Result<()> {
|
2021-08-05 06:38:48 -07:00
|
|
|
let tx_out = TxOut {
|
|
|
|
addr: address.to_string(),
|
|
|
|
amount: u64::from(amount),
|
|
|
|
ovk: hex::encode(ovk.0),
|
2021-09-08 07:10:22 -07:00
|
|
|
memo: hex::encode(MemoBytes::from(memo).as_slice()),
|
2021-08-05 06:38:48 -07:00
|
|
|
};
|
|
|
|
self.tx.outputs.push(tx_out);
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-10-11 02:13:36 -07:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
fn set_change(
|
2021-08-16 06:07:04 -07:00
|
|
|
&mut self,
|
2021-11-11 17:39:50 -08:00
|
|
|
ovk: &OutgoingViewingKey,
|
|
|
|
address: &PaymentAddress,
|
2021-08-16 06:07:04 -07:00
|
|
|
) -> anyhow::Result<()> {
|
2022-06-07 09:58:24 -07:00
|
|
|
self.tx.change = encode_payment_address(
|
|
|
|
self.chain().network().hrp_sapling_payment_address(),
|
|
|
|
address,
|
|
|
|
);
|
2021-11-11 17:39:50 -08:00
|
|
|
self.tx.ovk = hex::encode(ovk.0);
|
2021-08-05 06:38:48 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
/// Add inputs to the transaction
|
|
|
|
///
|
|
|
|
/// Select utxos and shielded notes and add them to
|
|
|
|
/// the transaction
|
|
|
|
///
|
|
|
|
/// Returns an array of received note ids
|
|
|
|
pub fn select_inputs(
|
|
|
|
&mut self,
|
|
|
|
fvk: &ExtendedFullViewingKey,
|
|
|
|
notes: &[SpendableNote],
|
|
|
|
utxos: &[GetAddressUtxosReply],
|
|
|
|
target_amount: u64,
|
|
|
|
) -> anyhow::Result<Vec<u32>> {
|
|
|
|
let mut selected_notes: Vec<u32> = vec![];
|
|
|
|
let target_amount = Amount::from_u64(target_amount).unwrap();
|
|
|
|
let mut t_amount = Amount::zero();
|
|
|
|
// If we use the transparent address, we use all the utxos
|
|
|
|
if !utxos.is_empty() {
|
|
|
|
for utxo in utxos.iter() {
|
|
|
|
let mut tx_hash = [0u8; 32];
|
|
|
|
tx_hash.copy_from_slice(&utxo.txid);
|
|
|
|
let op = OutPoint::new(tx_hash, utxo.index as u32);
|
|
|
|
self.add_t_input(op, utxo.value_zat as u64, &utxo.script);
|
|
|
|
t_amount += Amount::from_i64(utxo.value_zat).unwrap();
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
2022-06-07 09:58:24 -07:00
|
|
|
let target_amount_with_fee =
|
|
|
|
(target_amount + DEFAULT_FEE).ok_or(anyhow!("Invalid amount"))?;
|
2021-11-11 17:39:50 -08:00
|
|
|
if target_amount_with_fee > t_amount {
|
|
|
|
// We need to use some shielded notes because the transparent balance is not enough
|
2022-03-14 19:40:08 -07:00
|
|
|
let mut amount = (target_amount_with_fee - t_amount).unwrap();
|
2021-11-11 17:39:50 -08:00
|
|
|
|
|
|
|
// Pick spendable notes until we exceed the target_amount_with_fee or we ran out of notes
|
|
|
|
let mut notes = notes.to_vec();
|
|
|
|
notes.shuffle(&mut OsRng);
|
|
|
|
|
|
|
|
for n in notes.iter() {
|
|
|
|
if amount.is_positive() {
|
|
|
|
let a = amount.min(
|
|
|
|
Amount::from_u64(n.note.value)
|
|
|
|
.map_err(|_| anyhow::anyhow!("Invalid amount"))?,
|
|
|
|
);
|
|
|
|
amount -= a;
|
|
|
|
let mut witness_bytes: Vec<u8> = vec![];
|
|
|
|
n.witness.write(&mut witness_bytes)?;
|
|
|
|
if let Rseed::BeforeZip212(rseed) = n.note.rseed {
|
|
|
|
// rseed are stored as pre-zip212
|
|
|
|
self.add_z_input(
|
|
|
|
&n.diversifier,
|
|
|
|
fvk,
|
|
|
|
Amount::from_u64(n.note.value).unwrap(),
|
|
|
|
&rseed.to_bytes(),
|
|
|
|
&witness_bytes,
|
|
|
|
)?;
|
|
|
|
selected_notes.push(n.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if amount.is_positive() {
|
|
|
|
log::info!("Not enough balance");
|
|
|
|
anyhow::bail!(
|
|
|
|
"Not enough balance, need {} zats, missing {} zats",
|
|
|
|
u64::from(target_amount_with_fee),
|
|
|
|
u64::from(amount)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(selected_notes)
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
/// Add outputs
|
|
|
|
///
|
|
|
|
/// Expand the recipients if their amount exceeds the max amount per note
|
|
|
|
/// Set the change
|
|
|
|
pub fn select_outputs(
|
2021-08-16 06:07:04 -07:00
|
|
|
&mut self,
|
2021-11-11 17:39:50 -08:00
|
|
|
fvk: &ExtendedFullViewingKey,
|
|
|
|
recipients: &[RecipientMemo],
|
2021-08-16 06:07:04 -07:00
|
|
|
) -> anyhow::Result<()> {
|
2021-11-11 17:39:50 -08:00
|
|
|
let ovk = &fvk.fvk.ovk;
|
2022-03-14 19:40:08 -07:00
|
|
|
let (_, change) = fvk.default_address();
|
2022-06-08 05:48:16 -07:00
|
|
|
self.set_change(ovk, &change)?;
|
2021-11-11 17:39:50 -08:00
|
|
|
|
|
|
|
for r in recipients.iter() {
|
2022-03-07 06:47:06 -08:00
|
|
|
let to_addr = RecipientAddress::decode(self.chain().network(), &r.address)
|
2021-11-11 17:39:50 -08:00
|
|
|
.ok_or(anyhow::anyhow!("Invalid address"))?;
|
|
|
|
let memo = &r.memo;
|
|
|
|
|
|
|
|
let amount = Amount::from_u64(r.amount).unwrap();
|
|
|
|
let max_amount_per_note = r.max_amount_per_note;
|
|
|
|
let max_amount_per_note = if max_amount_per_note != 0 {
|
|
|
|
Amount::from_u64(max_amount_per_note).unwrap()
|
|
|
|
} else {
|
|
|
|
Amount::from_i64(MAX_MONEY).unwrap()
|
|
|
|
};
|
|
|
|
|
2021-11-17 20:56:29 -08:00
|
|
|
let mut is_first = true; // make at least an output note
|
2021-11-11 17:39:50 -08:00
|
|
|
let mut remaining_amount = amount;
|
2021-11-17 20:56:29 -08:00
|
|
|
while remaining_amount.is_positive() || is_first {
|
|
|
|
is_first = false;
|
2021-11-11 17:39:50 -08:00
|
|
|
let note_amount = remaining_amount.min(max_amount_per_note);
|
|
|
|
remaining_amount -= note_amount;
|
|
|
|
|
|
|
|
match &to_addr {
|
|
|
|
RecipientAddress::Shielded(_pa) => {
|
|
|
|
log::info!("Sapling output: {}", r.amount);
|
2022-06-08 05:48:16 -07:00
|
|
|
self.add_z_output(&r.address, ovk, note_amount, memo)
|
2021-11-11 17:39:50 -08:00
|
|
|
}
|
|
|
|
RecipientAddress::Transparent(_address) => {
|
|
|
|
self.add_t_output(&r.address, note_amount)
|
|
|
|
}
|
|
|
|
}?;
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
2021-10-11 02:13:36 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-03-07 06:47:06 -08:00
|
|
|
|
2022-06-07 09:58:24 -07:00
|
|
|
fn chain(&self) -> &dyn CoinChain {
|
|
|
|
get_coin_chain(self.coin_type)
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
impl Tx {
|
|
|
|
/// Sign the transaction with the transparent and shielded secret keys
|
|
|
|
///
|
|
|
|
/// Returns the raw transaction bytes
|
|
|
|
pub fn sign(
|
|
|
|
&self,
|
|
|
|
tsk: Option<SecretKey>,
|
|
|
|
zsk: &ExtendedSpendingKey,
|
2022-03-14 19:40:08 -07:00
|
|
|
prover: &impl TxProver,
|
2021-11-11 17:39:50 -08:00
|
|
|
progress_callback: impl Fn(Progress) + Send + 'static,
|
|
|
|
) -> anyhow::Result<Vec<u8>> {
|
2022-03-07 06:47:06 -08:00
|
|
|
let chain = get_coin_chain(self.coin_type);
|
2021-11-11 17:39:50 -08:00
|
|
|
let last_height = BlockHeight::from_u32(self.height as u32);
|
2022-03-07 06:47:06 -08:00
|
|
|
let mut builder = Builder::new(*chain.network(), last_height);
|
2022-06-20 02:05:11 -07:00
|
|
|
let efvk = ExtendedFullViewingKey::from(zsk);
|
2021-11-11 17:39:50 -08:00
|
|
|
|
|
|
|
let ovk = hex_to_hash(&self.ovk)?;
|
|
|
|
builder.send_change_to(
|
|
|
|
OutgoingViewingKey(ovk),
|
2022-03-07 06:47:06 -08:00
|
|
|
decode_payment_address(chain.network().hrp_sapling_payment_address(), &self.change)
|
2021-11-11 17:39:50 -08:00
|
|
|
.unwrap()
|
|
|
|
.unwrap(),
|
2021-08-16 06:07:04 -07:00
|
|
|
);
|
2021-08-05 06:38:48 -07:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
if let Some(tsk) = tsk {
|
|
|
|
for txin in self.t_inputs.iter() {
|
|
|
|
let mut txid = [0u8; 32];
|
|
|
|
hex::decode_to_slice(&txin.op, &mut txid)?;
|
|
|
|
builder.add_transparent_input(
|
|
|
|
tsk,
|
|
|
|
OutPoint::new(txid, txin.n),
|
|
|
|
ZTxOut {
|
|
|
|
value: Amount::from_u64(txin.amount).unwrap(),
|
|
|
|
script_pubkey: Script(hex::decode(&txin.script).unwrap()),
|
|
|
|
},
|
|
|
|
)?;
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
2021-11-11 17:39:50 -08:00
|
|
|
} else if !self.t_inputs.is_empty() {
|
|
|
|
anyhow::bail!("Missing secret key of transparent account");
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
for txin in self.inputs.iter() {
|
|
|
|
let mut diversifier = [0u8; 11];
|
|
|
|
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
|
|
|
|
let diversifier = Diversifier(diversifier);
|
|
|
|
let fvk = decode_extended_full_viewing_key(
|
2022-03-07 06:47:06 -08:00
|
|
|
chain.network().hrp_sapling_extended_full_viewing_key(),
|
2021-11-11 17:39:50 -08:00
|
|
|
&txin.fvk,
|
|
|
|
)?
|
2021-08-16 06:07:04 -07:00
|
|
|
.unwrap();
|
2022-06-20 02:05:11 -07:00
|
|
|
if fvk != efvk {
|
|
|
|
anyhow::bail!("Incorrect account - Secret key mismatch")
|
|
|
|
}
|
2021-11-11 17:39:50 -08:00
|
|
|
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
|
|
|
|
let mut rseed_bytes = [0u8; 32];
|
|
|
|
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
|
|
|
|
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
|
|
|
|
let note = pa
|
|
|
|
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
|
|
|
|
.unwrap();
|
|
|
|
let w = hex::decode(&txin.witness)?;
|
|
|
|
let witness = IncrementalWitness::<Node>::read(&*w)?;
|
|
|
|
let merkle_path = witness.path().unwrap();
|
2021-08-05 06:38:48 -07:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
builder.add_sapling_spend(zsk.clone(), diversifier, note, merkle_path)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for txout in self.outputs.iter() {
|
2022-03-07 06:47:06 -08:00
|
|
|
let recipient = RecipientAddress::decode(chain.network(), &txout.addr).unwrap();
|
2021-11-11 17:39:50 -08:00
|
|
|
let amount = Amount::from_u64(txout.amount).unwrap();
|
|
|
|
match recipient {
|
|
|
|
RecipientAddress::Transparent(ta) => {
|
|
|
|
builder.add_transparent_output(&ta, amount)?;
|
|
|
|
}
|
|
|
|
RecipientAddress::Shielded(pa) => {
|
|
|
|
let mut ovk = [0u8; 32];
|
|
|
|
hex::decode_to_slice(&txout.ovk, &mut ovk)?;
|
|
|
|
let ovk = OutgoingViewingKey(ovk);
|
|
|
|
let mut memo = vec![0; 512];
|
|
|
|
let m = hex::decode(&txout.memo)?;
|
|
|
|
memo[..m.len()].copy_from_slice(&m);
|
|
|
|
let memo = MemoBytes::from_bytes(&memo)?;
|
2022-03-14 19:40:08 -07:00
|
|
|
builder.add_sapling_output(Some(ovk), pa, amount, memo)?;
|
2021-11-11 17:39:50 -08:00
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
|
|
|
|
|
|
|
|
builder.with_progress_notifier(progress_tx);
|
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Ok(progress) = progress_rx.recv() {
|
|
|
|
log::info!("Progress: {}", progress.cur());
|
|
|
|
progress_callback(progress);
|
|
|
|
}
|
|
|
|
});
|
2022-03-14 19:40:08 -07:00
|
|
|
let (tx, _) = builder.build(prover)?;
|
2021-11-11 17:39:50 -08:00
|
|
|
let mut raw_tx = vec![];
|
|
|
|
tx.write(&mut raw_tx)?;
|
2021-08-05 06:38:48 -07:00
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
Ok(raw_tx)
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
|
|
|
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let mut client = c.connect_lwd().await?;
|
2021-08-05 06:38:48 -07:00
|
|
|
let latest_height = get_latest_height(&mut client).await?;
|
2021-08-16 06:07:04 -07:00
|
|
|
let raw_tx = RawTransaction {
|
2021-08-05 06:38:48 -07:00
|
|
|
data: tx.to_vec(),
|
|
|
|
height: latest_height as u64,
|
|
|
|
};
|
2021-08-16 06:07:04 -07:00
|
|
|
let rep = client
|
|
|
|
.send_transaction(Request::new(raw_tx))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
2021-11-11 17:39:50 -08:00
|
|
|
let code = rep.error_code;
|
|
|
|
if code == 0 {
|
|
|
|
Ok(rep.error_message)
|
|
|
|
} else {
|
|
|
|
Err(anyhow::anyhow!(rep.error_message))
|
|
|
|
}
|
2021-08-05 06:38:48 -07:00
|
|
|
}
|
2022-06-21 17:18:47 -07:00
|
|
|
|
|
|
|
pub fn get_tx_summary(tx: &Tx) -> anyhow::Result<TxSummary> {
|
|
|
|
let mut recipients = vec![];
|
|
|
|
for tx_out in tx.outputs.iter() {
|
|
|
|
recipients.push(RecipientSummary {
|
|
|
|
address: tx_out.addr.clone(),
|
|
|
|
amount: tx_out.amount,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Ok(TxSummary { recipients })
|
|
|
|
}
|