use super::{
transaction::{Output, Transaction},
CashNote, CashNoteOutputDetails, DerivationIndex, DerivedSecretKey, Hash, Input, MainPubkey,
NanoTokens, SignedSpend, Spend, UniquePubkey,
};
use crate::{transfers::TransferRecipientDetails, Result, TransferError};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
pub type InputSrcTx = Transaction;
#[derive(custom_debug::Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UnsignedTransfer {
pub tx: Transaction,
pub spends: BTreeSet<(Spend, DerivationIndex)>,
pub change_id: UniquePubkey,
pub output_details: BTreeMap<UniquePubkey, CashNoteOutputDetails>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TransactionBuilder {
inputs: Vec<Input>,
outputs: Vec<Output>,
input_details: BTreeMap<UniquePubkey, (Option<DerivedSecretKey>, InputSrcTx, DerivationIndex)>,
output_details: BTreeMap<UniquePubkey, CashNoteOutputDetails>,
}
impl TransactionBuilder {
pub fn add_input(
mut self,
input: Input,
derived_key: Option<DerivedSecretKey>,
input_src_tx: InputSrcTx,
derivation_index: DerivationIndex,
) -> Self {
self.input_details.insert(
*input.unique_pubkey(),
(derived_key, input_src_tx, derivation_index),
);
self.inputs.push(input);
self
}
pub fn add_inputs(
mut self,
inputs: impl IntoIterator<Item = (Input, Option<DerivedSecretKey>, InputSrcTx, DerivationIndex)>,
) -> Self {
for (input, derived_key, input_src_tx, derivation_index) in inputs.into_iter() {
self = self.add_input(input, derived_key, input_src_tx, derivation_index);
}
self
}
pub fn add_output(
mut self,
token: NanoTokens,
purpose: String,
main_pubkey: MainPubkey,
derivation_index: DerivationIndex,
) -> Self {
let unique_pubkey = main_pubkey.new_unique_pubkey(&derivation_index);
self.output_details.insert(
unique_pubkey,
(purpose.clone(), main_pubkey, derivation_index),
);
let output = Output::new(unique_pubkey, token.as_nano(), purpose);
self.outputs.push(output);
self
}
pub fn add_outputs(
mut self,
outputs: impl IntoIterator<Item = TransferRecipientDetails>,
) -> Self {
for (token, purpose, main_pubkey, derivation_index) in outputs.into_iter() {
self = self.add_output(token, purpose, main_pubkey, derivation_index);
}
self
}
pub fn build(
self,
reason: Hash,
network_royalties: Vec<DerivationIndex>,
) -> Result<CashNoteBuilder> {
let spent_tx = Transaction {
inputs: self.inputs,
outputs: self.outputs,
};
let mut signed_spends = BTreeSet::new();
for input in &spent_tx.inputs {
if let Some((Some(derived_key), input_src_tx, _)) =
self.input_details.get(&input.unique_pubkey)
{
let spend = Spend {
unique_pubkey: *input.unique_pubkey(),
spent_tx: spent_tx.clone(),
reason,
amount: input.amount,
parent_tx: input_src_tx.clone(),
network_royalties: network_royalties.clone(),
};
let derived_key_sig = derived_key.sign(&spend.to_bytes());
signed_spends.insert(SignedSpend {
spend,
derived_key_sig,
});
}
}
Ok(CashNoteBuilder::new(
spent_tx,
self.output_details,
signed_spends,
))
}
pub fn build_unsigned_transfer(
self,
reason: Hash,
network_royalties: Vec<DerivationIndex>,
change_id: UniquePubkey,
) -> Result<UnsignedTransfer> {
let tx = Transaction {
inputs: self.inputs,
outputs: self.outputs,
};
let mut spends = BTreeSet::new();
for input in &tx.inputs {
if let Some((_, input_src_tx, derivation_index)) =
self.input_details.get(&input.unique_pubkey)
{
let spend = Spend {
unique_pubkey: *input.unique_pubkey(),
spent_tx: tx.clone(),
reason,
amount: input.amount,
parent_tx: input_src_tx.clone(),
network_royalties: network_royalties.clone(),
};
spends.insert((spend, *derivation_index));
}
}
Ok(UnsignedTransfer {
tx,
spends,
change_id,
output_details: self.output_details,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CashNoteBuilder {
pub spent_tx: Transaction,
pub output_details: BTreeMap<UniquePubkey, CashNoteOutputDetails>,
pub signed_spends: BTreeSet<SignedSpend>,
}
impl CashNoteBuilder {
pub fn new(
spent_tx: Transaction,
output_details: BTreeMap<UniquePubkey, CashNoteOutputDetails>,
signed_spends: BTreeSet<SignedSpend>,
) -> Self {
Self {
spent_tx,
output_details,
signed_spends,
}
}
pub fn signed_spends(&self) -> Vec<&SignedSpend> {
self.signed_spends.iter().collect()
}
pub fn build(self) -> Result<Vec<(CashNote, NanoTokens)>> {
self.spent_tx
.verify_against_inputs_spent(self.signed_spends.iter())?;
self.build_output_cashnotes()
}
pub fn build_without_verifying(self) -> Result<Vec<(CashNote, NanoTokens)>> {
self.build_output_cashnotes()
}
fn build_output_cashnotes(self) -> Result<Vec<(CashNote, NanoTokens)>> {
self.spent_tx
.outputs
.iter()
.map(|output| {
let (purpose, main_pubkey, derivation_index) = self
.output_details
.get(&output.unique_pubkey)
.ok_or(TransferError::UniquePubkeyNotFound)?;
Ok((
CashNote {
unique_pubkey: main_pubkey.new_unique_pubkey(derivation_index),
parent_tx: self.spent_tx.clone(),
parent_spends: self.signed_spends.clone(),
purpose: purpose.clone(),
main_pubkey: *main_pubkey,
derivation_index: *derivation_index,
},
output.amount,
))
})
.collect()
}
}