1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use crate::Error;
use chain_crypto::SecretKey;
use chain_impl_mockchain::{
    account::{self, SpendingCounter},
    fragment::Fragment,
    header::BlockDate,
    transaction::{Input, Payload, Transaction, WitnessAccountData},
};
use std::str::FromStr;
use wallet::{
    transaction::{AccountSecretKey, WitnessInput},
    AccountId, AccountWitnessBuilder, EitherAccount, Settings,
};

pub struct TxBuilder<P: Payload> {
    builder: wallet::TransactionBuilder<P, AccountSecretKey, WitnessAccountData, account::Witness>,
}

impl<P: Payload> TxBuilder<P> {
    pub fn new(settings: Settings, payload: P) -> Self {
        let builder = wallet::TransactionBuilder::new(settings, payload, BlockDate::first());
        Self { builder }
    }

    pub fn prepare_tx(
        mut self,
        account_id_hex: String,
        spending_counter: SpendingCounter,
    ) -> Result<Self, Error> {
        let account_id = AccountId::from_str(&account_id_hex)
            .map_err(|e| Error::wallet_transaction().with(e))?;

        // It is needed to provide a 1 extra input as we are generating it later, but should take into account at this place.
        let value = self.builder.estimate_fee_with(1, 0);
        let input = Input::from_account_public_key(account_id.into(), value);
        self.builder
            .add_input(input, AccountWitnessBuilder(spending_counter));
        Ok(self)
    }

    pub fn get_sign_data(&self) -> Result<WitnessAccountData, Error> {
        let data = self
            .builder
            .get_sign_data()
            .map_err(|e| Error::wallet_transaction().with(e))?;
        // as inside build_tx() function has been inserted only 1 input, valid tx should contains only first witness sign data
        data.into_iter().next().ok_or_else(Error::invalid_fragment)
    }

    pub fn build_tx(
        self,
        auth: P::Auth,
        signature: &[u8],
        fragment_build_fn: impl FnOnce(Transaction<P>) -> Fragment,
    ) -> Result<Fragment, Error> {
        // as inside build_tx() function has been inserted only 1 input, we should put only 1 witness input
        let witness_input = vec![WitnessInput::Signature(
            account::Witness::from_binary(signature).unwrap(),
        )];
        Ok(fragment_build_fn(
            self.builder
                .finalize_tx(auth, witness_input)
                .map_err(|e| Error::wallet_transaction().with(e))?,
        ))
    }

    pub fn sign_tx(
        self,
        auth: P::Auth,
        account_bytes: &[u8],
        fragment_build_fn: impl FnOnce(Transaction<P>) -> Fragment,
    ) -> Result<Fragment, Error> {
        let account = EitherAccount::new_from_key(
            SecretKey::from_binary(account_bytes)
                .map_err(|e| Error::wallet_transaction().with(e))?,
        );
        // as inside build_tx() function has been inserted only 1 input, we should put only 1 witness input
        let witness_input = vec![WitnessInput::SecretKey(account.secret_key())];
        Ok(fragment_build_fn(
            self.builder
                .finalize_tx(auth, witness_input)
                .map_err(|e| Error::wallet_transaction().with(e))?,
        ))
    }
}