Repository: mintlayer/mintlayer-core
Pull request #743 (2023-03-06 10:57:59)

Wallet accounts
This PR adds a basic account system to the wallet. This is by no means complete and there are still many TODOs.

Particularly the APIs and the storage format will be optimised.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b64f4474c..c6b66b001 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,7 +9,7 @@ dependencies = [
  "common",
  "parity-scale-codec",
  "proptest",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "thiserror",
  "utils",
@@ -506,7 +506,7 @@ dependencies = [
  "oneshot",
  "pos_accounting",
  "rpc",
- "rstest",
+ "rstest 0.17.0",
  "serde_json",
  "serialization",
  "static_assertions",
@@ -544,7 +544,7 @@ dependencies = [
  "mockall",
  "num-traits",
  "pos_accounting",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "storage",
  "test-utils",
@@ -566,7 +566,7 @@ dependencies = [
  "itertools",
  "logging",
  "pos_accounting",
- "rstest",
+ "rstest 0.17.0",
  "test-utils",
  "tx-verifier",
  "utils",
@@ -590,7 +590,7 @@ dependencies = [
  "itertools",
  "logging",
  "pos_accounting",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "test-utils",
  "tx-verifier",
@@ -740,7 +740,7 @@ dependencies = [
  "once_cell",
  "parity-scale-codec",
  "proptest",
- "rstest",
+ "rstest 0.17.0",
  "script",
  "serde",
  "serde_json",
@@ -779,7 +779,7 @@ dependencies = [
  "itertools",
  "num",
  "pos_accounting",
- "rstest",
+ "rstest 0.17.0",
  "test-utils",
  "thiserror",
  "utils",
@@ -938,7 +938,7 @@ dependencies = [
  "rand 0.8.5",
  "rand_chacha 0.3.1",
  "ripemd",
- "rstest",
+ "rstest 0.17.0",
  "schnorrkel",
  "secp256k1",
  "serialization",
@@ -1090,7 +1090,7 @@ dependencies = [
  "logging",
  "p2p",
  "parity-scale-codec",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "storage",
  "storage-lmdb",
@@ -2148,7 +2148,7 @@ dependencies = [
  "mockall",
  "parking_lot",
  "rpc",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "subsystem",
  "test-utils",
@@ -2167,7 +2167,7 @@ dependencies = [
  "parity-scale-codec",
  "rand 0.8.5",
  "rand_chacha 0.3.1",
- "rstest",
+ "rstest 0.17.0",
  "thiserror",
 ]
 
@@ -2516,7 +2516,7 @@ dependencies = [
  "portpicker",
  "rlimit",
  "rpc",
- "rstest",
+ "rstest 0.17.0",
  "serde",
  "serialization",
  "siphasher",
@@ -2784,7 +2784,7 @@ dependencies = [
  "common",
  "crypto",
  "parity-scale-codec",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "test-utils",
  "thiserror",
@@ -3146,7 +3146,7 @@ dependencies = [
  "hyper",
  "jsonrpsee",
  "logging",
- "rstest",
+ "rstest 0.17.0",
  "subsystem",
  "test-utils",
  "thiserror",
@@ -3156,6 +3156,18 @@ dependencies = [
  "utils",
 ]
 
+[[package]]
+name = "rstest"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "rstest_macros 0.16.0",
+ "rustc_version 0.4.0",
+]
+
 [[package]]
 name = "rstest"
 version = "0.17.0"
@@ -3164,8 +3176,22 @@ checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
 dependencies = [
  "futures",
  "futures-timer",
- "rstest_macros",
+ "rstest_macros 0.17.0",
+ "rustc_version 0.4.0",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
  "rustc_version 0.4.0",
+ "syn 1.0.109",
+ "unicode-ident",
 ]
 
 [[package]]
@@ -3840,7 +3866,7 @@ version = "0.1.0"
 dependencies = [
  "lmdb-mintlayer",
  "logging",
- "rstest",
+ "rstest 0.17.0",
  "storage-backend-test-suite",
  "storage-core",
  "tempfile",
@@ -4374,7 +4400,7 @@ dependencies = [
  "mockall",
  "pos_accounting",
  "replace_with",
- "rstest",
+ "rstest 0.17.0",
  "test-utils",
  "thiserror",
  "utils",
@@ -4502,7 +4528,7 @@ dependencies = [
  "loom",
  "num-traits",
  "probabilistic-collections",
- "rstest",
+ "rstest 0.17.0",
  "slave-pool",
  "static_assertions",
  "test-utils",
@@ -4518,7 +4544,7 @@ dependencies = [
  "itertools",
  "logging",
  "parity-scale-codec",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "test-utils",
  "thiserror",
@@ -4575,6 +4601,8 @@ dependencies = [
  "common",
  "crypto",
  "logging",
+ "parity-scale-codec",
+ "rstest 0.16.0",
  "serialization",
  "storage",
  "tempfile",
@@ -4592,7 +4620,7 @@ version = "0.1.0"
 dependencies = [
  "common",
  "crypto",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "storage",
  "storage-sqlite",
@@ -4608,8 +4636,11 @@ version = "0.1.0"
 dependencies = [
  "bip39",
  "common",
+ "crypto",
  "parity-scale-codec",
+ "rstest 0.16.0",
  "serialization",
+ "storage",
  "test-utils",
  "thiserror",
  "zeroize",
@@ -4709,7 +4740,7 @@ version = "0.1.0"
 dependencies = [
  "crypto",
  "getrandom 0.2.9",
- "rstest",
+ "rstest 0.17.0",
  "serialization",
  "test-utils",
  "thiserror",
diff --git a/common/src/address/mod.rs b/common/src/address/mod.rs
index ff5156d7e..33bd5b552 100644
--- a/common/src/address/mod.rs
+++ b/common/src/address/mod.rs
@@ -18,7 +18,7 @@ use crate::chain::ChainConfig;
 use crate::primitives::{encoding, Bech32Error, DecodedArbitraryDataFromBech32};
 use crypto::key::PublicKey;
 pub mod pubkeyhash;
-use serialization::Encode;
+use serialization::{Decode, Encode};
 
 pub trait AddressableData<T: AsRef<[u8]>> {
     fn encode(&self) -> Result<String, Bech32Error> {
@@ -50,7 +50,7 @@ impl From<Bech32Error> for AddressError {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
 pub struct Address {
     address: String,
 }
diff --git a/common/src/chain/config/builder.rs b/common/src/chain/config/builder.rs
index e4c02cbf8..5a9cdc848 100644
--- a/common/src/chain/config/builder.rs
+++ b/common/src/chain/config/builder.rs
@@ -25,6 +25,7 @@ use crate::{
     },
     primitives::{id::WithId, semver::SemVer, Amount, BlockDistance, BlockHeight, H256},
 };
+use crypto::key::hdkd::child_number::ChildNumber;
 
 impl ChainType {
     fn default_genesis_init(&self) -> GenesisBlockInit {
@@ -87,6 +88,7 @@ impl GenesisBlockInit {
 pub struct Builder {
     chain_type: ChainType,
     address_prefix: String,
+    bip44_coin_type: ChildNumber,
     magic_bytes: [u8; 4],
     p2p_port: u16,
     max_future_block_time_offset: Duration,
@@ -120,6 +122,7 @@ impl Builder {
         Self {
             chain_type,
             address_prefix: chain_type.default_address_prefix().to_string(),
+            bip44_coin_type: chain_type.default_bip44_coin_type(),
             coin_decimals: Mlt::DECIMALS,
             magic_bytes: chain_type.default_magic_bytes(),
             p2p_port: chain_type.default_p2p_port(),
@@ -162,6 +165,7 @@ impl Builder {
         let Self {
             chain_type,
             address_prefix,
+            bip44_coin_type,
             coin_decimals,
             magic_bytes,
             p2p_port,
@@ -209,6 +213,7 @@ impl Builder {
         ChainConfig {
             chain_type,
             address_prefix,
+            bip44_coin_type,
             coin_decimals,
             magic_bytes,
             p2p_port,
@@ -253,6 +258,7 @@ macro_rules! builder_method {
 impl Builder {
     builder_method!(chain_type: ChainType);
     builder_method!(address_prefix: String);
+    builder_method!(bip44_coin_type: ChildNumber);
     builder_method!(magic_bytes: [u8; 4]);
     builder_method!(p2p_port: u16);
     builder_method!(max_future_block_time_offset: Duration);
diff --git a/common/src/chain/config/mod.rs b/common/src/chain/config/mod.rs
index 3eeea963d..dfa599b8c 100644
--- a/common/src/chain/config/mod.rs
+++ b/common/src/chain/config/mod.rs
@@ -30,6 +30,7 @@ use crate::chain::{PoWChainConfig, UpgradeVersion};
 use crate::primitives::id::{Id, Idable, WithId};
 use crate::primitives::semver::SemVer;
 use crate::primitives::{Amount, BlockDistance, BlockHeight, H256};
+use crypto::key::hdkd::{child_number::ChildNumber, u31::U31};
 use std::collections::BTreeMap;
 use std::num::NonZeroU64;
 use std::sync::Arc;
@@ -44,6 +45,12 @@ const DEFAULT_EPOCH_LENGTH: NonZeroU64 =
     };
 const DEFAULT_SEALED_EPOCH_DISTANCE_FROM_TIP: usize = 2;
 
+pub const BIP44_PATH: ChildNumber = ChildNumber::from_hardened(U31::from_u32_with_msb(44).0);
+pub const MINTLAYER_COIN_TYPE: ChildNumber =
+    ChildNumber::from_hardened(U31::from_u32_with_msb(0x4D4C).0);
+pub const MINTLAYER_COIN_TYPE_TEST: ChildNumber =
+    ChildNumber::from_hardened(U31::from_u32_with_msb(0x01).0);
+
 pub type EpochIndex = u64;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -90,12 +97,20 @@ impl ChainType {
             ChainType::Signet => 33031,
         }
     }
+
+    const fn default_bip44_coin_type(&self) -> ChildNumber {
+        match self {
+            ChainType::Mainnet => MINTLAYER_COIN_TYPE,
+            ChainType::Testnet | ChainType::Regtest | ChainType::Signet => MINTLAYER_COIN_TYPE_TEST,
+        }
+    }
 }
 
 #[derive(Debug, Clone)]
 pub struct ChainConfig {
     chain_type: ChainType,
     address_prefix: String,
+    bip44_coin_type: ChildNumber,
     height_checkpoint_data: BTreeMap<BlockHeight, Id<Block>>,
     net_upgrades: NetUpgrades<UpgradeVersion>,
     magic_bytes: [u8; 4],
@@ -131,6 +146,10 @@ impl ChainConfig {
         &self.address_prefix
     }
 
+    pub fn bip44_coin_type(&self) -> ChildNumber {
+        self.bip44_coin_type
+    }
+
     pub fn genesis_block_id(&self) -> Id<GenBlock> {
         self.genesis_block.get_id().into()
     }
diff --git a/crypto/src/key/extended.rs b/crypto/src/key/extended.rs
index fc4bea8a1..d9b55fce8 100644
--- a/crypto/src/key/extended.rs
+++ b/crypto/src/key/extended.rs
@@ -32,12 +32,12 @@ pub enum ExtendedKeyKind {
     Secp256k1Schnorr,
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Decode, Encode)]
 pub struct ExtendedPrivateKey {
     key: ExtendedPrivateKeyHolder,
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Decode, Encode)]
 pub struct ExtendedPublicKey {
     pub_key: ExtendedPublicKeyHolder,
 }
diff --git a/crypto/src/key/hdkd/derivation_path.rs b/crypto/src/key/hdkd/derivation_path.rs
index d024a4f77..fac8bfeda 100644
--- a/crypto/src/key/hdkd/derivation_path.rs
+++ b/crypto/src/key/hdkd/derivation_path.rs
@@ -50,6 +50,10 @@ impl DerivationPath {
     pub fn is_empty(&self) -> bool {
         self.0.is_empty()
     }
+
+    pub fn is_root(&self) -> bool {
+        self.is_empty()
+    }
 }
 
 impl Encode for DerivationPath {
@@ -213,7 +217,6 @@ mod tests {
             DerivationPath(vec![]),
             DerivationPath::from_str("m").unwrap()
         );
-        // assert_eq!(DerivationPath::master(), DerivationPath::default());
         assert_eq!(DerivationPath::from_str("m"), Ok(DerivationPath::empty()));
         assert_eq!(
             DerivationPath::from_str("m/0'"),
diff --git a/crypto/src/key/key_holder.rs b/crypto/src/key/key_holder.rs
index e2164c092..dbce649bf 100644
--- a/crypto/src/key/key_holder.rs
+++ b/crypto/src/key/key_holder.rs
@@ -31,7 +31,7 @@ pub enum PublicKeyHolder {
     Secp256k1Schnorr(Secp256k1PublicKey),
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Decode, Encode)]
 pub enum ExtendedPrivateKeyHolder {
     #[codec(index = 0)]
     Secp256k1Schnorr(Secp256k1ExtendedPrivateKey),
diff --git a/crypto/src/key/secp256k1/extended_keys.rs b/crypto/src/key/secp256k1/extended_keys.rs
index 5517d12ab..719847e65 100644
--- a/crypto/src/key/secp256k1/extended_keys.rs
+++ b/crypto/src/key/secp256k1/extended_keys.rs
@@ -28,6 +28,7 @@ use secp256k1;
 use secp256k1::SECP256K1;
 use serialization::{Decode, Encode};
 use sha2::Sha512;
+use std::cmp::Ordering;
 use zeroize::Zeroize;
 
 /// Given a tree of keys that are derived from a master key using BIP32 rules, this struct represents
@@ -47,6 +48,18 @@ fn new_hmac_sha_512(key: &[u8]) -> Hmac<Sha512> {
     Hmac::<Sha512>::new_from_slice(key).expect("HMAC can take key of any size")
 }
 
+impl PartialOrd for Secp256k1ExtendedPrivateKey {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Secp256k1ExtendedPrivateKey {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.derivation_path.cmp(&other.derivation_path)
+    }
+}
+
 fn to_key_and_chain_code(
     mac: Hmac<Sha512>,
 ) -> Result<(secp256k1::SecretKey, ChainCode), DerivationError> {
@@ -169,7 +182,7 @@ impl Derivable for Secp256k1ExtendedPrivateKey {
 
 /// Given a tree of keys that are derived from a master key using BIP32 rules, this struct represents
 /// the public key at one of the nodes of this tree.
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode)]
+#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
 pub struct Secp256k1ExtendedPublicKey {
     /// The absolute derivation path that was used to derive this key
     derivation_path: DerivationPath,
@@ -180,6 +193,18 @@ pub struct Secp256k1ExtendedPublicKey {
     public_key: Secp256k1PublicKey,
 }
 
+impl PartialOrd for Secp256k1ExtendedPublicKey {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Secp256k1ExtendedPublicKey {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.derivation_path.cmp(&other.derivation_path)
+    }
+}
+
 impl Secp256k1ExtendedPublicKey {
     pub fn public_key(&self) -> &Secp256k1PublicKey {
         &self.public_key
diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml
index a9040233c..8b232b82d 100644
--- a/wallet/Cargo.toml
+++ b/wallet/Cargo.toml
@@ -18,10 +18,12 @@ wallet-storage = {path = './storage'}
 wallet-types = {path = './types'}
 
 bip39 = { version = "2.0", default-features = false, features = ["std", "zeroize"] }
-zeroize = "1.5"
+parity-scale-codec.workspace = true
 thiserror.workspace = true
+zeroize = "1.5"
 
 [dev-dependencies]
 test-utils = { path = "../test-utils" }
 
+rstest = "0.16"
 tempfile = "3.3"
diff --git a/wallet/src/account.rs b/wallet/src/account.rs
new file mode 100644
index 000000000..6797c765a
--- /dev/null
+++ b/wallet/src/account.rs
@@ -0,0 +1,301 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::key_chain::{AccountKeyChain, KeyPurpose};
+use crate::{WalletError, WalletResult};
+use common::address::Address;
+use common::chain::{ChainConfig, OutPoint, Transaction, TxOutput};
+use common::primitives::{Id, Idable};
+use std::collections::BTreeMap;
+use std::sync::Arc;
+use storage::Backend;
+use utxo::Utxo;
+use wallet_storage::{StoreTxRo, StoreTxRw, WalletStorageRead, WalletStorageWrite};
+use wallet_types::{AccountId, AccountOutPointId, AccountTxId, TxState, WalletTx};
+
+pub struct Account {
+    #[allow(dead_code)] // TODO remove
+    chain_config: Arc<ChainConfig>,
+    key_chain: AccountKeyChain,
+    txs: BTreeMap<Id<Transaction>, WalletTx>,
+    utxo: BTreeMap<OutPoint, Utxo>,
+}
+
+impl Account {
+    pub fn load_from_database<B: storage::Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &StoreTxRo<B>,
+        id: AccountId,
+    ) -> WalletResult<Account> {
+        let key_chain = AccountKeyChain::load_from_database(chain_config.clone(), db_tx, &id)?;
+
+        let utxo: BTreeMap<OutPoint, Utxo> = db_tx
+            .get_utxo_set(&id)?
+            .into_iter()
+            .map(|(k, v)| (k.into_item_id(), v))
+            .collect();
+
+        let txs: BTreeMap<Id<Transaction>, WalletTx> = db_tx
+            .get_transactions(&id)?
+            .into_iter()
+            .map(|(k, v)| (k.into_item_id(), v))
+            .collect();
+
+        Ok(Account {
+            chain_config,
+            key_chain,
+            txs,
+            utxo,
+        })
+    }
+
+    /// Create a new account by providing a key chain
+    pub fn new<B: storage::Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &mut StoreTxRw<B>,
+        key_chain: AccountKeyChain,
+    ) -> WalletResult<Account> {
+        let account_id = key_chain.get_account_id();
+        let account_info = key_chain.get_account_info();
+
+        db_tx.set_account(&account_id, &account_info)?;
+
+        Ok(Account {
+            chain_config,
+            key_chain,
+            txs: BTreeMap::new(),
+            utxo: BTreeMap::new(),
+        })
+    }
+
+    /// Get the id of this account
+    pub fn get_acount_id(&self) -> AccountId {
+        self.key_chain.get_account_id()
+    }
+
+    /// Get a new address that hasn't been used before
+    pub fn get_new_address<B: Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        purpose: KeyPurpose,
+    ) -> WalletResult<Address> {
+        Ok(self.key_chain.issue_new_address(db_tx, purpose)?)
+    }
+
+    #[allow(dead_code)] // TODO remove
+    fn add_transaction<B: storage::Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        tx: Transaction,
+        state: TxState,
+    ) -> WalletResult<()> {
+        let tx_id = tx.get_id();
+
+        if self.txs.contains_key(&tx_id) {
+            return Err(WalletError::DuplicateTransaction(tx_id));
+        }
+
+        let account_tx_id = AccountTxId::new(self.get_acount_id(), tx_id);
+        let wallet_tx = WalletTx::new(tx, state);
+
+        db_tx.set_transaction(&account_tx_id, &wallet_tx)?;
+
+        self.txs.insert(tx_id, wallet_tx);
+
+        // TODO add UTXO?
+
+        Ok(())
+    }
+
+    #[allow(dead_code)] // TODO remove
+    fn delete_transaction<B: storage::Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        tx_id: Id<Transaction>,
+    ) -> WalletResult<()> {
+        if !self.txs.contains_key(&tx_id) {
+            return Err(WalletError::NoTransactionFound(tx_id));
+        }
+
+        let account_tx_id = AccountTxId::new(self.get_acount_id(), tx_id);
+        db_tx.del_transaction(&account_tx_id)?;
+
+        self.txs.remove(&tx_id);
+
+        // TODO remove UTXO?
+
+        Ok(())
+    }
+
+    // TODO fix incompatibility between borrowing mut self and the database transaction
+    #[allow(dead_code)] // TODO remove
+    fn add_to_utxos<B: storage::Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        tx: &Transaction,
+    ) -> WalletResult<()> {
+        for (i, output) in tx.outputs().iter().enumerate() {
+            // Check if this output belongs to this wallet or it is watched
+            if self.is_available_for_spending(output) && self.is_mine_or_watched(output) {
+                let outpoint = OutPoint::new(tx.get_id().into(), i as u32);
+                let utxo = Utxo::new(output.clone(), false, utxo::UtxoSource::Mempool);
+                self.utxo.insert(outpoint.clone(), utxo.clone());
+                let account_utxo_id = AccountOutPointId::new(self.get_acount_id(), outpoint);
+                db_tx.set_utxo(&account_utxo_id, utxo)?;
+            }
+        }
+        Ok(())
+    }
+
+    #[allow(dead_code)] // TODO remove
+    fn is_available_for_spending(&self, _txo: &TxOutput) -> bool {
+        // TODO implement
+        true
+    }
+
+    #[allow(dead_code)] // TODO remove
+    fn is_mine_or_watched(&self, _txo: &TxOutput) -> bool {
+        // TODO implement
+        true
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::key_chain::MasterKeyChain;
+    use common::chain::config::create_regtest;
+    use common::chain::{GenBlock, Transaction};
+    use common::primitives::{Idable, H256};
+    use crypto::key::hdkd::child_number::ChildNumber;
+    use crypto::key::hdkd::u31::U31;
+    use wallet_storage::{DefaultBackend, Store, TransactionRo, TransactionRw, Transactional};
+    use wallet_types::TxState;
+
+    const ZERO_H: ChildNumber = ChildNumber::from_hardened(U31::from_u32_with_msb(0).0);
+    const MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
+
+    #[test]
+    fn account_transactions() {
+        let config = Arc::new(create_regtest());
+        let db = Arc::new(Store::new(DefaultBackend::new_in_memory()).unwrap());
+        let mut db_tx = db.transaction_rw(None).unwrap();
+
+        let master_key_chain =
+            MasterKeyChain::new_from_mnemonic(config.clone(), &mut db_tx, MNEMONIC, None).unwrap();
+
+        let key_chain = master_key_chain.create_account_key_chain(&mut db_tx, ZERO_H).unwrap();
+
+        let mut account = Account::new(config.clone(), &mut db_tx, key_chain).unwrap();
+        db_tx.commit().unwrap();
+
+        let id = account.get_acount_id();
+
+        let tx1 = Transaction::new(1, vec![], vec![], 0).unwrap();
+        let tx2 = Transaction::new(2, vec![], vec![], 0).unwrap();
+        let tx3 = Transaction::new(3, vec![], vec![], 0).unwrap();
+        let tx4 = Transaction::new(4, vec![], vec![], 0).unwrap();
+
+        let block_id: Id<GenBlock> = H256::from_low_u64_le(123).into();
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        account
+            .add_transaction(&mut db_tx, tx1.clone(), TxState::Confirmed(block_id))
+            .unwrap();
+        account
+            .add_transaction(&mut db_tx, tx2.clone(), TxState::Conflicted(block_id))
+            .unwrap();
+        account.add_transaction(&mut db_tx, tx3.clone(), TxState::InMempool).unwrap();
+        account.add_transaction(&mut db_tx, tx4.clone(), TxState::Inactive).unwrap();
+        db_tx.commit().unwrap();
+
+        assert_eq!(id, account.get_acount_id());
+        assert_eq!(4, account.txs.len());
+        assert_eq!(&tx1, account.txs.get(&tx1.get_id()).unwrap().get_tx());
+        assert_eq!(&tx2, account.txs.get(&tx2.get_id()).unwrap().get_tx());
+        assert_eq!(&tx3, account.txs.get(&tx3.get_id()).unwrap().get_tx());
+        assert_eq!(&tx4, account.txs.get(&tx4.get_id()).unwrap().get_tx());
+
+        drop(account);
+
+        let db_tx = db.transaction_ro().unwrap();
+        let mut account = Account::load_from_database(config.clone(), &db_tx, id.clone()).unwrap();
+        db_tx.close();
+
+        assert_eq!(id, account.get_acount_id());
+        assert_eq!(4, account.txs.len());
+        assert_eq!(&tx1, account.txs.get(&tx1.get_id()).unwrap().get_tx());
+        assert_eq!(&tx2, account.txs.get(&tx2.get_id()).unwrap().get_tx());
+        assert_eq!(&tx3, account.txs.get(&tx3.get_id()).unwrap().get_tx());
+        assert_eq!(&tx4, account.txs.get(&tx4.get_id()).unwrap().get_tx());
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        account.delete_transaction(&mut db_tx, tx1.get_id()).unwrap();
+        account.delete_transaction(&mut db_tx, tx3.get_id()).unwrap();
+        db_tx.commit().unwrap();
+
+        assert_eq!(id, account.get_acount_id());
+        assert_eq!(2, account.txs.len());
+        assert_eq!(&tx2, account.txs.get(&tx2.get_id()).unwrap().get_tx());
+        assert_eq!(&tx4, account.txs.get(&tx4.get_id()).unwrap().get_tx());
+
+        drop(account);
+
+        let db_tx = db.transaction_ro().unwrap();
+        let account = Account::load_from_database(config, &db_tx, id.clone()).unwrap();
+        db_tx.close();
+
+        assert_eq!(id, account.get_acount_id());
+        assert_eq!(2, account.txs.len());
+        assert_eq!(&tx2, account.txs.get(&tx2.get_id()).unwrap().get_tx());
+        assert_eq!(&tx4, account.txs.get(&tx4.get_id()).unwrap().get_tx());
+    }
+
+    #[test]
+    fn account_addresses() {
+        let config = Arc::new(create_regtest());
+        let db = Arc::new(Store::new(DefaultBackend::new_in_memory()).unwrap());
+        let mut db_tx = db.transaction_rw(None).unwrap();
+
+        let master_key_chain =
+            MasterKeyChain::new_from_mnemonic(config.clone(), &mut db_tx, MNEMONIC, None).unwrap();
+
+        let key_chain = master_key_chain.create_account_key_chain(&mut db_tx, ZERO_H).unwrap();
+
+        let mut account = Account::new(config, &mut db_tx, key_chain).unwrap();
+        db_tx.commit().unwrap();
+
+        let test_vec = vec![
+            (
+                KeyPurpose::ReceiveFunds,
+                "rmt14qdg6kvlkpfwcw6zjc3dlxpj0g6ddknf54evpv",
+            ),
+            (
+                KeyPurpose::Change,
+                "rmt1867l3cva9qprxny6yanula7k6scuj9xy9rv7m2",
+            ),
+            (
+                KeyPurpose::ReceiveFunds,
+                "rmt1vnqqfgfccs2sg7c0feptrw03qm8ejq5vqqvpql",
+            ),
+        ];
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        for (purpose, address_str) in test_vec {
+            let address = account.get_new_address(&mut db_tx, purpose).unwrap();
+            assert_eq!(address.get(), address_str);
+        }
+    }
+}
diff --git a/wallet/src/key_chain.rs b/wallet/src/key_chain.rs
index 83e70ff19..9db430b93 100644
--- a/wallet/src/key_chain.rs
+++ b/wallet/src/key_chain.rs
@@ -17,43 +17,85 @@
 //! The KeyChain struct holds and constantly derives keys for the wallet addresses
 //! It uses the following derivation scheme:
 //!
-//! m/0'/<account_number>'/<key_purpose>'/<key_index>'
+//! m/44'/19788'/<account_number>'/<key_purpose>'/<key_index>'
 //!
-//! Where `account_number` is the index of an account,
+//! Where 44' is the standard BIP44 prefix
+//!       19788' or 0x4D4C' (1' for the testnets) is Mintlayer's BIP44 registered coin type
+//!       `account_number` is the index of an account,
 //!       `key_purpose` is if the generated address is for receiving or change purposes and this
 //!                     value is 0 or 1 respectively,
 //!       `key_index` starts from 0 and it is incremented for each new address
 
+use crate::key_chain::KeyPurpose::{Change, ReceiveFunds};
 use common::address::{Address, AddressError};
-use common::chain::config::create_regtest;
+use common::chain::config::BIP44_PATH;
 use common::chain::ChainConfig;
 use crypto::key::extended::{ExtendedKeyKind, ExtendedPrivateKey, ExtendedPublicKey};
+use crypto::key::hdkd::child_number::ChildNumber;
 use crypto::key::hdkd::derivable::{Derivable, DerivationError};
 use crypto::key::hdkd::derivation_path::DerivationPath;
-use std::str::FromStr;
+use crypto::key::hdkd::u31::U31;
+use serialization::{Decode, Encode};
+use std::collections::BTreeMap;
+use std::convert::TryInto;
 use std::sync::Arc;
 use storage::Backend;
-use wallet_storage::{Store, WalletStorageRead};
+use wallet_storage::{StoreTxRo, StoreTxRw, WalletStorageRead, WalletStorageWrite};
+use wallet_types::account_info::KeychainUsageState;
+use wallet_types::{
+    AccountAddressId, AccountId, AccountInfo, DeterministicAccountInfo, RootKeyContent, RootKeyId,
+};
 use zeroize::Zeroize;
 
-const KEY_KIND: ExtendedKeyKind = ExtendedKeyKind::Secp256k1Schnorr;
+/// The number of nodes in a BIP44 path
+const BIP44_PATH_LENGTH: usize = 5;
+/// The index of key_purpose
+const BIP44_KEY_PURPOSE_INDEX: usize = 3;
+/// The index of the usable key in the BIP44 hierarchy
+const BIP44_KEY_INDEX: usize = 4;
+/// Default cryptography type
+const DEFAULT_KEY_KIND: ExtendedKeyKind = ExtendedKeyKind::Secp256k1Schnorr;
+/// Default size of the number of unused addresses that need to be checked after the
+/// last used address.
 const LOOKAHEAD_SIZE: u16 = 100;
 
 /// KeyChain errors
 #[derive(thiserror::Error, Debug, Eq, PartialEq)]
 pub enum KeyChainError {
+    #[error("Wallet database error: {0}")]
+    DatabaseError(#[from] wallet_storage::Error),
     #[error("Bip39 error: {0}")]
     Bip39(bip39::Error),
     #[error("Key derivation error: {0}")]
     Derivation(#[from] DerivationError),
     #[error("Address error: {0}")]
     Address(#[from] AddressError),
+    #[error("Key chain is locked")]
+    KeyChainIsLocked,
+    #[error("No account found")] // TODO implement display for AccountId
+    NoAccountFound(AccountId),
+    #[error("Invalid BIP44 derivation path format: {0}")]
+    InvalidBip44DerivationPath(DerivationPath),
+    #[error("Could not load key chain")]
+    CouldNotLoadKeyChain,
+    #[error("The provided keys do not belong to the same hierarchy")]
+    KeysNotInSameHierarchy,
+    #[error("Invalid key purpose index {0}")]
+    InvalidKeyPurpose(ChildNumber),
+    #[error("Only one root key is supported")]
+    OnlyOneRootKeyIsSupported,
+    #[error("Cannot issue more keys, lookahead exceeded")]
+    LookAheadExceeded,
+    #[error("The provided key is not a root in a hierarchy")]
+    KeyNotRoot,
 }
 
 /// Result type used for the key chain
 type KeyChainResult<T> = Result<T, KeyChainError>;
 
 /// The usage purpose of a key i.e. if it is for receiving funds or for change
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+#[allow(clippy::unnecessary_cast)]
 pub enum KeyPurpose {
     /// This is for addresses created for receiving funds that are given to the user
     ReceiveFunds = 0,
@@ -61,140 +103,555 @@ pub enum KeyPurpose {
     Change = 1,
 }
 
+impl KeyPurpose {
+    const ALL: [KeyPurpose; 2] = [ReceiveFunds, Change];
+    /// The index for each purpose
+    const DETERMINISTIC_INDEX: [ChildNumber; 2] = [
+        ChildNumber::from_normal(U31::from_u32_with_msb(0).0),
+        ChildNumber::from_normal(U31::from_u32_with_msb(1).0),
+    ];
+}
+
+impl TryFrom<ChildNumber> for KeyPurpose {
+    type Error = KeyChainError;
+
+    fn try_from(num: ChildNumber) -> Result<Self, Self::Error> {
+        match num.get_index() {
+            0 => Ok(ReceiveFunds),
+            1 => Ok(Change),
+            _ => Err(KeyChainError::InvalidKeyPurpose(num)),
+        }
+    }
+}
+
 #[allow(dead_code)] // TODO remove
-/// This key chain contains a pool of pre-generated keys and addresses for the usage in a wallet
-pub struct KeyChain<B: Backend> {
+pub struct MasterKeyChain {
+    // pub struct MasterKeyChain<B: Backend> {
     /// The specific chain this KeyChain is based on, this will affect the address format
     chain_config: Arc<ChainConfig>,
-
     /// The master key of this key chain from where all the keys are derived from
     // TODO implement encryption
-    master_key: ExtendedPrivateKey,
+    root_key: ExtendedPrivateKey,
+}
 
-    /// The database connection
-    // TODO Consider if this should be an Option
-    db: Arc<Store<B>>,
+impl MasterKeyChain {
+    #[allow(dead_code)] // TODO remove
+    pub fn mnemonic_to_root_key(
+        mnemonic_str: &str,
+        passphrase: Option<&str>,
+    ) -> KeyChainResult<ExtendedPrivateKey> {
+        let mut mnemonic = bip39::Mnemonic::parse(mnemonic_str).map_err(KeyChainError::Bip39)?;
+        let mut seed = mnemonic.to_seed(passphrase.unwrap_or(""));
+        let root_key = ExtendedPrivateKey::new_master(&seed, DEFAULT_KEY_KIND)?;
+        // TODO(SECURITY) confirm that the mnemonic is erased on drop
+        mnemonic.zeroize();
+        seed.zeroize();
+        Ok(root_key)
+    }
 
-    /// The number of addresses to pre-generate from the master key
-    lookahead_size: u16,
-}
-#[allow(dead_code)] // TODO remove
-impl<B: Backend> KeyChain<B> {
-    pub fn new_from_mnemonic(
+    #[allow(dead_code)] // TODO remove
+    pub fn new_from_mnemonic<B: Backend>(
         chain_config: Arc<ChainConfig>,
+        db_tx: &mut StoreTxRw<B>,
         mnemonic_str: &str,
         passphrase: Option<&str>,
-        db: Arc<Store<B>>,
     ) -> KeyChainResult<Self> {
-        // TODO use Mnemonic::parse when bip39 is configured with "std"
-        let mnemonic =
-            bip39::Mnemonic::parse_normalized(mnemonic_str).map_err(KeyChainError::Bip39)?;
-        // TODO use Mnemonic::to_seed when bip39 is configured with "std"
-        let mut seed = mnemonic.to_seed_normalized(passphrase.unwrap_or(""));
-        let master_key = ExtendedPrivateKey::new_master(&seed, KEY_KIND)?;
-        // TODO(SECURITY) erase mnemonic
-        seed.zeroize();
+        let root_key = Self::mnemonic_to_root_key(mnemonic_str, passphrase)?;
+        Self::new_from_root_key(chain_config, db_tx, root_key)
+    }
+
+    #[allow(dead_code)] // TODO remove
+    pub fn new_from_root_key<B: Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &mut StoreTxRw<B>,
+        root_key: ExtendedPrivateKey,
+    ) -> KeyChainResult<Self> {
+        if !root_key.get_derivation_path().is_root() {
+            return Err(KeyChainError::KeyNotRoot);
+        }
+
+        let key_id = RootKeyId::from(root_key.to_public_key());
+        let key_content = RootKeyContent::from(root_key);
+
+        db_tx.set_root_key(&key_id, &key_content)?;
+
+        let root_key = key_content.into_key();
 
-        Ok(KeyChain {
+        Ok(MasterKeyChain {
             chain_config,
-            master_key,
-            db,
-            lookahead_size: LOOKAHEAD_SIZE,
+            root_key,
         })
     }
 
-    pub fn load_key_chain(db: Arc<Store<B>>) -> KeyChainResult<Self> {
-        // TODO remove this
-        let _ = db.get_storage_version().expect("This should work?");
-        // TODO implement loading from database
-        Ok(KeyChain {
-            chain_config: Arc::new(create_regtest()),
-            master_key: ExtendedPrivateKey::new_from_entropy(KEY_KIND).0,
-            db,
+    /// Load the Master key chain from database and all the account key chains it derives
+    #[allow(dead_code)] // TODO remove
+    pub fn load_from_database<B: Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &StoreTxRo<B>,
+    ) -> KeyChainResult<Self> {
+        let mut root_keys = db_tx.get_all_root_keys()?;
+
+        // The current format supports a single root key
+        if root_keys.len() != 1 {
+            return Err(KeyChainError::OnlyOneRootKeyIsSupported);
+        }
+
+        let (_, key_content) =
+            root_keys.pop_first().expect("Should not fail because it contains 1 key/value");
+
+        let root_key = key_content.into_key();
+
+        Ok(MasterKeyChain {
+            chain_config,
+            root_key,
+        })
+    }
+
+    #[allow(dead_code)] // TODO remove
+    pub fn create_account_key_chain<B: Backend>(
+        &self,
+        db_tx: &mut StoreTxRw<B>,
+        account_index: ChildNumber,
+    ) -> KeyChainResult<AccountKeyChain> {
+        AccountKeyChain::new_from_root_key(
+            self.chain_config.clone(),
+            db_tx,
+            &self.root_key,
+            account_index,
+        )
+    }
+
+    #[allow(dead_code)] // TODO remove
+    pub fn load_keychain_from_database<B: Backend>(
+        &self,
+        db_tx: &StoreTxRo<B>,
+        id: &AccountId,
+    ) -> KeyChainResult<AccountKeyChain> {
+        AccountKeyChain::load_from_database(self.chain_config.clone(), db_tx, id)
+    }
+
+    #[allow(dead_code)] // TODO remove
+    pub fn get_root_key(&self) -> ExtendedPublicKey {
+        self.root_key.to_public_key()
+    }
+}
+
+#[allow(dead_code)] // TODO remove
+/// This key chain contains a pool of pre-generated keys and addresses for the usage in a wallet
+pub struct AccountKeyChain {
+    /// The specific chain this KeyChain is based on, this will affect the address format
+    chain_config: Arc<ChainConfig>,
+
+    /// The account key from which all the addresses are derived
+    account_pubkey: ExtendedPublicKey,
+
+    /// The master/root key that this account key was derived from
+    root_hierarchy_key: Option<ExtendedPublicKey>,
+
+    /// The derived addresses for the receiving funds. Those are derived as needed.
+    receiving_addresses: BTreeMap<ChildNumber, Address>,
+
+    /// Key hierarchy used for receiving funds
+    receiving_state: KeychainUsageState,
+
+    /// The derived addresses for the change. Those are derived as needed.
+    change_addresses: BTreeMap<ChildNumber, Address>,
+
+    /// Key hierarchy used for change
+    change_state: KeychainUsageState,
+
+    /// The number of unused addresses that need to be checked after the last used address
+    lookahead_size: u16,
+}
+
+impl AccountKeyChain {
+    fn new_from_root_key<B: Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &mut StoreTxRw<B>,
+        root_key: &ExtendedPrivateKey,
+        index: ChildNumber,
+    ) -> KeyChainResult<AccountKeyChain> {
+        let account_path = make_account_path(&chain_config, index);
+
+        let account_privkey = root_key.clone().derive_absolute_path(&account_path)?;
+
+        let mut new_account = AccountKeyChain {
+            chain_config,
+            account_pubkey: account_privkey.to_public_key(),
+            root_hierarchy_key: Some(root_key.to_public_key()),
+            receiving_addresses: BTreeMap::new(),
+            receiving_state: KeychainUsageState::default(),
+            change_addresses: BTreeMap::new(),
+            change_state: KeychainUsageState::default(),
             lookahead_size: LOOKAHEAD_SIZE,
+        };
+
+        Self::persist_account_info(&mut new_account, db_tx)?;
+
+        Ok(new_account)
+    }
+
+    /// Load all
+    pub fn load_from_database<B: Backend>(
+        chain_config: Arc<ChainConfig>,
+        db_tx: &StoreTxRo<B>,
+        id: &AccountId,
+    ) -> KeyChainResult<Self> {
+        let account_info =
+            db_tx.get_account(id)?.ok_or(KeyChainError::NoAccountFound(id.clone()))?;
+
+        let AccountInfo::Deterministic(account_info) = account_info;
+
+        let (receiving_addresses, change_addresses) = Self::load_addresses(db_tx, id)?;
+
+        Ok(AccountKeyChain {
+            chain_config,
+            account_pubkey: account_info.get_account_key().clone(),
+            root_hierarchy_key: account_info.get_root_hierarchy_key().clone(),
+            receiving_addresses,
+            receiving_state: account_info.get_receiving_state().clone(),
+            change_addresses,
+            change_state: account_info.get_change_state().clone(),
+            lookahead_size: account_info.get_lookahead_size(),
         })
     }
 
-    /// Get a new address that hasn't been used before
-    pub fn get_new_address(&self, purpose: KeyPurpose) -> KeyChainResult<Address> {
-        let key = self.get_new_key(purpose)?;
+    fn load_addresses<B: Backend>(
+        db_tx: &StoreTxRo<B>,
+        id: &AccountId,
+    ) -> KeyChainResult<(
+        BTreeMap<ChildNumber, Address>,
+        BTreeMap<ChildNumber, Address>,
+    )> {
+        let mut receiving_addresses = BTreeMap::new();
+        let mut change_addresses = BTreeMap::new();
 
-        let address = Address::from_public_key(
-            &self.chain_config,
-            &ExtendedPublicKey::from_private_key(&key).into_public_key(),
-        )?;
+        for (address_id, address) in db_tx.get_addresses(id)? {
+            // Check that derivation path has the expected number of nodes
+            let derivation_path = address_id.into_item_id();
+            if derivation_path.len() != BIP44_PATH_LENGTH {
+                return Err(KeyChainError::InvalidBip44DerivationPath(derivation_path));
+            }
+            let path = derivation_path.as_vec();
+            let purpose = KeyPurpose::try_from(path[BIP44_KEY_PURPOSE_INDEX])?;
+            let old_value = match purpose {
+                ReceiveFunds => receiving_addresses.insert(path[BIP44_KEY_INDEX], address),
+                Change => change_addresses.insert(path[BIP44_KEY_INDEX], address),
+            };
+            if old_value.is_some() {
+                return Err(KeyChainError::CouldNotLoadKeyChain);
+            }
+        }
+        Ok((receiving_addresses, change_addresses))
+    }
 
-        // TODO save address
+    pub fn get_account_id(&self) -> AccountId {
+        AccountId::new_from_xpub(&self.account_pubkey)
+    }
+
+    pub fn get_account_key(&self) -> ExtendedPublicKey {
+        self.account_pubkey.clone()
+    }
+
+    /// Issue a new address that hasn't been used before
+    pub fn issue_new_address<B: Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        purpose: KeyPurpose,
+    ) -> KeyChainResult<Address> {
+        let key = self.issue_new_key(db_tx, purpose)?;
+        let derivation_path = key.get_derivation_path().clone();
+
+        let address = Address::from_public_key(&self.chain_config, &key.into_public_key())?;
+        let id = AccountAddressId::new(self.get_account_id(), derivation_path);
+        db_tx.set_address(&id, &address)?;
 
         Ok(address)
     }
 
-    /// Get a new derived key that hasn't been used before
-    pub fn get_new_key(&self, purpose: KeyPurpose) -> KeyChainResult<ExtendedPrivateKey> {
-        // TODO implement with correct paths
-        let hd_path = match purpose {
-            KeyPurpose::ReceiveFunds => DerivationPath::from_str("m/0'/0'/0'/0'")?,
-            KeyPurpose::Change => DerivationPath::from_str("m/0'/0'/1'/0'")?,
+    /// Issue a new derived key that hasn't been used before
+    pub fn issue_new_key<B: Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        purpose: KeyPurpose,
+    ) -> KeyChainResult<ExtendedPublicKey> {
+        let usage_state = self.get_usage_state(purpose);
+        let new_issued = {
+            match usage_state.get_last_issued() {
+                None => ChildNumber::ZERO,
+                Some(last_issued) => last_issued.increment()?,
+            }
+        };
+
+        self.check_issued_lookahead(purpose, new_issued)?;
+
+        // The path of the new key
+        let key_path = {
+            let mut path = self.account_pubkey.get_derivation_path().clone().into_vec();
+            path.push(KeyPurpose::DETERMINISTIC_INDEX[purpose as usize]);
+            path.push(new_issued);
+            path.try_into()?
         };
+
         // TODO get key from a precalculated pool
-        let new_key = self.master_key.clone().derive_absolute_path(&hd_path)?;
+        let new_key = self.account_pubkey.clone().derive_absolute_path(&key_path)?;
+        // Update the last issued index
+        self.get_usage_state_mut(purpose).set_last_issued(Some(new_issued));
+        Self::persist_account_info(self, db_tx)?;
+
         Ok(new_key)
     }
+
+    fn check_issued_lookahead(
+        &self,
+        purpose: KeyPurpose,
+        new_last_issued: ChildNumber,
+    ) -> KeyChainResult<()> {
+        let usage_state = self.get_usage_state(purpose);
+
+        let new_issued_index = new_last_issued.get_index();
+        let lookahead = self.lookahead_size as u32;
+
+        // Check if the issued addresses are less or equal to lookahead size
+        let lookahead_exceeded = match usage_state.get_last_used() {
+            None => new_issued_index >= lookahead,
+            Some(last_used_index) => new_issued_index > last_used_index.get_index() + lookahead,
+        };
+
+        if lookahead_exceeded {
+            Err(KeyChainError::LookAheadExceeded)
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get the private key that corresponds to the provided public key
+    #[allow(dead_code)] // TODO remove
+    pub(crate) fn get_private_key(
+        &self,
+        parent_key: &ExtendedPrivateKey,
+        requested_key: &ExtendedPublicKey,
+    ) -> KeyChainResult<ExtendedPrivateKey> {
+        let derived_key =
+            parent_key.clone().derive_absolute_path(requested_key.get_derivation_path())?;
+        if &derived_key.to_public_key() == requested_key {
+            Ok(derived_key)
+        } else {
+            Err(KeyChainError::KeysNotInSameHierarchy)
+        }
+    }
+
+    fn get_usage_state(&self, purpose: KeyPurpose) -> &KeychainUsageState {
+        match purpose {
+            ReceiveFunds => &self.receiving_state,
+            Change => &self.change_state,
+        }
+    }
+
+    /// Get the mutable usage state, this is used internally with database persistence done
+    /// externally.
+    fn get_usage_state_mut(&mut self, purpose: KeyPurpose) -> &mut KeychainUsageState {
+        match purpose {
+            ReceiveFunds => &mut self.receiving_state,
+            Change => &mut self.change_state,
+        }
+    }
+
+    /// Derive addresses until there are lookahead unused ones
+    fn top_up_all(&mut self) -> KeyChainResult<()> {
+        for purpose in KeyPurpose::ALL {
+            self.top_up(purpose)?
+        }
+        Ok(())
+    }
+
+    /// Derive addresses for the `purpose` key chain
+    fn top_up(&mut self, purpose: KeyPurpose) -> KeyChainResult<()> {
+        // TODO add db_tx
+        let _dest = match purpose {
+            ReceiveFunds => &mut self.receiving_addresses,
+            Change => &mut self.change_addresses,
+        };
+        Ok(())
+    }
+
+    pub(crate) fn get_account_info(&self) -> AccountInfo {
+        AccountInfo::Deterministic(DeterministicAccountInfo::new(
+            self.root_hierarchy_key.clone(),
+            self.account_pubkey.clone(),
+            self.lookahead_size,
+            self.get_usage_state(ReceiveFunds).clone(),
+            self.get_usage_state(Change).clone(),
+        ))
+    }
+
+    pub fn get_lookahead_size(&self) -> u16 {
+        self.lookahead_size
+    }
+
+    pub fn set_lookahead_size<B: Backend>(
+        &mut self,
+        db_tx: &mut StoreTxRw<B>,
+        lookahead_size: u16,
+    ) -> KeyChainResult<()> {
+        self.lookahead_size = lookahead_size;
+        Self::persist_account_info(self, db_tx)
+    }
+
+    fn persist_account_info<B: Backend>(
+        account: &mut Self,
+        db_tx: &mut StoreTxRw<B>,
+    ) -> KeyChainResult<()> {
+        db_tx.set_account(&account.get_account_id(), &account.get_account_info())?;
+        account.top_up_all()
+    }
+}
+
+/// Create a deterministic path for an account identified by the `account_index`
+fn make_account_path(chain_config: &ChainConfig, account_index: ChildNumber) -> DerivationPath {
+    // The path is m/44'/<coin_type>'/<account_index>'
+    let path = vec![BIP44_PATH, chain_config.bip44_coin_type(), account_index];
+    path.try_into().expect("Path creation should not fail")
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
     use common::chain::config::create_unit_test_config;
+    use rstest::rstest;
+    use std::str::FromStr;
     use test_utils::assert_encoded_eq;
-    use wallet_storage::{DefaultBackend, Store};
+    use wallet_storage::{DefaultBackend, Store, TransactionRw, Transactional};
 
+    const ZERO_H: ChildNumber = ChildNumber::from_hardened(U31::from_u32_with_msb(0).0);
     const MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
 
-    #[test]
-    fn key_chain_creation() {
+    #[rstest]
+    #[case(
+        KeyPurpose::ReceiveFunds,
+        "m/44'/19788'/0'/0/0",
+        "8000002c80004d4c800000000000000000000000",
+        "b870ce52f8ccb3204e7fcdbb84f122fee29ce3c462750c54d411201baa4cf23c",
+        "03bf6f8d52dade77f95e9c6c9488fd8492a99c09ff23095caffb2e6409d1746ade",
+        "0ae454a1024d0ddb9e10d23479cf8ef39fb400727fabd17844bd8362b1c70d7d"
+    )]
+    #[case(
+        KeyPurpose::Change,
+        "m/44'/19788'/0'/1/0",
+        "8000002c80004d4c800000000000000100000000",
+        "f2b1cb7118920fe9b3a0470bd67588fb4bdd4af1355ff39171ed41e968a8621b",
+        "035df5d551bac1d61a5473615a70eb17b2f4ccbf7e354166639428941e4dbbcd81",
+        "8d62d08e7a23e4b510b970ffa84b4a5ed22e6c03faecf32c5dafaf092938516d"
+    )]
+    #[case(
+        KeyPurpose::ReceiveFunds,
+        "m/44'/19788'/0'/0/1",
+        "8000002c80004d4c800000000000000000000001",
+        "f73943cf443cd5cdd6c35e3fc1c8f039dd92c29a3d9fc1f56c5145ad67535fba",
+        "030d1d07a8e45110d14f4e2c8623e8db556c11a90c0aac6be9a88f2464e446ee95",
+        "7ed12073a4cc61d8a79f3dc0dfc5ca1a23d9ce1fe3c1e92d3b6939cd5848a390"
+    )]
+    fn key_chain_creation(
+        #[case] purpose: KeyPurpose,
+        #[case] path_str: &str,
+        #[case] path_encoded_str: &str,
+        #[case] secret: &str,
+        #[case] public: &str,
+        #[case] chaincode: &str,
+    ) {
         let chain_config = Arc::new(create_unit_test_config());
         let db = Arc::new(Store::new(DefaultBackend::new_in_memory()).unwrap());
-        let key_chain = KeyChain::new_from_mnemonic(chain_config, MNEMONIC, None, db).unwrap();
-
-        // Sort test vectors by key_index i.e. the key with index 0 should be first
-        let test_vec = vec![
-            (
-                KeyPurpose::ReceiveFunds,
-                "m/0'/0'/0'/0'",
-                "80000000800000008000000080000000",
-                "04feff4263658459430aea33cb851b830a0235db1611d3279624f40c7c2c0135",
-                "031ac0ee91fe1ff500f4b21579cda6ded3b10a2f9162d571b4c8873454f3593326",
-                "4fddb29b630431422b3a534e0028e053eb212ab10a5f1db3ba5cbc4e81ff3294",
-            ),
-            (
-                KeyPurpose::Change,
-                "m/0'/0'/1'/0'",
-                "80000000800000008000000180000000",
-                "404dafb8e79d3110e816be00e020a91ef1754ab6b2ada14ec87a26f87e86e19e",
-                "03305f803928705f620e6a05dce2e4a6f8c03d1dc0757008096bf689160f394641",
-                "04e13b373ed3d5753657d375feec032187cdada01e5df83cc8fddd29c1f15755",
-            ),
-        ];
-
-        for (purpose, path_str, path_encoded_str, secret, public, chaincode) in test_vec {
-            let sk = key_chain.get_new_key(purpose).unwrap();
-            let pk = ExtendedPublicKey::from_private_key(&sk);
-            let pk2 = ExtendedPublicKey::from_private_key(&sk);
-            assert_eq!(pk2.get_derivation_path().to_string(), path_str.to_string());
-            assert_eq!(pk, pk2);
-            let path = DerivationPath::from_str(path_str).unwrap();
-            assert_eq!(sk.get_derivation_path(), &path);
-            assert_eq!(pk.get_derivation_path(), &path);
-            let path_len = path.len();
-            assert_encoded_eq(
-                &sk,
-                format!("00{path_len:02x}{path_encoded_str}{chaincode}{secret}").as_str(),
-            );
-            assert_encoded_eq(
-                &pk,
-                format!("00{path_len:02x}{path_encoded_str}{chaincode}{public}").as_str(),
-            );
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        let master_key_chain =
+            MasterKeyChain::new_from_mnemonic(chain_config, &mut db_tx, MNEMONIC, None).unwrap();
+
+        let mut key_chain = master_key_chain.create_account_key_chain(&mut db_tx, ZERO_H).unwrap();
+        db_tx.commit().unwrap();
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        let path = DerivationPath::from_str(path_str).unwrap();
+        // Derive expected key
+        let pk = {
+            let key_index = path.as_vec().last().unwrap().get_index();
+            // Derive previous key if necessary
+            if key_index > 0 {
+                for _ in 0..key_index {
+                    let _ = key_chain.issue_new_key(&mut db_tx, purpose).unwrap();
+                }
+            }
+            key_chain.issue_new_key(&mut db_tx, purpose).unwrap()
+        };
+        assert_eq!(pk.get_derivation_path().to_string(), path_str.to_string());
+        let sk = key_chain.get_private_key(&master_key_chain.root_key, &pk).unwrap();
+        let pk2 = ExtendedPublicKey::from_private_key(&sk);
+        assert_eq!(pk2.get_derivation_path().to_string(), path_str.to_string());
+        assert_eq!(pk, pk2);
+        assert_eq!(sk.get_derivation_path(), &path);
+        assert_eq!(pk.get_derivation_path(), &path);
+        let path_len = path.len();
+        assert_encoded_eq(
+            &sk,
+            format!("00{path_len:02x}{path_encoded_str}{chaincode}{secret}").as_str(),
+        );
+        assert_encoded_eq(
+            &pk,
+            format!("00{path_len:02x}{path_encoded_str}{chaincode}{public}").as_str(),
+        );
+    }
+
+    #[rstest]
+    #[case(KeyPurpose::ReceiveFunds)]
+    #[case(KeyPurpose::Change)]
+    fn key_lookahead(#[case] purpose: KeyPurpose) {
+        let chain_config = Arc::new(create_unit_test_config());
+        let db = Arc::new(Store::new(DefaultBackend::new_in_memory()).unwrap());
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        let master_key_chain =
+            MasterKeyChain::new_from_mnemonic(chain_config, &mut db_tx, MNEMONIC, None).unwrap();
+        let mut key_chain = master_key_chain.create_account_key_chain(&mut db_tx, ZERO_H).unwrap();
+        db_tx.commit().unwrap();
+
+        let id = key_chain.get_account_id();
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+        key_chain.set_lookahead_size(&mut db_tx, 5).unwrap();
+        assert_eq!(key_chain.get_lookahead_size(), 5);
+
+        // Issue new addresses until the lookahead size is reached
+        for _ in 0..5 {
+            key_chain.issue_new_address(&mut db_tx, purpose).unwrap();
+        }
+        assert_eq!(
+            key_chain.issue_new_address(&mut db_tx, purpose),
+            Err(KeyChainError::LookAheadExceeded)
+        );
+        db_tx.commit().unwrap();
+        drop(key_chain);
+
+        let mut key_chain = master_key_chain
+            .load_keychain_from_database(&db.transaction_ro().unwrap(), &id)
+            .unwrap();
+        assert_eq!(key_chain.get_lookahead_size(), 5);
+        assert_eq!(key_chain.get_usage_state(purpose).get_last_used(), None);
+        assert_eq!(
+            key_chain.get_usage_state(purpose).get_last_issued(),
+            Some(ChildNumber::from_normal(U31::from_u32_with_msb(4).0))
+        );
+
+        let mut db_tx = db.transaction_rw(None).unwrap();
+
+        // Increase the lookahead size
+        key_chain.set_lookahead_size(&mut db_tx, 10).unwrap();
+
+        // Should be able to issue more addresses
+        for _ in 0..5 {
+            key_chain.issue_new_address(&mut db_tx, purpose).unwrap();
         }
+        assert_eq!(
+            key_chain.issue_new_address(&mut db_tx, purpose),
+            Err(KeyChainError::LookAheadExceeded)
+        );
+
+        // TODO mark an address as used and issue more addresses
     }
 }
diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs
index 70f83c58d..4c7fa6d54 100644
--- a/wallet/src/lib.rs
+++ b/wallet/src/lib.rs
@@ -13,7 +13,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+pub mod account;
 mod key_chain;
 pub mod wallet;
 
-pub use crate::wallet::{Wallet, WalletError};
+pub use crate::account::Account;
+pub use crate::wallet::{Wallet, WalletError, WalletResult};
diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs
index 4c65ef763..6ddd86ab7 100644
--- a/wallet/src/wallet.rs
+++ b/wallet/src/wallet.rs
@@ -13,23 +13,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use common::address::Address;
-use std::collections::BTreeMap;
 use std::path::Path;
 use std::sync::Arc;
 
-use crate::key_chain::{KeyChain, KeyChainError, KeyPurpose};
-use common::chain::{OutPoint, Transaction, TxOutput};
-use common::primitives::{Id, Idable};
-use utxo::Utxo;
+use crate::key_chain::{KeyChainError, MasterKeyChain};
+use common::chain::{ChainConfig, Transaction};
+use common::primitives::Id;
 use wallet_storage::{
-    DefaultBackend, Store, StoreTxRw, TransactionRw, Transactional, WalletStorageWrite,
+    DefaultBackend, Store, TransactionRw, Transactional, WalletStorageRead, WalletStorageWrite,
 };
-use wallet_types::{TxState, WalletTx};
+use wallet_types::AccountId;
+
+pub const WALLET_VERSION_UNINITIALIZED: u32 = 0;
+pub const WALLET_VERSION_V1: u32 = 1;
+pub const CURRENT_WALLET_VERSION: u32 = WALLET_VERSION_V1;
 
 /// Wallet errors
 #[derive(thiserror::Error, Debug, Eq, PartialEq)]
 pub enum WalletError {
+    #[error("Wallet is not initialized")]
+    WalletNotInitialized,
     #[error("Wallet database error: {0}")]
     DatabaseError(#[from] wallet_storage::Error),
     #[error("Transaction already present: {0}")]
@@ -38,169 +41,104 @@ pub enum WalletError {
     NoTransactionFound(Id<Transaction>),
     #[error("Key chain error: {0}")]
     KeyChainError(#[from] KeyChainError),
+    #[error("No account found")] // TODO implement display for AccountId
+    NoAccountFound(AccountId),
 }
 
 /// Result type used for the wallet
-type WalletResult<T> = Result<T, WalletError>;
+pub type WalletResult<T> = Result<T, WalletError>;
 
 #[allow(dead_code)] // TODO remove
 pub struct Wallet<B: storage::Backend> {
+    chain_config: Arc<ChainConfig>,
     db: Arc<Store<B>>,
-    key_chain: KeyChain<B>,
-    txs: BTreeMap<Id<Transaction>, WalletTx>,
-    utxo: BTreeMap<OutPoint, Utxo>,
+    // key_chain: MasterKeyChain<B>,
+    key_chain: MasterKeyChain,
 }
 
-pub fn open_wallet_file<P: AsRef<Path>>(path: P) -> WalletResult<Wallet<DefaultBackend>> {
-    let db = Store::new(DefaultBackend::new(path))?;
-
-    Wallet::load_wallet(db)
+pub fn open_or_create_wallet_file<P: AsRef<Path>>(
+    path: P,
+) -> WalletResult<Arc<Store<DefaultBackend>>> {
+    Ok(Arc::new(Store::new(DefaultBackend::new(path))?))
 }
 
-pub fn open_wallet_in_memory() -> WalletResult<Wallet<DefaultBackend>> {
-    let db = Store::new(DefaultBackend::new_in_memory())?;
-
-    Wallet::load_wallet(db)
+pub fn open_or_create_wallet_in_memory() -> WalletResult<Arc<Store<DefaultBackend>>> {
+    Ok(Arc::new(Store::new(DefaultBackend::new_in_memory())?))
 }
 
 impl<B: storage::Backend> Wallet<B> {
-    fn load_wallet(db: Store<B>) -> WalletResult<Self> {
-        let db = Arc::new(db);
-        let txs = db.read_transactions()?;
-        let utxo = db.read_utxo_set()?;
-        let key_chain = KeyChain::load_key_chain(db.clone())?;
+    #[allow(dead_code)] // TODO remove
+    fn new_wallet(
+        chain_config: Arc<ChainConfig>,
+        db: Arc<Store<B>>,
+        mnemonic: &str,
+        passphrase: Option<&str>,
+    ) -> WalletResult<Self> {
+        let mut db_tx = db.transaction_rw(None)?;
+
+        // TODO wallet should save the chain config
+
+        let key_chain = MasterKeyChain::new_from_mnemonic(
+            chain_config.clone(),
+            &mut db_tx,
+            mnemonic,
+            passphrase,
+        )?;
+
+        db_tx.set_storage_version(CURRENT_WALLET_VERSION)?;
+        db_tx.commit()?;
+
         Ok(Wallet {
+            chain_config,
             db,
             key_chain,
-            txs,
-            utxo,
         })
     }
 
-    pub fn get_database(&self) -> &Store<B> {
-        &self.db
-    }
-
-    /// Get a new address that hasn't been used before
-    pub fn get_new_address(&mut self, purpose: KeyPurpose) -> WalletResult<Address> {
-        Ok(self.key_chain.get_new_address(purpose)?)
-    }
-
-    #[allow(dead_code)] // TODO remove
-    fn add_transaction(&mut self, tx: Transaction, state: TxState) -> WalletResult<()> {
-        let tx_id = tx.get_id();
-
-        if self.txs.contains_key(&tx_id) {
-            return Err(WalletError::DuplicateTransaction(tx_id));
-        }
-
-        let mut db_tx = self.db.transaction_rw(None)?;
-
-        let wallet_tx = WalletTx::new(tx, state);
-
-        db_tx.set_transaction(&tx_id, &wallet_tx)?;
-        db_tx.commit()?;
-
-        self.txs.insert(tx_id, wallet_tx);
-
-        // TODO add UTXO?
-
-        Ok(())
-    }
-
     #[allow(dead_code)] // TODO remove
-    fn delete_transaction(&mut self, tx_id: Id<Transaction>) -> WalletResult<()> {
-        if !self.txs.contains_key(&tx_id) {
-            return Err(WalletError::NoTransactionFound(tx_id));
+    fn load_wallet(chain_config: Arc<ChainConfig>, db: Arc<Store<B>>) -> WalletResult<Self> {
+        let version = db.get_storage_version()?;
+        if version == WALLET_VERSION_UNINITIALIZED {
+            return Err(WalletError::WalletNotInitialized);
         }
 
-        let mut db_tx = self.db.transaction_rw(None)?;
-        db_tx.del_transaction(&tx_id)?;
-        db_tx.commit()?;
-
-        self.txs.remove(&tx_id);
+        let key_chain =
+            MasterKeyChain::load_from_database(chain_config.clone(), &db.transaction_ro()?)?;
 
-        // TODO remove UTXO?
-
-        Ok(())
-    }
-
-    // TODO fix incompatibility between borrowing mut self and the database transaction
-    #[allow(dead_code)] // TODO remove
-    fn add_to_utxos(&mut self, tx: &Transaction, db_tx: &mut StoreTxRw<B>) -> WalletResult<()> {
-        for (i, output) in tx.outputs().iter().enumerate() {
-            // Check if this output belongs to this wallet or it is watched
-            if self.is_available_for_spending(output) && self.is_mine_or_watched(output) {
-                let outpoint = OutPoint::new(tx.get_id().into(), i as u32);
-                let utxo = Utxo::new(output.clone(), false, utxo::UtxoSource::Mempool);
-                self.utxo.insert(outpoint.clone(), utxo.clone());
-                db_tx.set_utxo(&outpoint, utxo)?;
-            }
-        }
-        Ok(())
-    }
-
-    #[allow(dead_code)] // TODO remove
-    fn is_available_for_spending(&self, _txo: &TxOutput) -> bool {
-        // TODO implement
-        true
+        Ok(Wallet {
+            chain_config,
+            db,
+            key_chain,
+        })
     }
 
-    #[allow(dead_code)] // TODO remove
-    fn is_mine_or_watched(&self, _txo: &TxOutput) -> bool {
-        // TODO implement
-        true
+    pub fn get_database(&self) -> &Store<B> {
+        &self.db
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use common::chain::{GenBlock, Transaction};
-    use common::primitives::H256;
+    use common::chain::config::create_regtest;
 
-    #[test]
-    fn in_memory_wallet() {
-        let wallet = open_wallet_in_memory();
-        assert!(wallet.is_ok())
-    }
+    const MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
 
     #[test]
-    fn wallet_transactions() {
-        let temp_dir_path = tempfile::TempDir::new().unwrap();
-        let wallet_path = temp_dir_path.path().join("test_wallet_transactions.sqlite");
-
-        let mut wallet = open_wallet_file(wallet_path.as_path()).expect("the wallet to load");
-
-        let tx1 = Transaction::new(1, vec![], vec![], 0).unwrap();
-        let tx2 = Transaction::new(2, vec![], vec![], 0).unwrap();
-        let tx3 = Transaction::new(3, vec![], vec![], 0).unwrap();
-        let tx4 = Transaction::new(4, vec![], vec![], 0).unwrap();
+    fn wallet_creation_in_memory() {
+        let chain_config = Arc::new(create_regtest());
+        let db = open_or_create_wallet_in_memory().unwrap();
 
-        let block_id: Id<GenBlock> = H256::from_low_u64_le(123).into();
-
-        wallet.add_transaction(tx1.clone(), TxState::Confirmed(block_id)).unwrap();
-        wallet.add_transaction(tx2.clone(), TxState::Conflicted(block_id)).unwrap();
-        wallet.add_transaction(tx3.clone(), TxState::InMempool).unwrap();
-        wallet.add_transaction(tx4.clone(), TxState::Inactive).unwrap();
-        drop(wallet);
-
-        let mut wallet = open_wallet_file(wallet_path.as_path()).expect("the wallet to load");
-
-        assert_eq!(4, wallet.txs.len());
-        assert_eq!(&tx1, wallet.txs.get(&tx1.get_id()).unwrap().get_tx());
-        assert_eq!(&tx2, wallet.txs.get(&tx2.get_id()).unwrap().get_tx());
-        assert_eq!(&tx3, wallet.txs.get(&tx3.get_id()).unwrap().get_tx());
-        assert_eq!(&tx4, wallet.txs.get(&tx4.get_id()).unwrap().get_tx());
+        match Wallet::load_wallet(chain_config.clone(), db.clone()) {
+            Ok(_) => panic!("Wallet loading should fail"),
+            Err(err) => assert_eq!(err, WalletError::WalletNotInitialized),
+        }
 
-        wallet.delete_transaction(tx1.get_id()).unwrap();
-        wallet.delete_transaction(tx3.get_id()).unwrap();
+        let wallet = Wallet::new_wallet(chain_config.clone(), db.clone(), MNEMONIC, None);
+        assert!(wallet.is_ok());
         drop(wallet);
 
-        let wallet = open_wallet_file(wallet_path.as_path()).expect("the wallet to load");
-
-        assert_eq!(2, wallet.txs.len());
-        assert_eq!(&tx2, wallet.txs.get(&tx2.get_id()).unwrap().get_tx());
-        assert_eq!(&tx4, wallet.txs.get(&tx4.get_id()).unwrap().get_tx());
+        let wallet = Wallet::load_wallet(chain_config, db);
+        assert!(wallet.is_ok());
     }
 }
diff --git a/wallet/storage/src/internal/mod.rs b/wallet/storage/src/internal/mod.rs
index 4203fff56..1179b96c0 100644
--- a/wallet/storage/src/internal/mod.rs
+++ b/wallet/storage/src/internal/mod.rs
@@ -15,20 +15,20 @@
 
 use std::collections::BTreeMap;
 
-use common::{
-    chain::{OutPoint, Transaction},
-    primitives::Id,
-};
+use common::address::Address;
 use utxo::Utxo;
 
 use crate::{
-    schema::{self as db, Schema},
-    TransactionRw, Transactional, WalletStorage, WalletStorageRead, WalletStorageWrite,
+    schema::Schema, TransactionRw, Transactional, WalletStorage, WalletStorageRead,
+    WalletStorageWrite,
 };
 
 mod store_tx;
 pub use store_tx::{StoreTxRo, StoreTxRw};
-use wallet_types::WalletTx;
+use wallet_types::{
+    AccountAddressId, AccountId, AccountInfo, AccountOutPointId, AccountTxId, RootKeyContent,
+    RootKeyId, WalletTx,
+};
 
 /// Store for wallet data, parametrized over the backend B
 pub struct Store<B: storage::Backend>(storage::Storage<B, Schema>);
@@ -36,8 +36,7 @@ pub struct Store<B: storage::Backend>(storage::Storage<B, Schema>);
 impl<B: storage::Backend> Store<B> {
     /// Create a new wallet storage
     pub fn new(backend: B) -> crate::Result<Self> {
-        let mut storage = Self(storage::Storage::new(backend).map_err(crate::Error::from)?);
-        storage.set_storage_version(1)?;
+        let storage = Self(storage::Storage::new(backend).map_err(crate::Error::from)?);
         Ok(storage)
     }
 
@@ -45,18 +44,6 @@ impl<B: storage::Backend> Store<B> {
     pub fn dump_raw(&self) -> crate::Result<storage::raw::StorageContents<Schema>> {
         self.0.dump_raw().map_err(crate::Error::from)
     }
-
-    /// Collect and return all utxos from the storage
-    pub fn read_utxo_set(&self) -> crate::Result<BTreeMap<OutPoint, Utxo>> {
-        let db_tx = self.transaction_ro()?;
-        db_tx.0.get::<db::DBUtxo, _>().prefix_iter_decoded(&()).map(Iterator::collect)
-    }
-
-    /// Collect and return all transactions from the storage
-    pub fn read_transactions(&self) -> crate::Result<BTreeMap<Id<Transaction>, WalletTx>> {
-        let db_tx = self.transaction_ro()?;
-        db_tx.0.get::<db::DBTxs, _>().prefix_iter_decoded(&()).map(Iterator::collect)
-    }
 }
 
 impl<B: storage::Backend> Clone for Store<B>
@@ -111,18 +98,32 @@ macro_rules! delegate_to_transaction {
 impl<B: storage::Backend> WalletStorageRead for Store<B> {
     delegate_to_transaction! {
         fn get_storage_version(&self) -> crate::Result<u32>;
-        fn get_utxo(&self, outpoint: &OutPoint) -> crate::Result<Option<Utxo>>;
-        fn get_transaction(&self, id: &Id<Transaction>) -> crate::Result<Option<WalletTx>>;
+        fn get_utxo(&self, outpoint: &AccountOutPointId) -> crate::Result<Option<Utxo>>;
+        fn get_utxo_set(&self, account_id: &AccountId) -> crate::Result<BTreeMap<AccountOutPointId, Utxo>>;
+        fn get_transaction(&self, id: &AccountTxId) -> crate::Result<Option<WalletTx>>;
+        fn get_transactions(&self,account_id: &AccountId) -> crate::Result<BTreeMap<AccountTxId, WalletTx>>;
+        fn get_account(&self, id: &AccountId) -> crate::Result<Option<AccountInfo>>;
+        fn get_address(&self, id: &AccountAddressId) -> crate::Result<Option<Address>>;
+        fn get_addresses(&self, account_id: &AccountId) -> crate::Result<BTreeMap<AccountAddressId, Address>>;
+        fn get_root_key(&self, id: &RootKeyId) -> crate::Result<Option<RootKeyContent >>;
+        fn get_all_root_keys(&self) -> crate::Result<BTreeMap<RootKeyId, RootKeyContent >>;
+
     }
 }
 
 impl<B: storage::Backend> WalletStorageWrite for Store<B> {
     delegate_to_transaction! {
         fn set_storage_version(&mut self, version: u32) -> crate::Result<()>;
-        fn set_utxo(&mut self, outpoint: &OutPoint, entry: Utxo) -> crate::Result<()>;
-        fn del_utxo(&mut self, outpoint: &OutPoint) -> crate::Result<()>;
-        fn set_transaction(&mut self, id: &Id<Transaction>, tx: &WalletTx) -> crate::Result<()>;
-        fn del_transaction(&mut self, id: &Id<Transaction>) -> crate::Result<()>;
+        fn set_utxo(&mut self, outpoint: &AccountOutPointId, entry: Utxo) -> crate::Result<()>;
+        fn del_utxo(&mut self, outpoint: &AccountOutPointId) -> crate::Result<()>;
+        fn set_transaction(&mut self, id: &AccountTxId, tx: &WalletTx) -> crate::Result<()>;
+        fn del_transaction(&mut self, id: &AccountTxId) -> crate::Result<()>;
+        fn set_account(&mut self, id: &AccountId, content: &AccountInfo) -> crate::Result<()>;
+        fn del_account(&mut self, id: &AccountId) -> crate::Result<()>;
+        fn set_address(&mut self, id: &AccountAddressId, address: &Address) -> crate::Result<()>;
+        fn del_address(&mut self, id: &AccountAddressId) -> crate::Result<()>;
+        fn set_root_key(&mut self, id: &RootKeyId, content: &RootKeyContent) -> crate::Result<()>;
+        fn del_root_key(&mut self, id: &RootKeyId) -> crate::Result<()>;
     }
 }
 
diff --git a/wallet/storage/src/internal/store_tx.rs b/wallet/storage/src/internal/store_tx.rs
index 55650f110..a882b192b 100644
--- a/wallet/storage/src/internal/store_tx.rs
+++ b/wallet/storage/src/internal/store_tx.rs
@@ -13,12 +13,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use common::chain::{OutPoint, Transaction};
-use common::primitives::Id;
+use std::collections::BTreeMap;
+
+use common::address::Address;
 use serialization::{Codec, DecodeAll, Encode, EncodeLike};
 use storage::schema;
 use utxo::Utxo;
-use wallet_types::WalletTx;
+use wallet_types::{
+    AccountAddressId, AccountId, AccountInfo, AccountOutPointId, AccountTxId, RootKeyContent,
+    RootKeyId, WalletTx,
+};
 
 use crate::{
     schema::{self as db, Schema},
@@ -63,13 +67,65 @@ macro_rules! impl_read_ops {
                 self.read_value::<well_known::StoreVersion>().map(|v| v.unwrap_or_default())
             }
 
-            fn get_utxo(&self, outpoint: &OutPoint) -> crate::Result<Option<Utxo>> {
+            fn get_utxo(&self, outpoint: &AccountOutPointId) -> crate::Result<Option<Utxo>> {
                 self.read::<db::DBUtxo, _, _>(outpoint)
             }
 
-            fn get_transaction(&self, id: &Id<Transaction>) -> crate::Result<Option<WalletTx>> {
+            /// Collect and return all utxos from the storage
+            fn get_utxo_set(
+                &self,
+                account_id: &AccountId,
+            ) -> crate::Result<BTreeMap<AccountOutPointId, Utxo>> {
+                self.0
+                    .get::<db::DBUtxo, _>()
+                    .prefix_iter_decoded(account_id)
+                    .map(Iterator::collect)
+            }
+
+            fn get_transaction(&self, id: &AccountTxId) -> crate::Result<Option<WalletTx>> {
                 self.read::<db::DBTxs, _, _>(id)
             }
+
+            fn get_account(&self, id: &AccountId) -> crate::Result<Option<AccountInfo>> {
+                self.read::<db::DBAccounts, _, _>(id)
+            }
+
+            fn get_address(&self, id: &AccountAddressId) -> crate::Result<Option<Address>> {
+                self.read::<db::DBAddresses, _, _>(id)
+            }
+
+            fn get_addresses(
+                &self,
+                account_id: &AccountId,
+            ) -> crate::Result<BTreeMap<AccountAddressId, Address>> {
+                self.0
+                    .get::<db::DBAddresses, _>()
+                    .prefix_iter_decoded(account_id)
+                    .map(Iterator::collect)
+            }
+
+            fn get_root_key(&self, id: &RootKeyId) -> crate::Result<Option<RootKeyContent>> {
+                self.read::<db::DBRootKeys, _, _>(id)
+            }
+
+            /// Collect and return all keys from the storage
+            fn get_all_root_keys(&self) -> crate::Result<BTreeMap<RootKeyId, RootKeyContent>> {
+                self.0
+                    .get::<db::DBRootKeys, _>()
+                    .prefix_iter_decoded(&())
+                    .map(Iterator::collect)
+            }
+
+            /// Collect and return all transactions from the storage
+            fn get_transactions(
+                &self,
+                account_id: &AccountId,
+            ) -> crate::Result<BTreeMap<AccountTxId, WalletTx>> {
+                self.0
+                    .get::<db::DBTxs, _>()
+                    .prefix_iter_decoded(account_id)
+                    .map(Iterator::collect)
+            }
         }
 
         impl<'st, B: storage::Backend> $TxType<'st, B> {
@@ -105,21 +161,45 @@ impl<'st, B: storage::Backend> WalletStorageWrite for StoreTxRw<'st, B> {
         self.write_value::<well_known::StoreVersion>(&version)
     }
 
-    fn set_utxo(&mut self, outpoint: &OutPoint, entry: Utxo) -> crate::Result<()> {
+    fn set_utxo(&mut self, outpoint: &AccountOutPointId, entry: Utxo) -> crate::Result<()> {
         self.write::<db::DBUtxo, _, _, _>(outpoint, entry)
     }
 
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> crate::Result<()> {
+    fn del_utxo(&mut self, outpoint: &AccountOutPointId) -> crate::Result<()> {
         self.0.get_mut::<db::DBUtxo, _>().del(outpoint).map_err(Into::into)
     }
 
-    fn set_transaction(&mut self, id: &Id<Transaction>, tx: &WalletTx) -> crate::Result<()> {
+    fn set_transaction(&mut self, id: &AccountTxId, tx: &WalletTx) -> crate::Result<()> {
         self.write::<db::DBTxs, _, _, _>(id, tx)
     }
 
-    fn del_transaction(&mut self, id: &Id<Transaction>) -> crate::Result<()> {
+    fn del_transaction(&mut self, id: &AccountTxId) -> crate::Result<()> {
         self.0.get_mut::<db::DBTxs, _>().del(id).map_err(Into::into)
     }
+
+    fn set_account(&mut self, id: &AccountId, tx: &AccountInfo) -> crate::Result<()> {
+        self.write::<db::DBAccounts, _, _, _>(id, tx)
+    }
+
+    fn del_account(&mut self, id: &AccountId) -> crate::Result<()> {
+        self.0.get_mut::<db::DBAccounts, _>().del(id).map_err(Into::into)
+    }
+
+    fn set_address(&mut self, id: &AccountAddressId, address: &Address) -> crate::Result<()> {
+        self.write::<db::DBAddresses, _, _, _>(id, address)
+    }
+
+    fn del_address(&mut self, id: &AccountAddressId) -> crate::Result<()> {
+        self.0.get_mut::<db::DBAddresses, _>().del(id).map_err(Into::into)
+    }
+
+    fn set_root_key(&mut self, id: &RootKeyId, tx: &RootKeyContent) -> crate::Result<()> {
+        self.write::<db::DBRootKeys, _, _, _>(id, tx)
+    }
+
+    fn del_root_key(&mut self, id: &RootKeyId) -> crate::Result<()> {
+        self.0.get_mut::<db::DBRootKeys, _>().del(id).map_err(Into::into)
+    }
 }
 
 impl<'st, B: storage::Backend> StoreTxRw<'st, B> {
diff --git a/wallet/storage/src/lib.rs b/wallet/storage/src/lib.rs
index 093152bbb..14ae9c6ff 100644
--- a/wallet/storage/src/lib.rs
+++ b/wallet/storage/src/lib.rs
@@ -19,12 +19,15 @@ mod internal;
 mod is_transaction_seal;
 pub mod schema;
 
+use common::address::Address;
 pub use internal::{Store, StoreTxRo, StoreTxRw};
+use std::collections::BTreeMap;
 
-use common::chain::{OutPoint, Transaction};
-use common::primitives::Id;
 use utxo::Utxo;
-use wallet_types::WalletTx;
+use wallet_types::{
+    AccountAddressId, AccountId, AccountInfo, AccountOutPointId, AccountTxId, RootKeyContent,
+    RootKeyId, WalletTx,
+};
 
 /// Possibly failing result of wallet storage query
 pub type Result<T> = storage::Result<T>;
@@ -34,18 +37,31 @@ pub type Error = storage::Error;
 pub trait WalletStorageRead {
     /// Get storage version
     fn get_storage_version(&self) -> Result<u32>;
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<Utxo>>;
-    fn get_transaction(&self, id: &Id<Transaction>) -> Result<Option<WalletTx>>;
+    fn get_utxo(&self, outpoint: &AccountOutPointId) -> Result<Option<Utxo>>;
+    fn get_utxo_set(&self, account_id: &AccountId) -> Result<BTreeMap<AccountOutPointId, Utxo>>;
+    fn get_transaction(&self, id: &AccountTxId) -> Result<Option<WalletTx>>;
+    fn get_transactions(&self, account_id: &AccountId) -> Result<BTreeMap<AccountTxId, WalletTx>>;
+    fn get_account(&self, id: &AccountId) -> Result<Option<AccountInfo>>;
+    fn get_address(&self, id: &AccountAddressId) -> Result<Option<Address>>;
+    fn get_addresses(&self, account_id: &AccountId) -> Result<BTreeMap<AccountAddressId, Address>>;
+    fn get_root_key(&self, id: &RootKeyId) -> Result<Option<RootKeyContent>>;
+    fn get_all_root_keys(&self) -> Result<BTreeMap<RootKeyId, RootKeyContent>>;
 }
 
 /// Modifying operations on persistent wallet data
 pub trait WalletStorageWrite: WalletStorageRead {
     /// Set storage version
     fn set_storage_version(&mut self, version: u32) -> Result<()>;
-    fn set_utxo(&mut self, outpoint: &OutPoint, entry: Utxo) -> Result<()>;
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<()>;
-    fn set_transaction(&mut self, id: &Id<Transaction>, tx: &WalletTx) -> Result<()>;
-    fn del_transaction(&mut self, id: &Id<Transaction>) -> Result<()>;
+    fn set_utxo(&mut self, outpoint: &AccountOutPointId, entry: Utxo) -> Result<()>;
+    fn del_utxo(&mut self, outpoint: &AccountOutPointId) -> Result<()>;
+    fn set_transaction(&mut self, id: &AccountTxId, tx: &WalletTx) -> Result<()>;
+    fn del_transaction(&mut self, id: &AccountTxId) -> Result<()>;
+    fn set_account(&mut self, id: &AccountId, content: &AccountInfo) -> Result<()>;
+    fn del_account(&mut self, id: &AccountId) -> Result<()>;
+    fn set_address(&mut self, id: &AccountAddressId, address: &Address) -> Result<()>;
+    fn del_address(&mut self, id: &AccountAddressId) -> Result<()>;
+    fn set_root_key(&mut self, id: &RootKeyId, content: &RootKeyContent) -> Result<()>;
+    fn del_root_key(&mut self, id: &RootKeyId) -> Result<()>;
 }
 
 /// Marker trait for types where read/write operations are run in a transaction
diff --git a/wallet/storage/src/schema.rs b/wallet/storage/src/schema.rs
index 795c26fff..c47fbc97c 100644
--- a/wallet/storage/src/schema.rs
+++ b/wallet/storage/src/schema.rs
@@ -15,22 +15,27 @@
 
 //! Wallet database schema
 
-use wallet_types::WalletTx;
-
-use common::{
-    chain::{OutPoint, Transaction},
-    primitives::Id,
-};
+use common::address::Address;
 use utxo::Utxo;
+use wallet_types::{
+    AccountAddressId, AccountId, AccountInfo, AccountOutPointId, AccountTxId, RootKeyContent,
+    RootKeyId, WalletTx,
+};
 
 storage::decl_schema! {
     /// Database schema for wallet storage
     pub Schema {
         /// Storage for individual values.
         pub DBValue: Map<Vec<u8>, Vec<u8>>,
+        /// Store for all the accounts in this wallet
+        pub DBAccounts: Map<AccountId, AccountInfo>,
+        /// Store for all the private keys in this wallet
+        pub DBRootKeys: Map<RootKeyId, RootKeyContent>,
+        /// Store for all the addresses that belong to an account
+        pub DBAddresses: Map<AccountAddressId, Address>,
         /// Store for Utxo Entries
-        pub DBUtxo: Map<OutPoint, Utxo>,
+        pub DBUtxo: Map<AccountOutPointId, Utxo>,
         /// Store for Transaction entries
-        pub DBTxs: Map<Id<Transaction>, WalletTx>,
+        pub DBTxs: Map<AccountTxId, WalletTx>,
     }
 }
diff --git a/wallet/types/Cargo.toml b/wallet/types/Cargo.toml
index 9545cdfb8..9c0c8b4bf 100644
--- a/wallet/types/Cargo.toml
+++ b/wallet/types/Cargo.toml
@@ -9,7 +9,9 @@ rust-version.workspace = true
 
 [dependencies]
 common = { path = "../../common/" }
+crypto = { path = "../../crypto/" }
 serialization = { path = "../../serialization" }
+storage = { path = '../../storage', features = ['inmemory'] }
 
 bip39 = { version = "2.0", default-features = false, features = ["std", "zeroize"] }
 zeroize = "1.5"
@@ -18,3 +20,5 @@ parity-scale-codec.workspace = true
 
 [dev-dependencies]
 test-utils = { path = "../../test-utils" }
+
+rstest = "0.16"
\ No newline at end of file
diff --git a/wallet/types/src/account_info.rs b/wallet/types/src/account_info.rs
new file mode 100644
index 000000000..51a28c97b
--- /dev/null
+++ b/wallet/types/src/account_info.rs
@@ -0,0 +1,158 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use common::address::pubkeyhash::PublicKeyHash;
+use common::chain::{OutPoint, Transaction};
+use common::primitives::Id;
+use crypto::key::extended::ExtendedPublicKey;
+use crypto::key::hdkd::child_number::ChildNumber;
+use crypto::key::hdkd::derivation_path::DerivationPath;
+use serialization::{Decode, Encode};
+use storage::HasPrefix;
+
+/// The account id is described by it's public key hash or a random value for
+/// non deterministic accounts
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct AccountId(PublicKeyHash);
+
+impl AccountId {
+    pub fn new_from_xpub(pub_key: &ExtendedPublicKey) -> Self {
+        let pk = pub_key.clone().into_public_key();
+        let pkh = PublicKeyHash::from(&pk);
+        AccountId(pkh)
+    }
+}
+
+/// Account metadata that contains information like from which master key it was derived from
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub enum AccountInfo {
+    #[codec(index = 0)]
+    Deterministic(DeterministicAccountInfo),
+}
+
+/// Struct that holds information for account addresses
+#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct KeychainUsageState {
+    /// Last used address index. An address might not be used so the corresponding entry would be
+    /// None, otherwise it would be that last used ChildNumber
+    last_used: Option<ChildNumber>,
+    /// Last issued address to the user. Those addresses can be issued until the
+    /// last used index + lookahead size.
+    last_issued: Option<ChildNumber>,
+}
+
+impl KeychainUsageState {
+    pub fn new(last_used: Option<ChildNumber>, last_issued: Option<ChildNumber>) -> Self {
+        Self {
+            last_used,
+            last_issued,
+        }
+    }
+
+    /// Get the last index used in the blockchain
+    pub fn get_last_used(&self) -> Option<ChildNumber> {
+        self.last_used
+    }
+
+    /// Set the last index used in the blockchain
+    pub fn set_last_used(&mut self, new_last_used: Option<ChildNumber>) {
+        self.last_used = new_last_used;
+    }
+
+    /// Get the last index issued to the user
+    pub fn get_last_issued(&self) -> Option<ChildNumber> {
+        self.last_issued
+    }
+
+    /// Set the last index issued to the user
+    pub fn set_last_issued(&mut self, new_last_issued: Option<ChildNumber>) {
+        self.last_issued = new_last_issued;
+    }
+}
+
+/// Serialized data for deterministic accounts. The fields are documented in `AccountKeyChain`.
+// TODO tbd what metadata we need to store
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct DeterministicAccountInfo {
+    root_hierarchy_key: Option<ExtendedPublicKey>,
+    account_key: ExtendedPublicKey,
+    lookahead_size: u16,
+    // TODO store separately
+    receiving_state: KeychainUsageState,
+    change_state: KeychainUsageState,
+}
+
+impl DeterministicAccountInfo {
+    pub fn new(
+        root_hierarchy_key: Option<ExtendedPublicKey>,
+        account_key: ExtendedPublicKey,
+        lookahead_size: u16,
+        receiving_state: KeychainUsageState,
+        change_state: KeychainUsageState,
+    ) -> Self {
+        Self {
+            root_hierarchy_key,
+            account_key,
+            lookahead_size,
+            receiving_state,
+            change_state,
+        }
+    }
+
+    pub fn get_root_hierarchy_key(&self) -> &Option<ExtendedPublicKey> {
+        &self.root_hierarchy_key
+    }
+
+    pub fn get_account_key(&self) -> &ExtendedPublicKey {
+        &self.account_key
+    }
+
+    pub fn get_lookahead_size(&self) -> u16 {
+        self.lookahead_size
+    }
+
+    pub fn get_receiving_state(&self) -> &KeychainUsageState {
+        &self.receiving_state
+    }
+
+    pub fn get_change_state(&self) -> &KeychainUsageState {
+        &self.change_state
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct AccountPrefixedId<Id> {
+    account_id: AccountId,
+    item_id: Id,
+}
+
+impl<Id: Encode> AccountPrefixedId<Id> {
+    pub fn new(account_id: AccountId, item_id: Id) -> AccountPrefixedId<Id> {
+        Self {
+            account_id,
+            item_id,
+        }
+    }
+
+    pub fn into_item_id(self) -> Id {
+        self.item_id
+    }
+}
+
+impl<ID: Encode> HasPrefix<AccountId> for AccountPrefixedId<ID> {}
+
+pub type AccountTxId = AccountPrefixedId<Id<Transaction>>;
+pub type AccountAddressId = AccountPrefixedId<DerivationPath>;
+pub type AccountOutPointId = AccountPrefixedId<OutPoint>;
diff --git a/wallet/types/src/keys.rs b/wallet/types/src/keys.rs
new file mode 100644
index 000000000..fabf4c28d
--- /dev/null
+++ b/wallet/types/src/keys.rs
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crypto::key::extended::{ExtendedPrivateKey, ExtendedPublicKey};
+use serialization::{Decode, Encode};
+
+/// The key id is described by it's public key
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct RootKeyId(ExtendedPublicKey);
+
+impl From<ExtendedPublicKey> for RootKeyId {
+    fn from(key: ExtendedPublicKey) -> Self {
+        Self(key)
+    }
+}
+
+/// The useful content of this key e.g. a private key or an address depending on the usage
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
+pub struct RootKeyContent(ExtendedPrivateKey);
+
+impl RootKeyContent {
+    pub fn into_key(self) -> ExtendedPrivateKey {
+        self.0
+    }
+}
+
+impl From<ExtendedPrivateKey> for RootKeyContent {
+    fn from(key: ExtendedPrivateKey) -> Self {
+        Self(key)
+    }
+}
diff --git a/wallet/types/src/lib.rs b/wallet/types/src/lib.rs
index 4657be60e..ffca75f8a 100644
--- a/wallet/types/src/lib.rs
+++ b/wallet/types/src/lib.rs
@@ -13,6 +13,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+pub mod account_info;
+pub mod keys;
 pub mod wallet_tx;
 
+pub use account_info::{
+    AccountAddressId, AccountId, AccountInfo, AccountOutPointId, AccountTxId,
+    DeterministicAccountInfo,
+};
+pub use keys::{RootKeyContent, RootKeyId};
 pub use wallet_tx::{TxState, WalletTx};


Test patch:
diff --git a/wallet/storage/src/internal/test.rs b/wallet/storage/src/internal/test.rs
index 5dd43602f..bbd14952a 100644
--- a/wallet/storage/src/internal/test.rs
+++ b/wallet/storage/src/internal/test.rs
@@ -16,8 +16,9 @@
 use super::*;
 use crate::DefaultBackend;
 use common::chain::tokens::OutputValue;
-use common::chain::{Destination, OutPointSourceId, TxOutput};
+use common::chain::{Destination, OutPoint, OutPointSourceId, TxOutput};
 use common::primitives::{Amount, Id, H256};
+use crypto::key::extended::{ExtendedKeyKind, ExtendedPrivateKey};
 use crypto::key::{KeyKind, PrivateKey};
 use crypto::random::Rng;
 use rstest::rstest;
@@ -26,7 +27,8 @@ use test_utils::random::{make_seedable_rng, Seed};
 #[test]
 fn storage_get_default_version_in_tx() {
     utils::concurrency::model(|| {
-        let store = Store::new(DefaultBackend::new_in_memory()).unwrap();
+        let mut store = Store::new(DefaultBackend::new_in_memory()).unwrap();
+        store.set_storage_version(1).unwrap();
         let vtx = store.transaction_ro().unwrap().get_storage_version().unwrap();
         let vst = store.get_storage_version().unwrap();
         assert_eq!(vtx, 1, "Default storage version wrong");
@@ -42,6 +44,11 @@ fn read_write_utxo_in_db_transaction(#[case] seed: Seed) {
     let mut rng = make_seedable_rng(seed);
     let mut db_interface = Store::new(DefaultBackend::new_in_memory()).unwrap();
 
+    // generate an account id
+    let account_id = AccountId::new_from_xpub(
+        &ExtendedPrivateKey::new_from_rng(&mut rng, ExtendedKeyKind::Secp256k1Schnorr).1,
+    );
+
     // generate a utxo and outpoint
     let (_, pub_key) = PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr);
     let output = TxOutput::Transfer(
@@ -54,8 +61,10 @@ fn read_write_utxo_in_db_transaction(#[case] seed: Seed) {
         0,
     );
 
-    assert!(db_interface.set_utxo(&outpoint, utxo.clone()).is_ok());
-    assert_eq!(db_interface.get_utxo(&outpoint), Ok(Some(utxo)));
-    assert!(db_interface.del_utxo(&outpoint).is_ok());
-    assert_eq!(db_interface.get_utxo(&outpoint), Ok(None));
+    let account_outpoint_id = AccountOutPointId::new(account_id, outpoint);
+
+    assert!(db_interface.set_utxo(&account_outpoint_id, utxo.clone()).is_ok());
+    assert_eq!(db_interface.get_utxo(&account_outpoint_id), Ok(Some(utxo)));
+    assert!(db_interface.del_utxo(&account_outpoint_id).is_ok());
+    assert_eq!(db_interface.get_utxo(&account_outpoint_id), Ok(None));
 }