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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
use crate::{Error, Proposal};
use chain_core::property::Serialize as _;
use chain_crypto::SecretKey;
use chain_impl_mockchain::{
account::SpendingCounterIncreasing,
block::BlockDate,
fragment::{Fragment, FragmentId},
value::Value,
vote::Choice,
};
use wallet::{transaction::WitnessInput, AccountId, Settings};
/// the wallet
///
/// * use the `recover` function to recover the wallet from the mnemonics/password;
/// * use the `retrieve_funds` to retrieve initial funds (if necessary) from the block0;
/// then you can use `total_value` to see how much was recovered from the initial block0;
///
pub struct Wallet {
account: wallet::Wallet,
}
impl Wallet {
/// Returns address of the account with the given chain discrimination.
pub fn account(&self, discrimination: chain_addr::Discrimination) -> chain_addr::Address {
self.account.account_id().address(discrimination)
}
pub fn id(&self) -> AccountId {
self.account.account_id()
}
/// Retrieve a wallet from a key used as utxo's
///
/// You can also use this function to recover a wallet even after you have
/// transferred all the funds to the new format
///
/// Parameters
///
/// * `account_key`: the private key used for voting
///
/// # Errors
///
/// The function may fail if:
///
/// TODO
///
pub fn recover_free_keys(account_key: &[u8]) -> Result<Self, Error> {
let account = wallet::Wallet::new_from_key(SecretKey::from_binary(account_key).unwrap());
Ok(Wallet { account })
}
/// use this function to confirm a transaction has been properly received
///
/// This function will automatically update the state of the wallet
pub fn confirm_transaction(&mut self, id: FragmentId) {
self.account.confirm(&id);
}
/// get the current spending counter
///
pub fn spending_counter(&self) -> [u32; SpendingCounterIncreasing::LANES] {
let spending_counters = self.account.spending_counter();
[
spending_counters[0].into(),
spending_counters[1].into(),
spending_counters[2].into(),
spending_counters[3].into(),
spending_counters[4].into(),
spending_counters[5].into(),
spending_counters[6].into(),
spending_counters[7].into(),
]
}
/// get the total value in the wallet
///
/// make sure to call `retrieve_funds` prior to calling this function
/// otherwise you will always have `0`
///
/// Once a conversion has been performed, this value can be use to display
/// how much the wallet started with or retrieved from the chain.
///
pub fn total_value(&self) -> Value {
self.account.value()
}
/// Update the wallet's account state.
///
/// The values to update the account state with can be retrieved from a
/// Jormungandr API endpoint. It sets the balance value on the account
/// as well as the current spending counter.
///
/// It is important to be sure to have an up to date wallet state
/// before doing any transactions, otherwise future transactions may fail
/// to be accepted by the blockchain nodes because of an invalid witness
/// signature.
pub fn set_state(
&mut self,
value: Value,
counters: [u32; SpendingCounterIncreasing::LANES],
) -> Result<(), Error> {
self.account
.set_state(
value,
[
counters[0].into(),
counters[1].into(),
counters[2].into(),
counters[3].into(),
counters[4].into(),
counters[5].into(),
counters[6].into(),
counters[7].into(),
],
)
.map_err(|_| Error::invalid_spending_counters())
}
/// Cast a vote
///
/// This function outputs a fragment containing a voting transaction.
///
/// # Parameters
///
/// * `settings` - ledger settings.
/// * `proposal` - proposal information including the range of values
/// allowed in `choice`.
/// * `choice` - the option to vote for.
///
/// # Errors
///
/// The error is returned when `choice` does not fall withing the range of
/// available choices specified in `proposal`.
pub fn vote(
&mut self,
settings: Settings,
proposal: &Proposal,
choice: Choice,
valid_until: &BlockDate,
lane: u8,
) -> Result<Box<[u8]>, Error> {
let payload = if let Some(payload) = proposal.vote(choice) {
payload
} else {
return Err(Error::wallet_vote_range());
};
let mut builder = wallet::TransactionBuilder::new(settings, payload, *valid_until);
let value = builder.estimate_fee_with(1, 0);
let secret_key = self.account.secret_key();
let account_tx_builder = self
.account
.new_transaction(value, lane)
.map_err(|_| Error::not_enough_funds())?;
let input = account_tx_builder.input();
let witness_builder = account_tx_builder.witness_builder();
builder.add_input(input, witness_builder);
let tx = builder
.finalize_tx((), vec![WitnessInput::SecretKey(secret_key)])
.map_err(|e| Error::wallet_transaction().with(e))?;
let fragment = Fragment::VoteCast(tx);
let id = fragment.hash();
account_tx_builder.add_fragment_id(id);
Ok(fragment.serialize_as_vec().unwrap().into_boxed_slice())
}
}