pub trait WalletWrite: WalletRead {
type UtxoRef;
Show 13 methods
// Required methods
fn create_account(
&mut self,
account_name: &str,
seed: &SecretVec<u8>,
birthday: &AccountBirthday,
key_source: Option<&str>,
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error>;
fn import_account_hd(
&mut self,
account_name: &str,
seed: &SecretVec<u8>,
account_index: AccountId,
birthday: &AccountBirthday,
key_source: Option<&str>,
) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error>;
fn import_account_ufvk(
&mut self,
account_name: &str,
unified_key: &UnifiedFullViewingKey,
birthday: &AccountBirthday,
purpose: AccountPurpose,
key_source: Option<&str>,
) -> Result<Self::Account, Self::Error>;
fn get_next_available_address(
&mut self,
account: Self::AccountId,
request: UnifiedAddressRequest,
) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error>;
fn get_address_for_index(
&mut self,
account: Self::AccountId,
diversifier_index: DiversifierIndex,
request: UnifiedAddressRequest,
) -> Result<Option<UnifiedAddress>, Self::Error>;
fn update_chain_tip(
&mut self,
tip_height: BlockHeight,
) -> Result<(), Self::Error>;
fn put_blocks(
&mut self,
from_state: &ChainState,
blocks: Vec<ScannedBlock<Self::AccountId>>,
) -> Result<(), Self::Error>;
fn put_received_transparent_utxo(
&mut self,
output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error>;
fn store_decrypted_tx(
&mut self,
received_tx: DecryptedTransaction<'_, Self::AccountId>,
) -> Result<(), Self::Error>;
fn store_transactions_to_be_sent(
&mut self,
transactions: &[SentTransaction<'_, Self::AccountId>],
) -> Result<(), Self::Error>;
fn truncate_to_height(
&mut self,
max_height: BlockHeight,
) -> Result<BlockHeight, Self::Error>;
fn set_transaction_status(
&mut self,
_txid: TxId,
_status: TransactionStatus,
) -> Result<(), Self::Error>;
// Provided method
fn reserve_next_n_ephemeral_addresses(
&mut self,
_account_id: Self::AccountId,
_n: usize,
) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> { ... }
}
Expand description
This trait encapsulates the write capabilities required to update stored wallet data.
§Adding accounts
This trait provides several methods for adding accounts to the wallet data:
All of these methods take an AccountBirthday
. The birthday height is defined as
the minimum block height that will be scanned for funds belonging to the wallet. If
birthday.height()
is below the current chain tip, the account addition operation
will trigger a re-scan of the blocks at and above the provided height.
The order in which you call these methods will affect the resulting wallet structure:
- If only
WalletWrite::create_account
is used, the resulting accounts will have sequential ZIP 32 account indices within each given seed. - If
WalletWrite::import_account_hd
is used to import accounts with non-sequential ZIP 32 account indices from the same seed, a call toWalletWrite::create_account
will use the ZIP 32 account index just after the highest-numbered existing account. - If an account is added to the wallet, and then a later call to one of the methods
would produce a UFVK that collides with that account on any FVK component (i.e.
Sapling, Orchard, or transparent), an error will be returned. This can occur in the
following cases:
- An account is created via
WalletWrite::create_account
with an auto-selected ZIP 32 account index, and that index is later imported explicitly via eitherWalletWrite::import_account_ufvk
orWalletWrite::import_account_hd
. - An account is imported via
WalletWrite::import_account_ufvk
orWalletWrite::import_account_hd
, and then the ZIP 32 account index corresponding to that account’s UFVK is later imported either implicitly viaWalletWrite::create_account
, or explicitly via a call toWalletWrite::import_account_ufvk
orWalletWrite::import_account_hd
.
- An account is created via
Note that an error will be returned on an FVK collision even if the UFVKs do not match exactly, e.g. if they have different subsets of components.
A future change to this trait might introduce a method to “upgrade” an imported account with derivation information. See zcash/librustzcash#1284 for details.
Users of the WalletWrite
trait should generally distinguish in their APIs and wallet
UIs between creating a new account, and importing an account that previously existed.
By convention, wallets should only allow a new account to be generated after confirmed
funds have been received by the newest existing account; this allows automated account
recovery to discover and recover all funds within a particular seed.
§Creating a new wallet
To create a new wallet:
- Generate a new BIP 39 mnemonic phrase, using a crate like
bip0039
. - Derive the corresponding seed from the mnemonic phrase.
- Use
WalletWrite::create_account
with the resulting seed.
Callers should construct the AccountBirthday
using AccountBirthday::from_treestate
for
the block at height chain_tip_height - 100
. Setting the birthday height to a tree state below
the pruning depth ensures that reorgs cannot cause funds intended for the wallet to be missed;
otherwise, if the chain tip height were used for the wallet birthday, a transaction targeted at
a height greater than the chain tip could be mined at a height below that tip as part of a
reorg.
§Restoring a wallet from backup
To restore a backed-up wallet:
- Derive the seed from its BIP 39 mnemonic phrase.
- Use
WalletWrite::import_account_hd
once for each ZIP 32 account index that the user wants to restore. - If the highest previously-used ZIP 32 account index was not restored by the user,
remember this index separately as
index_max
. The first time the user wants to generate a new account, useWalletWrite::import_account_hd
to create the accountindex_max + 1
. WalletWrite::create_account
can be used to generate subsequent new accounts in the restored wallet.
Automated account recovery has not yet been implemented by this crate. A wallet app
that supports multiple accounts can implement it manually by tracking account balances
relative to WalletSummary::fully_scanned_height
, and creating new accounts as
funds appear in existing accounts.
If the number of accounts is known in advance, the wallet should create all accounts before scanning the chain so that the scan can be done in a single pass for all accounts.
Required Associated Types§
Required Methods§
Sourcefn create_account(
&mut self,
account_name: &str,
seed: &SecretVec<u8>,
birthday: &AccountBirthday,
key_source: Option<&str>,
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error>
fn create_account( &mut self, account_name: &str, seed: &SecretVec<u8>, birthday: &AccountBirthday, key_source: Option<&str>, ) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error>
Tells the wallet to track the next available account-level spend authority, given the current set of ZIP 316 account identifiers known to the wallet database.
The “next available account” is defined as the ZIP-32 account index immediately following
the highest existing account index among all accounts in the wallet that share the given
seed. Users of the WalletWrite
trait that only call this method are guaranteed to have
accounts with sequential indices.
Returns the account identifier for the newly-created wallet database entry, along with the
associated UnifiedSpendingKey
. Note that the unique account identifier should not be
assumed equivalent to the ZIP 32 account index. It is an opaque identifier for a pool of
funds or set of outputs controlled by a single spending authority.
The ZIP-32 account index may be obtained by calling WalletRead::get_account
with the returned account identifier.
The WalletWrite
trait documentation has more details about account creation and import.
§Arguments
account_name
: A human-readable name for the account.seed
: The 256-byte (at least) HD seed from which to derive the account UFVK.birthday
: Metadata about where to start scanning blocks to find transactions intended for the account.key_source
: A string identifier or other metadata describing the source of the seed. This is treated as opaque metadata by the wallet backend; it is provided for use by applications which need to track additional identifying information for an account.
§Implementation notes
Implementations of this method MUST NOT “fill in gaps” by selecting an account index that is lower than any existing account index among all accounts in the wallet that share the given seed.
§Panics
Panics if the length of the seed is not between 32 and 252 bytes inclusive.
Sourcefn import_account_hd(
&mut self,
account_name: &str,
seed: &SecretVec<u8>,
account_index: AccountId,
birthday: &AccountBirthday,
key_source: Option<&str>,
) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error>
fn import_account_hd( &mut self, account_name: &str, seed: &SecretVec<u8>, account_index: AccountId, birthday: &AccountBirthday, key_source: Option<&str>, ) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error>
Tells the wallet to track a specific account index for a given seed.
Returns details about the imported account, including the unique account identifier for
the newly-created wallet database entry, along with the associated UnifiedSpendingKey
.
Note that the unique account identifier should not be assumed equivalent to the ZIP 32
account index. It is an opaque identifier for a pool of funds or set of outputs controlled
by a single spending authority.
Import accounts with indices that are exactly one greater than the highest existing account index to ensure account indices are contiguous, thereby facilitating automated account recovery.
The WalletWrite
trait documentation has more details about account creation and import.
§Arguments
account_name
: A human-readable name for the account.seed
: The 256-byte (at least) HD seed from which to derive the account UFVK.account_index
: The ZIP 32 account-level component of the HD derivation path at which to derive the account’s UFVK.birthday
: Metadata about where to start scanning blocks to find transactions intended for the account.key_source
: A string identifier or other metadata describing the source of the seed. This is treated as opaque metadata by the wallet backend; it is provided for use by applications which need to track additional identifying information for an account.
§Panics
Panics if the length of the seed is not between 32 and 252 bytes inclusive.
Sourcefn import_account_ufvk(
&mut self,
account_name: &str,
unified_key: &UnifiedFullViewingKey,
birthday: &AccountBirthday,
purpose: AccountPurpose,
key_source: Option<&str>,
) -> Result<Self::Account, Self::Error>
fn import_account_ufvk( &mut self, account_name: &str, unified_key: &UnifiedFullViewingKey, birthday: &AccountBirthday, purpose: AccountPurpose, key_source: Option<&str>, ) -> Result<Self::Account, Self::Error>
Tells the wallet to track an account using a unified full viewing key.
Returns details about the imported account, including the unique account identifier for
the newly-created wallet database entry. Unlike the other account creation APIs
(Self::create_account
and Self::import_account_hd
), no spending key is returned
because the wallet has no information about how the UFVK was derived.
Certain optimizations are possible for accounts which will never be used to spend funds. If
spending_key_available
is false
, the wallet may choose to optimize for this case, in
which case any attempt to spend funds from the account will result in an error.
The WalletWrite
trait documentation has more details about account creation and import.
§Arguments
account_name
: A human-readable name for the account.unified_key
: The UFVK used to detect transactions involving the account.birthday
: Metadata about where to start scanning blocks to find transactions intended for the account.purpose
: Metadata describing whether or not data required for spending should be tracked by the wallet.key_source
: A string identifier or other metadata describing the source of the seed. This is treated as opaque metadata by the wallet backend; it is provided for use by applications which need to track additional identifying information for an account.
§Panics
Panics if the length of the seed is not between 32 and 252 bytes inclusive.
Sourcefn get_next_available_address(
&mut self,
account: Self::AccountId,
request: UnifiedAddressRequest,
) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error>
fn get_next_available_address( &mut self, account: Self::AccountId, request: UnifiedAddressRequest, ) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error>
Generates, persists, and marks as exposed the next available diversified address for the specified account, given the current addresses known to the wallet.
Returns Ok(None)
if the account identifier does not correspond to a known
account.
Sourcefn get_address_for_index(
&mut self,
account: Self::AccountId,
diversifier_index: DiversifierIndex,
request: UnifiedAddressRequest,
) -> Result<Option<UnifiedAddress>, Self::Error>
fn get_address_for_index( &mut self, account: Self::AccountId, diversifier_index: DiversifierIndex, request: UnifiedAddressRequest, ) -> Result<Option<UnifiedAddress>, Self::Error>
Generates, persists, and marks as exposed a diversified address for the specified account at the provided diversifier index.
Returns Ok(None)
in the case that it is not possible to generate an address conforming
to the provided request at the specified diversifier index. Such a result might arise from
the diversifier index not being valid for a ReceiverRequirement::Require
’ed receiver.
Some implementations of this trait may return Err(_)
in some cases to expose more
information, which is only accessible in a backend-specific context.
Address generation should fail if an address has already been exposed for the given diversifier index and the given request produced an address having different receivers than what was originally exposed.
§WARNINGS
If an address generated using this method has a transparent receiver and the chosen diversifier index would be outside the wallet’s internally-configured gap limit, funds sent to these address are likely to not be discovered on recovery from seed. It up to the caller of this method to either ensure that they only request transparent receivers with indices within the range of a reasonable gap limit, or that they ensure that their wallet provides backup facilities that can be used to ensure that funds sent to such addresses are recoverable after a loss of wallet data.
Sourcefn update_chain_tip(
&mut self,
tip_height: BlockHeight,
) -> Result<(), Self::Error>
fn update_chain_tip( &mut self, tip_height: BlockHeight, ) -> Result<(), Self::Error>
Updates the wallet’s view of the blockchain.
This method is used to provide the wallet with information about the state of the
blockchain, and detect any previously scanned data that needs to be re-validated
before proceeding with scanning. It should be called at wallet startup prior to calling
WalletRead::suggest_scan_ranges
in order to provide the wallet with the information it
needs to correctly prioritize scanning operations.
Sourcefn put_blocks(
&mut self,
from_state: &ChainState,
blocks: Vec<ScannedBlock<Self::AccountId>>,
) -> Result<(), Self::Error>
fn put_blocks( &mut self, from_state: &ChainState, blocks: Vec<ScannedBlock<Self::AccountId>>, ) -> Result<(), Self::Error>
Updates the state of the wallet database by persisting the provided block information, along with the note commitments that were detected when scanning the block for transactions pertaining to this wallet.
§Arguments
from_state
must be the chain state for the block height prior to the first block inblocks
.blocks
must be sequential, in order of increasing block height.
Sourcefn put_received_transparent_utxo(
&mut self,
output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error>
fn put_received_transparent_utxo( &mut self, output: &WalletTransparentOutput, ) -> Result<Self::UtxoRef, Self::Error>
Adds a transparent UTXO received by the wallet to the data store.
Sourcefn store_decrypted_tx(
&mut self,
received_tx: DecryptedTransaction<'_, Self::AccountId>,
) -> Result<(), Self::Error>
fn store_decrypted_tx( &mut self, received_tx: DecryptedTransaction<'_, Self::AccountId>, ) -> Result<(), Self::Error>
Caches a decrypted transaction in the persistent wallet store.
Sourcefn store_transactions_to_be_sent(
&mut self,
transactions: &[SentTransaction<'_, Self::AccountId>],
) -> Result<(), Self::Error>
fn store_transactions_to_be_sent( &mut self, transactions: &[SentTransaction<'_, Self::AccountId>], ) -> Result<(), Self::Error>
Saves information about transactions constructed by the wallet to the persistent wallet store.
This must be called before the transactions are sent to the network.
Transactions that have been stored by this method should be retransmitted while it is still possible that they could be mined.
Sourcefn truncate_to_height(
&mut self,
max_height: BlockHeight,
) -> Result<BlockHeight, Self::Error>
fn truncate_to_height( &mut self, max_height: BlockHeight, ) -> Result<BlockHeight, Self::Error>
Truncates the wallet database to at most the specified height.
Implementations of this method may choose a lower block height to which the data store will be truncated if it is not possible to truncate exactly to the specified height. Upon successful truncation, this method returns the height to which the data store was actually truncated.
This method assumes that the state of the underlying data store is consistent up to a particular block height. Since it is possible that a chain reorg might invalidate some stored state, this method must be implemented in order to allow users of this API to “reset” the data store to correctly represent chainstate as of at most the requested block height.
After calling this method, the block at the returned height will be the most recent block and all other operations will treat this block as the chain tip for balance determination purposes.
There may be restrictions on heights to which it is possible to truncate. Specifically, it will only be possible to truncate to heights at which is is possible to create a witness given the current state of the wallet’s note commitment tree.
Sourcefn set_transaction_status(
&mut self,
_txid: TxId,
_status: TransactionStatus,
) -> Result<(), Self::Error>
fn set_transaction_status( &mut self, _txid: TxId, _status: TransactionStatus, ) -> Result<(), Self::Error>
Updates the wallet backend with respect to the status of a specific transaction, from the perspective of the main chain.
Fully transparent transactions, and transactions that do not contain either shielded inputs or shielded outputs belonging to the wallet, may not be discovered by the process of chain scanning; as a consequence, the wallet must actively query to determine whether such transactions have been mined.
Provided Methods§
Sourcefn reserve_next_n_ephemeral_addresses(
&mut self,
_account_id: Self::AccountId,
_n: usize,
) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error>
Available on crate feature transparent-inputs
only.
fn reserve_next_n_ephemeral_addresses( &mut self, _account_id: Self::AccountId, _n: usize, ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error>
transparent-inputs
only.Reserves the next n
available ephemeral addresses for the given account.
This cannot be undone, so as far as possible, errors associated with transaction
construction should have been reported before calling this method.
To ensure that sufficient information is stored on-chain to allow recovering funds sent back to any of the used addresses, a “gap limit” of 20 addresses should be observed as described in BIP 44.
Returns an error if there is insufficient space within the gap limit to allocate the given number of addresses, or if the account identifier does not correspond to a known account.
Implementors§
Source§impl WalletWrite for MockWalletDb
Available on crate feature test-dependencies
only.
impl WalletWrite for MockWalletDb
test-dependencies
only.