diff --git a/Cargo.lock b/Cargo.lock
index f7fbb6a6aa5792381f4df4cc842aeac7eb13d9b1..254ee0a24bbc32b55572ef554a979c4c39b2a5a8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1454,6 +1454,7 @@ dependencies = [
  "clap",
  "clap_complete",
  "common-runtime",
+ "duniter-storage-node",
  "frame-benchmarking",
  "frame-benchmarking-cli",
  "futures 0.3.19",
@@ -1542,6 +1543,39 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "duniter-storage-node"
+version = "3.0.0"
+dependencies = [
+ "duniter-storage-primitives",
+ "hex-literal",
+ "memory-db",
+ "parity-scale-codec",
+ "sc-client-api",
+ "sp-core",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-trie",
+ "trie-db",
+]
+
+[[package]]
+name = "duniter-storage-primitives"
+version = "3.0.0"
+dependencies = [
+ "async-trait",
+ "frame-support",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-api",
+ "sp-core",
+ "sp-inherents",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "sp-trie",
+]
+
 [[package]]
 name = "dyn-clonable"
 version = "0.9.0"
@@ -4945,6 +4979,30 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-duniter-storage"
+version = "3.0.0"
+dependencies = [
+ "duniter-storage-primitives",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "maplit",
+ "memory-db",
+ "pallet-balances",
+ "pallet-provide-randomness",
+ "parity-scale-codec",
+ "scale-info",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-std",
+ "sp-trie",
+ "trie-db",
+]
+
 [[package]]
 name = "pallet-duniter-test-parameters"
 version = "3.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index e826d2d80ed88674a13b1742c87d25018cb92445..f08dab0524658a6c03d0b4d1cdf9254b5da398da 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,6 +55,7 @@ sp-trie = { git = "https://github.com/librelois/substrate.git", branch = "dunite
 [dependencies]
 # local dependencies
 common-runtime = { path = 'runtime/common' }
+duniter-storage-node = { path = 'node/duniter-storage' }
 g1-runtime = { path = 'runtime/g1', optional = true }
 gdev-runtime = { path = 'runtime/gdev', optional = true }
 gtest-runtime = { path = 'runtime/gtest', optional = true }
@@ -124,15 +125,18 @@ resolver = "2"
 members = [
     'end2end-tests',
     'pallets/certification',
+    'pallets/duniter-storage',
     'pallets/duniter-test-parameters',
     'pallets/duniter-test-parameters/macro',
     'pallets/duniter-wot',
     'pallets/identity',
-    'pallets/membership',
-    'pallets/authority-members',
+	'pallets/membership',
+	'pallets/authority-members',
+	'pallets/provide-randomness',
     'pallets/ud-accounts-storage',
     'pallets/universal-dividend',
-    'pallets/upgrade-origin',
+	'pallets/upgrade-origin',
+    'primitives/duniter-storage',
     'primitives/membership',
     'runtime/common',
     'runtime/gdev',
diff --git a/node/duniter-storage/Cargo.toml b/node/duniter-storage/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..0d8c2968039e4a318a9c64b1a3452fe849bd6a9f
--- /dev/null
+++ b/node/duniter-storage/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+authors = ["librelois <c@elo.tf>"]
+description = "duniter-storage client part"
+edition = "2018"
+homepage = "https://substrate.dev"
+license = "AGPL-3.0"
+name = "duniter-storage-node"
+repository = "https://git.duniter.org/nodes/rust/duniter-v2s"
+version = "3.0.0"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+# local
+duniter-storage-primitives = { path = "../../primitives/duniter-storage" }
+
+# crates.io
+codec = { package = 'parity-scale-codec', version = "2.3.1", features = ["derive"] }
+hex-literal = "0.3"
+memory-db = { version = "0.27.0" }
+trie-db = { version = "0.22.6" }
+
+# substrate
+sc-client-api = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" }
+sp-core = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" }
+sp-runtime = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" }
+sp-state-machine = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" }
+sp-trie = { git = "https://github.com/librelois/substrate.git", branch = "duniter-monthly-2022-02" }
diff --git a/node/duniter-storage/src/lib.rs b/node/duniter-storage/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..df5e71acbc94640652619d59593bd92448b959a9
--- /dev/null
+++ b/node/duniter-storage/src/lib.rs
@@ -0,0 +1,103 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use codec::Decode;
+use sc_client_api::backend::StorageProvider;
+use sc_client_api::{Backend as BackendT, StateBackend, StorageKey};
+use sp_runtime::{
+    generic::BlockId,
+    traits::{BlakeTwo256, Block as BlockT},
+};
+
+/// Prefix of Storage value DuniterStorage.Delta
+// twox128("DuniterStorage") ++ twox128("TrieDelta")
+const DUNITER_STORAGE_DELTA_STORAGE_KEY: [u8; 32] =
+    hex_literal::hex!("54893a1f81bcfacb273bd40750b36f5291ff17e39390d8c052a516c276ec74d6");
+
+type MemoryDB = memory_db::MemoryDB<
+    BlakeTwo256,
+    memory_db::HashKey<BlakeTwo256>,
+    trie_db::DBValue,
+    memory_db::NoopTracker<trie_db::DBValue>,
+>;
+
+/// Create a new [`duniter-storage-primitives::InherentDataProvider`] at the given block.
+pub fn duniter_storage_inherent_data_provider<Backend, Block, Client>(
+    client: &Client,
+    parent: Block::Hash,
+) -> Result<duniter_storage_primitives::InherentDataProvider, sc_client_api::blockchain::Error>
+where
+    Backend: BackendT<Block>,
+    Block: BlockT,
+    Client: StorageProvider<Block, Backend>,
+{
+    let maybe_storage_data = client.storage(
+        &BlockId::Hash(parent),
+        &StorageKey(DUNITER_STORAGE_DELTA_STORAGE_KEY.to_vec()),
+    )?;
+
+    let maybe_delta = if let Some(storage_data) = maybe_storage_data {
+        Some(
+            duniter_storage_primitives::Delta::decode(&mut &storage_data.0[..]).map_err(|e| {
+                sc_client_api::blockchain::Error::Backend(format!(
+                    "duniter_storage_inherent_data_provider: fail to decode Delta from storage: {}",
+                    e
+                ))
+            })?,
+        )
+    } else {
+        None
+    };
+
+    let maybe_proof = if let Some(delta) = maybe_delta {
+        let (db, root) = MemoryDB::default_with_root();
+        let trie_backend = sp_state_machine::TrieBackend::new(db, root);
+        Some(generate_trie_proof(
+            &trie_backend,
+            delta.iter().map(|entry| &entry.0),
+        )?)
+    } else {
+        None
+    };
+
+    Ok(duniter_storage_primitives::InherentDataProvider::new(
+        maybe_proof,
+    ))
+}
+
+fn generate_trie_proof<K, S>(
+    trie_backend: &sp_state_machine::TrieBackend<S, BlakeTwo256>,
+    keys: impl Iterator<Item = K>,
+) -> Result<sp_trie::CompactProof, sc_client_api::blockchain::Error>
+where
+    K: AsRef<[u8]>,
+    S: sp_state_machine::TrieBackendStorage<BlakeTwo256>,
+{
+    let proving_backend = sp_state_machine::ProvingBackend::new(trie_backend);
+    for key in keys {
+        let _ = proving_backend.storage(key.as_ref());
+    }
+    let trie_proof = proving_backend
+        .extract_proof()
+        .into_compact_proof::<BlakeTwo256>(*trie_backend.root())
+        .map_err(|e| {
+            sc_client_api::blockchain::Error::Backend(format!(
+                "duniter_storage_inherent_data_provider: fail to generate compact proof: {}",
+                e
+            ))
+        })?;
+    Ok(trie_proof)
+}
diff --git a/node/src/service.rs b/node/src/service.rs
index d3d68763b2c1cd8ecea7bfcfb21b285eb3c9eb59..85dca57f5c1db122a50e9e47e08ed71b7a475a65 100644
--- a/node/src/service.rs
+++ b/node/src/service.rs
@@ -404,6 +404,9 @@ where
         Vec::default(),
     ));
 
+    // TODO push duniter-storage network protocol here
+    // config.network.extra_sets.push(duniter_storage_peers_set_config);
+
     let (network, system_rpc_tx, network_starter) =
         sc_service::build_network(sc_service::BuildNetworkParams {
             config: &config,
@@ -503,18 +506,23 @@ where
                     commands_stream,
                     select_chain,
                     consensus_data_provider: Some(Box::new(babe_consensus_data_provider)),
-                    create_inherent_data_providers: move |_, _| {
-                        let client = client_clone.clone();
+                    create_inherent_data_providers: move |parent, _| {
+                        let client_clone = client_clone.clone();
                         async move {
                             let timestamp =
                                 manual_seal::consensus::timestamp::SlotTimestampProvider::new_babe(
-                                    client.clone(),
+                                    client_clone.clone(),
                                 )
                                 .map_err(|err| format!("{:?}", err))?;
                             let babe = sp_consensus_babe::inherents::InherentDataProvider::new(
                                 timestamp.slot().into(),
                             );
-                            Ok((timestamp, babe))
+                            let duniter_storage =
+                                duniter_storage_node::duniter_storage_inherent_data_provider(
+                                    &*client_clone,
+                                    parent,
+                                )?;
+                            Ok((timestamp, babe, duniter_storage))
                         }
                     },
                 }),
@@ -550,7 +558,13 @@ where
                                     slot_duration,
                                 );
 
-                        Ok((timestamp, slot, uncles))
+                        let duniter_storage =
+                            duniter_storage_node::duniter_storage_inherent_data_provider(
+                                &*client_clone,
+                                parent,
+                            )?;
+
+                        Ok((timestamp, slot, uncles, duniter_storage))
                     }
                 },
                 force_authoring,
diff --git a/pallets/duniter-storage/Cargo.toml b/pallets/duniter-storage/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..3649f6e9e9e7f461cb660c5b52f5a8a5e9081e06
--- /dev/null
+++ b/pallets/duniter-storage/Cargo.toml
@@ -0,0 +1,110 @@
+[package]
+authors = ['librelois <c@elo.tf>']
+description = 'FRAME pallet duniter storage.'
+edition = '2018'
+homepage = 'https://substrate.dev'
+license = 'AGPL-3.0'
+name = 'pallet-duniter-storage'
+readme = 'README.md'
+repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
+version = '3.0.0'
+
+[features]
+default = ['std']
+runtime-benchmarks = ['frame-benchmarking']
+std = [
+    'codec/std',
+    'duniter-storage-primitives/std',
+    'frame-support/std',
+    'frame-system/std',
+    'frame-benchmarking/std',
+    'pallet-provide-randomness/std',
+    'serde',
+    'sp-core/std',
+    'sp-io/std',
+    'sp-runtime/std',
+	'sp-std/std',
+	'sp-trie/std'
+]
+try-runtime = ['frame-support/try-runtime']
+
+[dependencies]
+# local
+duniter-storage-primitives = { path = "../../primitives/duniter-storage", default-features = false }
+pallet-provide-randomness = { path = "../provide-randomness", default-features = false }
+
+# crates.io
+codec = { package = 'parity-scale-codec', version = "2.3.1", default-features = false, features = ["derive"] }
+scale-info = { version = "1.0", default-features = false, features = ["derive"] }
+
+# substrate
+[dependencies.frame-benchmarking]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+optional = true
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.frame-support]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.frame-system]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.serde]
+version = "1.0.101"
+optional = true
+features = ["derive"]
+
+[dependencies.sp-core]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-io]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-runtime]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-std]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-trie]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+### DOC ###
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+### DEV ###
+
+[dev-dependencies]
+maplit = "1.0.2"
+memory-db = { version = "0.27.0" }
+serde = "1.0.119"
+trie-db = { version = "0.22.6" }
+
+[dev-dependencies.pallet-balances]
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dev-dependencies.sp-io]
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dev-dependencies.sp-state-machine]
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
diff --git a/pallets/duniter-storage/src/lib.rs b/pallets/duniter-storage/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8ab5f524e41bad572307a3301944a190f53ddda9
--- /dev/null
+++ b/pallets/duniter-storage/src/lib.rs
@@ -0,0 +1,646 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![allow(clippy::type_complexity)]
+
+//pub mod traits;
+mod types;
+
+#[cfg(test)]
+mod mock;
+
+#[cfg(test)]
+mod tests;
+
+/*#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;*/
+
+pub use pallet::*;
+pub use types::*;
+
+//use crate::traits::*;
+use pallet_provide_randomness::RequestId;
+use sp_io::hashing::blake2_256;
+use sp_std::prelude::*;
+
+#[frame_support::pallet]
+pub mod pallet {
+    use super::*;
+    use duniter_storage_primitives::*;
+    use frame_support::pallet_prelude::*;
+    use frame_support::traits::{Currency, NamedReservableCurrency, Randomness, StorageVersion};
+    use frame_system::pallet_prelude::*;
+    use sp_core::H256;
+    use sp_runtime::traits::{BlakeTwo256, Convert, IdentifyAccount, One, Verify};
+
+    pub type BalanceOf<T> =
+        <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+    pub type UserProofOf<T> = UserProof<<T as Config>::UserSigner, <T as Config>::UserSignature>;
+
+    const EMPTY_ROOT: H256 = H256([
+        3, 23, 10, 46, 117, 151, 183, 183, 227, 216, 76, 5, 57, 29, 19, 154, 98, 177, 87, 231, 135,
+        134, 216, 192, 130, 242, 157, 207, 76, 17, 19, 20,
+    ]);
+
+    const RESERVE_ID: [u8; 8] = [b'd', b'u', b'n', b'i', b's', b't', b'o', b'r'];
+
+    /// The current storage version.
+    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+
+    #[pallet::pallet]
+    #[pallet::generate_store(pub(super) trait Store)]
+    #[pallet::storage_version(STORAGE_VERSION)]
+    #[pallet::without_storage_info]
+    pub struct Pallet<T>(_);
+
+    // CONFIG //
+
+    #[pallet::config]
+    pub trait Config: frame_system::Config + pallet_provide_randomness::Config {
+        /// Origin authorized to modify the replication factor
+        type ChangeReplicationFactorOrigin: EnsureOrigin<Self::Origin>;
+        /// The currency mechanism.
+        type Currency: NamedReservableCurrency<Self::AccountId, ReserveIdentifier = [u8; 8]>;
+        #[pallet::constant]
+        type UserDeposit: Get<BalanceOf<Self>>;
+        #[pallet::constant]
+        type EntryExpiration: Get<Self::BlockNumber>;
+        /// The overarching event type.
+        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+        /// Minimum duration of participation for a provider
+        type MinProviderDuration: Get<Self::BlockNumber>;
+        #[pallet::constant]
+        type MinKeyLen: Get<u32>;
+        #[pallet::constant]
+        type ProviderRegistrationDeposit: Get<BalanceOf<Self>>;
+        /// Something that identifies a provider
+        type ProviderId: Copy + Default + MaybeSerializeDeserialize + Parameter + Ord;
+        /// Something that returns the ProviderId of an account, if the account does not have the
+        /// right to be Provider, the implementation must return none
+        type ProviderIdOf: Convert<Self::AccountId, Option<Self::ProviderId>>;
+        /// A safe source of randomness
+        type Randomness: Randomness<H256, Self::BlockNumber>;
+        /// Signing key of user payload
+        type UserSigner: Parameter + IdentifyAccount<AccountId = Self::AccountId>;
+        /// Signature of user payload
+        type UserSignature: Parameter + Verify<Signer = Self::UserSigner>;
+    }
+
+    // STORAGE //
+
+    #[pallet::storage]
+    #[pallet::getter(fn trie_delta)]
+    pub type TrieDelta<T: Config> = StorageValue<_, duniter_storage_primitives::Delta, ValueQuery>;
+
+    #[pallet::storage]
+    #[pallet::getter(fn trie_root)]
+    pub type TrieRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
+
+    #[pallet::storage]
+    pub type TrieRootUpdated<T: Config> = StorageValue<_, bool, ValueQuery>;
+
+    #[pallet::storage]
+    #[pallet::getter(fn pending_providers)]
+    pub type PendingProviders<T: Config> =
+        StorageMap<_, Twox64Concat, RequestId, (T::ProviderId, ProviderEndpoint), OptionQuery>;
+
+    #[pallet::storage]
+    #[pallet::getter(fn providers)]
+    pub type Providers<T: Config> = CountedStorageMap<
+        _,
+        Twox64Concat,
+        T::ProviderId,
+        ProviderInfo<T::BlockNumber>,
+        OptionQuery,
+    >;
+
+    #[pallet::storage]
+    #[pallet::getter(fn provider_by_pos)]
+    pub type ProviderByPos<T: Config> = StorageMap<_, Identity, H256, T::ProviderId, OptionQuery>;
+
+    #[pallet::storage]
+    #[pallet::getter(fn provider_by_reversed_pos)]
+    pub type ProviderByReversedPos<T: Config> =
+        StorageMap<_, Identity, H256, T::ProviderId, OptionQuery>;
+
+    #[pallet::storage]
+    #[pallet::getter(fn replication_factor_half)]
+    pub type ReplicationFactorHalf<T: Config> = StorageValue<_, u32, ValueQuery>;
+
+    // GENESIS STUFF //
+
+    #[pallet::genesis_config]
+    pub struct GenesisConfig<T: Config> {
+        pub providers: Vec<(T::ProviderId, ProviderEndpoint)>,
+        pub replication_factor: u32,
+    }
+
+    #[cfg(feature = "std")]
+    impl<T: Config> Default for GenesisConfig<T> {
+        fn default() -> Self {
+            Self {
+                providers: Vec::new(),
+                replication_factor: 0,
+            }
+        }
+    }
+
+    #[pallet::genesis_build]
+    impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
+        fn build(&self) {
+            assert!(self.replication_factor >= 2);
+            assert!(self.replication_factor % 2 == 0);
+            assert!(self.providers.len() >= self.replication_factor as usize);
+
+            ReplicationFactorHalf::<T>::put(self.replication_factor / 2);
+            for (provider_id, endpoint) in &self.providers {
+                Providers::<T>::insert(
+                    provider_id,
+                    ProviderInfo {
+                        endpoint: endpoint.clone(),
+                        pos: H256(blake2_256(&(provider_id, endpoint).encode())),
+                        unregisterable_on: T::MinProviderDuration::get(),
+                    },
+                );
+            }
+            TrieRoot::<T>::put(EMPTY_ROOT);
+        }
+    }
+
+    // EVENTS //
+
+    #[pallet::event]
+    #[pallet::generate_deposit(pub(super) fn deposit_event)]
+    pub enum Event<T: Config> {
+        /// An entry has been inserted or updated
+        /// [owner, key, deposit, value_or_hash]
+        EntryUpserted {
+            owner: T::AccountId,
+            key: Key,
+            value_hash: H256,
+        },
+        /// A user has updated his deposit for an entry
+        /// [owner, key, deposit, value_or_hash]
+        EntryDeleted { owner: T::AccountId, key: Key },
+        /// A user pruned an entry that had expired
+        /// [who, entry_owner, key]
+        EntryPruned {
+            who: T::AccountId,
+            owner: T::AccountId,
+            key: Key,
+        },
+        /// A new provider has requested registration
+        /// [id, endpoint]
+        NewProviderPending {
+            id: T::ProviderId,
+            endpoint: ProviderEndpoint,
+        },
+        /// A new provider has been registered
+        /// [provider_id, provider_position]
+        ProviderRegistered { id: T::ProviderId, pos: H256 },
+        /// A provider has been unregistered
+        /// [provider_id, provider_position]
+        ProviderUnregistered { id: T::ProviderId, pos: H256 },
+    }
+
+    // ERRORS //
+
+    #[pallet::error]
+    pub enum Error<T> {
+        /// Entry already inserted
+        AlreadyInserted,
+        /// Provider already registered
+        AlreadyRegistered,
+        /// Fail to compute new trie root
+        FailToComputeNewTrieRoot,
+        /// Key too short
+        KeyTooShort,
+        /// Invalid compact proof
+        InvalidCompactProof,
+        /// Invalid trie root
+        InvalidTrieRoot,
+        /// Invalid user signature
+        InvalidUserSig,
+        /// Not enough providers
+        NotEnoughProviders,
+        /// Entry not exist
+        NotExist,
+        /// The entry has not yet expired
+        NotExpiredYet,
+        /// This account has no right to be a provider
+        NoProviderId,
+        /// Provider not registered
+        NotRegistered,
+        /// Provider not unregisterable
+        NotUnregisterable,
+        /// Uneven replication factor
+        UnevenReplicationFactor,
+        /// Unsufficient deposit
+        UnsufficientDeposit,
+    }
+
+    // HOOKS //
+
+    #[pallet::hooks]
+    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+        fn on_initialize(_n: T::BlockNumber) -> Weight {
+            if TrieDelta::<T>::get().is_empty() {
+                TrieRootUpdated::<T>::put(true);
+            } else {
+                TrieRootUpdated::<T>::put(false);
+            }
+            // on_finalize is accounting here
+            T::DbWeight::get().reads_writes(2, 1)
+        }
+        fn on_finalize(_n: T::BlockNumber) {
+            if !TrieRootUpdated::<T>::get() {
+                panic!("invalid block: trie root was not updated!")
+            }
+        }
+    }
+
+    // CALLS //
+
+    #[pallet::call]
+    impl<T: Config> Pallet<T> {
+        #[pallet::weight(100_000_000)]
+        pub fn change_replication_factor(
+            origin: OriginFor<T>,
+            new_replication_factor: u32,
+        ) -> DispatchResultWithPostInfo {
+            // Verification phase //
+            T::ChangeReplicationFactorOrigin::ensure_origin(origin)?;
+            ensure!(
+                new_replication_factor % 2 == 0,
+                Error::<T>::UnevenReplicationFactor
+            );
+            ensure!(
+                Providers::<T>::count() >= new_replication_factor,
+                Error::<T>::NotEnoughProviders
+            );
+
+            ReplicationFactorHalf::<T>::put(new_replication_factor / 2);
+
+            Ok(().into())
+        }
+        #[pallet::weight(100_000_000)]
+        pub fn provider_register(
+            origin: OriginFor<T>,
+            endpoint: ProviderEndpoint,
+        ) -> DispatchResultWithPostInfo {
+            // Verification phase //
+            let who = ensure_signed(origin)?;
+
+            let provider_id =
+                T::ProviderIdOf::convert(who.clone()).ok_or(Error::<T>::NoProviderId)?;
+            ensure!(
+                !Providers::<T>::contains_key(&provider_id),
+                Error::<T>::AlreadyRegistered
+            );
+
+            // Apply phase //
+            let deposit = T::ProviderRegistrationDeposit::get();
+            <T as pallet::Config>::Currency::reserve_named(&RESERVE_ID, &who, deposit)?;
+            let salt = H256(blake2_256(&(RESERVE_ID, provider_id).encode()));
+            let request_id = pallet_provide_randomness::Pallet::<T>::do_request(
+                &who,
+                pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo,
+                salt,
+            )?;
+            PendingProviders::<T>::insert(request_id, (provider_id, endpoint.clone()));
+            Self::deposit_event(Event::NewProviderPending {
+                id: provider_id,
+                endpoint,
+            });
+
+            Ok(().into())
+        }
+        #[pallet::weight(100_000_000)]
+        pub fn provider_update_deposit(
+            origin: OriginFor<T>,
+            target: T::AccountId,
+        ) -> DispatchResultWithPostInfo {
+            // Verification phase //
+            let who = ensure_signed(origin)?;
+
+            let provider_id = T::ProviderIdOf::convert(who).ok_or(Error::<T>::NoProviderId)?;
+            ensure!(
+                Providers::<T>::contains_key(&provider_id),
+                Error::<T>::NotRegistered
+            );
+
+            let provider_id =
+                T::ProviderIdOf::convert(target.clone()).ok_or(Error::<T>::NoProviderId)?;
+            let provider_info =
+                Providers::<T>::get(&provider_id).ok_or(Error::<T>::NotRegistered)?;
+
+            // Apply phase //
+            let old_deposit =
+                <T as pallet::Config>::Currency::reserved_balance_named(&RESERVE_ID, &target);
+            let new_deposit = T::ProviderRegistrationDeposit::get();
+            #[allow(clippy::comparison_chain)]
+            if new_deposit > old_deposit {
+                if <T as pallet::Config>::Currency::reserve_named(
+                    &RESERVE_ID,
+                    &target,
+                    new_deposit - old_deposit,
+                )
+                .is_err()
+                {
+                    // Remove provider
+                    Self::do_remove_provider(
+                        provider_id,
+                        &target,
+                        Some(old_deposit),
+                        provider_info.pos,
+                    );
+                }
+            } else if new_deposit < old_deposit {
+                <T as pallet::Config>::Currency::unreserve_named(
+                    &RESERVE_ID,
+                    &target,
+                    old_deposit - new_deposit,
+                );
+            }
+
+            Ok(().into())
+        }
+        #[pallet::weight(100_000_000)]
+        pub fn provider_update_info(
+            origin: OriginFor<T>,
+            endpoint: ProviderEndpoint,
+        ) -> DispatchResultWithPostInfo {
+            // Verification phase //
+            let who = ensure_signed(origin)?;
+
+            let provider_id =
+                T::ProviderIdOf::convert(who.clone()).ok_or(Error::<T>::NoProviderId)?;
+            let mut provider_info =
+                Providers::<T>::get(&provider_id).ok_or(Error::<T>::NotRegistered)?;
+
+            // Apply phase //
+            let new_deposit = T::ProviderRegistrationDeposit::get();
+            let old_deposit =
+                <T as pallet::Config>::Currency::reserved_balance_named(&RESERVE_ID, &who);
+            provider_info.endpoint = endpoint;
+
+            if new_deposit > old_deposit
+                && <T as pallet::Config>::Currency::reserve_named(
+                    &RESERVE_ID,
+                    &who,
+                    new_deposit - old_deposit,
+                )
+                .is_err()
+            {
+                Self::do_remove_provider(provider_id, &who, Some(old_deposit), provider_info.pos);
+            } else {
+                // Write new provider info
+                Providers::<T>::insert(provider_id, provider_info);
+            }
+
+            Ok(().into())
+        }
+        #[pallet::weight(100_000_000)]
+        pub fn provider_unregister(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
+            // Verification phase //
+            let who = ensure_signed(origin)?;
+
+            let provider_id =
+                T::ProviderIdOf::convert(who.clone()).ok_or(Error::<T>::NoProviderId)?;
+            let provider_info =
+                Providers::<T>::get(&provider_id).ok_or(Error::<T>::NotRegistered)?;
+            let block_number = frame_system::Pallet::<T>::block_number();
+            ensure!(
+                block_number >= provider_info.unregisterable_on,
+                Error::<T>::NotUnregisterable
+            );
+            ensure!(
+                Providers::<T>::count() > ReplicationFactorHalf::<T>::get() * 2,
+                Error::<T>::NotEnoughProviders
+            );
+
+            // Apply phase //
+            Self::do_remove_provider(provider_id, &who, None, provider_info.pos);
+
+            Ok(().into())
+        }
+
+        #[pallet::weight(10_000_000_000)]
+        pub fn provider_declare_changes(
+            origin: OriginFor<T>,
+            users_proof: Vec<UserProofOf<T>>,
+        ) -> DispatchResult {
+            // Verification phase //
+            let who = ensure_signed(origin)?;
+
+            let provider_id = T::ProviderIdOf::convert(who).ok_or(Error::<T>::NoProviderId)?;
+            ensure!(
+                Providers::<T>::get(&provider_id).is_some(),
+                Error::<T>::NotRegistered
+            );
+
+            // TODO check each user proof
+            for user_proof in &users_proof {
+                ensure!(user_proof.verify_signature(), Error::<T>::InvalidUserSig,);
+                // The use of `ensure_reserved_named` allows to update the deposit amount in case a
+                // deposit has already been made.
+                <T as pallet::Config>::Currency::ensure_reserved_named(
+                    &RESERVE_ID,
+                    &user_proof.user_account(),
+                    T::UserDeposit::get(),
+                )?;
+            }
+
+            // Apply phase //
+            let block_number = frame_system::Pallet::<T>::block_number();
+            let delta: duniter_storage_primitives::Delta = users_proof
+                .iter()
+                .map(|user_proof| user_proof.to_delta(block_number + One::one()))
+                .collect();
+            TrieDelta::<T>::put(delta);
+
+            for user_proof in &users_proof {
+                Self::do_set_entry(
+                    user_proof.user_account(),
+                    user_proof.key().clone(),
+                    user_proof.maybe_value_hash(),
+                )
+            }
+
+            Ok(())
+        }
+
+        // TODO add call prune to apply entries expiration
+
+        // TODO add calls for moderators
+
+        #[pallet::weight((
+			1_000_000_000,
+			DispatchClass::Mandatory
+		))]
+        pub fn author_update_root(
+            origin: OriginFor<T>,
+            compact_proof: sp_trie::CompactProof,
+        ) -> DispatchResult {
+            // Verification phase //
+            ensure_none(origin)?;
+
+            let (storage_proof, root) = compact_proof
+                .to_storage_proof::<BlakeTwo256>(None)
+                .map_err(|_| Error::<T>::InvalidCompactProof)?;
+
+            ensure!(root == TrieRoot::<T>::get(), Error::<T>::InvalidTrieRoot);
+
+            let mut trie = storage_proof.into_memory_db::<BlakeTwo256>();
+            let new_root = sp_trie::delta_trie_root::<sp_trie::Layout<BlakeTwo256>, _, _, _, _, _>(
+                &mut trie,
+                root,
+                TrieDelta::<T>::get()
+                    .iter()
+                    .map(|(key, maybe_value)| (key.as_ref(), maybe_value.map(|value| value.0))),
+            )
+            .map_err(|_| Error::<T>::FailToComputeNewTrieRoot)?;
+
+            // Apply phase //
+            TrieRoot::<T>::put(new_root);
+            TrieRootUpdated::<T>::put(true);
+
+            Ok(())
+        }
+    }
+
+    #[pallet::inherent]
+    impl<T: Config> ProvideInherent for Pallet<T> {
+        type Call = Call<T>;
+        type Error = InherentError;
+        const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
+
+        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
+            let maybe_inherent_data = data
+                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
+                .expect("DuniterStorage inherent data not correctly encoded");
+
+            maybe_inherent_data.map(|inherent_data| Call::author_update_root {
+                compact_proof: inherent_data,
+            })
+        }
+
+        fn is_inherent(call: &Self::Call) -> bool {
+            matches!(call, Call::author_update_root { .. })
+        }
+    }
+
+    // PUBLIC FUNCTIONS //
+
+    impl<T: Config> Pallet<T> {}
+
+    // INTERNAL FUNCTIONS //
+
+    impl<T: Config> Pallet<T> {
+        fn do_set_entry(owner: T::AccountId, key: Key, maybe_value_hash: Option<H256>) {
+            if let Some(value_hash) = maybe_value_hash {
+                Self::deposit_event(Event::EntryUpserted {
+                    owner,
+                    key,
+                    value_hash,
+                });
+            } else {
+                Self::deposit_event(Event::EntryDeleted { owner, key });
+            }
+        }
+        fn do_remove_provider(
+            id: T::ProviderId,
+            owner_key: &T::AccountId,
+            maybe_deposit: Option<BalanceOf<T>>,
+            pos: H256,
+        ) {
+            <T as pallet::Config>::Currency::unreserve_named(
+                &RESERVE_ID,
+                owner_key,
+                maybe_deposit.unwrap_or_else(|| {
+                    <T as pallet::Config>::Currency::reserved_balance_named(&RESERVE_ID, owner_key)
+                }),
+            );
+            ProviderByPos::<T>::remove(pos);
+            ProviderByReversedPos::<T>::remove(reverse_hash(pos));
+            Providers::<T>::remove(id);
+            Self::deposit_event(Event::ProviderUnregistered { id, pos });
+        }
+
+        /*fn provider_neighbors(provider_pos: H256) -> Vec<T::ProviderId> {
+            let replication_factor_half = ReplicationFactorHalf::<T>::get();
+            let mut neighbors = Vec::with_capacity(replication_factor_half as usize * 2);
+            neighbors.extend(
+                ProviderByPos::<T>::iter_from(provider_pos.0.to_vec())
+                    .chain(ProviderByPos::<T>::iter())
+                    .take(replication_factor_half as usize)
+                    .map(|(_k, v)| v),
+            );
+            neighbors.extend(
+                ProviderByReversedPos::<T>::iter_from(provider_pos.0.to_vec())
+                    .chain(ProviderByReversedPos::<T>::iter())
+                    .take(replication_factor_half as usize)
+                    .map(|(_k, v)| v),
+            );
+            neighbors.sort_unstable();
+            neighbors
+        }
+        fn provider_range(provider_pos: H256) -> HashsRange {
+            let replication_factor_half = ReplicationFactorHalf::<T>::get();
+            (
+                ProviderByPos::<T>::iter_keys_from(provider_pos.0.to_vec())
+                    .chain(ProviderByPos::<T>::iter_keys())
+                    .take(replication_factor_half as usize)
+                    .last()
+                    .unwrap_or(H256([0; 32])),
+                ProviderByReversedPos::<T>::iter_keys_from(provider_pos.0.to_vec())
+                    .chain(ProviderByReversedPos::<T>::iter_keys())
+                    .take(replication_factor_half as usize)
+                    .last()
+                    .unwrap_or(H256([255; 32])),
+            )
+        }*/
+    }
+
+    impl<T: Config> pallet_provide_randomness::OnFilledRandomness for Pallet<T> {
+        fn on_filled_randomness(request_id: RequestId, randomness: H256) -> Weight {
+            if let Some((provider_id, provider_endpoint)) = PendingProviders::<T>::take(&request_id)
+            {
+                let provider_pos = randomness;
+                let block_number = frame_system::Pallet::<T>::block_number();
+                let unregisterable_on = block_number + T::MinProviderDuration::get();
+                Providers::<T>::insert(
+                    provider_id,
+                    ProviderInfo {
+                        endpoint: provider_endpoint,
+                        pos: provider_pos,
+                        unregisterable_on,
+                    },
+                );
+                ProviderByPos::<T>::insert(provider_pos, provider_id);
+                ProviderByReversedPos::<T>::insert(reverse_hash(provider_pos), provider_id);
+                Self::deposit_event(Event::ProviderRegistered {
+                    id: provider_id,
+                    pos: provider_pos,
+                });
+                500_000
+            } else {
+                50_000
+            }
+        }
+    }
+}
diff --git a/pallets/duniter-storage/src/mock.rs b/pallets/duniter-storage/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..502e8b864342beaaa7dc9fbaba6ce58f2dade0dd
--- /dev/null
+++ b/pallets/duniter-storage/src/mock.rs
@@ -0,0 +1,189 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use super::*;
+use crate::{self as pallet_duniter_storage};
+use frame_support::{
+    parameter_types,
+    traits::{Everything, OnFinalize, OnInitialize},
+};
+use frame_system as system;
+use sp_core::H256;
+use sp_io::hashing::blake2_256;
+use sp_runtime::{
+    testing::{Header, TestSignature, UintAuthorityId},
+    traits::{BlakeTwo256, IdentityLookup},
+    BuildStorage,
+};
+
+type AccountId = u64;
+type Balance = u64;
+type Block = frame_system::mocking::MockBlock<Test>;
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+
+// Configure a mock runtime to test the pallet.
+frame_support::construct_runtime!(
+    pub enum Test where
+        Block = Block,
+        NodeBlock = Block,
+        UncheckedExtrinsic = UncheckedExtrinsic,
+    {
+        System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+        Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+        DuniterStorage: pallet_duniter_storage::{Pallet, Call, Config<T>, Storage, Event<T>},
+        Randomness: pallet_provide_randomness::{Pallet, Call, Storage, Event},
+    }
+);
+
+// System
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const SS58Prefix: u8 = 42;
+}
+
+impl system::Config for Test {
+    type BaseCallFilter = Everything;
+    type BlockWeights = ();
+    type BlockLength = ();
+    type DbWeight = ();
+    type Origin = Origin;
+    type Call = Call;
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = AccountId;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = Event;
+    type BlockHashCount = BlockHashCount;
+    type Version = ();
+    type PalletInfo = PalletInfo;
+    type AccountData = pallet_balances::AccountData<Balance>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+    type SystemWeightInfo = ();
+    type SS58Prefix = SS58Prefix;
+    type OnSetCode = ();
+    type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+// Balances
+parameter_types! {
+    pub const ExistentialDeposit: Balance = 1;
+    pub const MaxLocks: u32 = 50;
+}
+
+impl pallet_balances::Config for Test {
+    type Balance = Balance;
+    type DustRemoval = ();
+    type Event = Event;
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = System;
+    type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
+    type MaxLocks = MaxLocks;
+    type MaxReserves = frame_support::pallet_prelude::ConstU32<1>;
+    type ReserveIdentifier = [u8; 8];
+}
+
+// DuniterStorage
+parameter_types! {
+    pub const UserDeposit: u64 = 100;
+    pub const ProviderRegistrationDeposit: u64 = 500;
+}
+
+pub struct TestRandomness;
+impl frame_support::traits::Randomness<H256, u64> for TestRandomness {
+    fn random(subject: &[u8]) -> (H256, u64) {
+        (H256(blake2_256(subject)), 0)
+    }
+}
+
+impl pallet_duniter_storage::Config for Test {
+    type ChangeReplicationFactorOrigin = frame_system::EnsureRoot<AccountId>;
+    type Currency = Balances;
+    type EntryExpiration = frame_support::traits::ConstU64<20>;
+    type Event = Event;
+    type MinProviderDuration = frame_support::traits::ConstU64<10>;
+    type MinKeyLen = frame_support::pallet_prelude::ConstU32<4>;
+    type ProviderRegistrationDeposit = ProviderRegistrationDeposit;
+    type ProviderId = AccountId;
+    type ProviderIdOf = sp_runtime::traits::ConvertInto;
+    type Randomness = TestRandomness;
+    type UserDeposit = UserDeposit;
+    type UserSigner = UintAuthorityId;
+    type UserSignature = TestSignature;
+}
+
+pub struct FakeRandomness;
+impl frame_support::traits::Randomness<Option<H256>, u64> for FakeRandomness {
+    fn random(_subject: &[u8]) -> (Option<H256>, u64) {
+        todo!()
+    }
+}
+impl frame_support::traits::Randomness<H256, u64> for FakeRandomness {
+    fn random(_subject: &[u8]) -> (H256, u64) {
+        todo!()
+    }
+}
+
+// Randomness
+parameter_types! {
+    pub const GetCurrentEpochIndex: u64 = 0;
+}
+impl pallet_provide_randomness::Config for Test {
+    type Currency = Balances;
+    type Event = Event;
+    type GetCurrentEpochIndex = GetCurrentEpochIndex;
+    type MaxRequests = frame_support::pallet_prelude::ConstU32<10>;
+    type RequestPrice = frame_support::traits::ConstU64<100>;
+    type OnFilledRandomness = DuniterStorage;
+    type OnUnbalanced = ();
+    type CurrentBlockRandomness = FakeRandomness;
+    type RandomnessFromOneEpochAgo = FakeRandomness;
+}
+
+// Build genesis storage according to the mock runtime.
+pub fn new_test_ext(replication_factor: u32) -> sp_io::TestExternalities {
+    GenesisConfig {
+        system: SystemConfig::default(),
+        balances: pallet_balances::GenesisConfig {
+            balances: (0..replication_factor + 3)
+                .map(|i| (i as u64, 800))
+                .collect(),
+        },
+        duniter_storage: pallet_duniter_storage::GenesisConfig {
+            providers: (0..replication_factor)
+                .map(|i| (i as u64, ProviderEndpoint::default()))
+                .collect(),
+            replication_factor,
+        },
+    }
+    .build_storage()
+    .unwrap()
+    .into()
+}
+
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        //Balances::on_finalize(System::block_number());
+        System::on_finalize(System::block_number());
+        System::reset_events();
+        System::set_block_number(System::block_number() + 1);
+        System::on_initialize(System::block_number());
+        //Balances::on_initialize(System::block_number());
+    }
+}
diff --git a/pallets/duniter-storage/src/tests.rs b/pallets/duniter-storage/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..97dc85df8687e04525528afb4bbbd6901b41d678
--- /dev/null
+++ b/pallets/duniter-storage/src/tests.rs
@@ -0,0 +1,350 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use crate::mock::Event as RuntimeEvent;
+use crate::mock::*;
+use crate::{Error, Event, UserProof, UserProofOf};
+use codec::Encode;
+use frame_support::{assert_err, assert_ok, BoundedVec};
+use sp_core::H256;
+use sp_io::hashing::blake2_256;
+use sp_runtime::testing::{TestSignature, UintAuthorityId};
+use sp_runtime::traits::BlakeTwo256;
+use sp_state_machine::Backend;
+use std::convert::TryFrom;
+
+fn create_user_proof(key: &[u8], value: Option<&[u8]>) -> UserProofOf<Test> {
+    let genesis_hash = H256::default();
+    let data_nonce = 0;
+    let key = BoundedVec::<u8, _>::try_from(key.to_vec()).unwrap();
+    let value = value.map(|value| (value.len() as u32, H256(blake2_256(value))));
+    let signer = UintAuthorityId(0);
+    let sig_payload = (genesis_hash, data_nonce, &key, value, &signer).encode();
+    UserProof {
+        genesis_hash,
+        data_nonce,
+        key,
+        value,
+        signer,
+        signature: TestSignature(0, sig_payload),
+    }
+}
+
+fn generate_trie_proof<S: sp_state_machine::TrieBackendStorage<BlakeTwo256>>(
+    trie_backend: &sp_state_machine::TrieBackend<S, BlakeTwo256>,
+    keys: Vec<&[u8]>,
+) -> sp_trie::CompactProof {
+    let proving_backend = sp_state_machine::ProvingBackend::new(trie_backend);
+    for key in keys {
+        let _ = proving_backend.storage(key);
+    }
+    let trie_proof = proving_backend
+        .extract_proof()
+        .into_compact_proof::<BlakeTwo256>(*trie_backend.root())
+        .expect("fail to generate compact proof");
+    println!("trie_proof_len={}", trie_proof.encoded_size());
+    trie_proof
+}
+
+fn to_leaf(value: &[u8]) -> Vec<u8> {
+    duniter_storage_primitives::compute_leaf_hash(
+        System::block_number() + 1,
+        H256(blake2_256(value)),
+    )
+    .0
+    .to_vec()
+}
+
+#[test]
+fn test_genesis_build() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        // Verify state
+        assert_eq!(DuniterStorage::replication_factor_half(), 1);
+        assert!(DuniterStorage::providers(0).is_some());
+        assert!(DuniterStorage::providers(1).is_some());
+        assert!(DuniterStorage::providers(2).is_none());
+    });
+}
+
+#[test]
+fn test_uneven_replication_factor() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::change_replication_factor(Origin::root(), 3),
+            Error::<Test>::UnevenReplicationFactor
+        );
+    });
+}
+
+#[test]
+fn test_not_enough_providers_to_increase_replication_factor() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::change_replication_factor(Origin::root(), 10),
+            Error::<Test>::NotEnoughProviders
+        );
+    });
+}
+
+#[test]
+fn test_already_registered() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_register(Origin::signed(1), Default::default()),
+            Error::<Test>::AlreadyRegistered
+        );
+    });
+}
+
+type MemoryDB = memory_db::MemoryDB<
+    BlakeTwo256,
+    memory_db::HashKey<BlakeTwo256>,
+    trie_db::DBValue,
+    memory_db::NoopTracker<trie_db::DBValue>,
+>;
+
+#[test]
+fn test_provider_declare_changes() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+
+        // Create initial trie
+        let (db, empty_root) = MemoryDB::default_with_root();
+        let mut trie_backend = sp_state_machine::TrieBackend::new(db, empty_root);
+        println!("empty_root={:?}", empty_root.0);
+
+        // try to insert 3 new entries, should success
+        assert_ok!(DuniterStorage::provider_declare_changes(
+            Origin::signed(1),
+            vec![
+                create_user_proof(b"k1", Some(b"v1")),
+                create_user_proof(b"k2", Some(b"v2")),
+                create_user_proof(b"k3", Some(b"v3")),
+            ],
+        ),);
+
+        // compute the new root onchain
+        assert_ok!(DuniterStorage::author_update_root(
+            Origin::none(),
+            generate_trie_proof(&trie_backend, vec![b"k1", b"k2", b"k3"])
+        ),);
+
+        // Apply changes in the trie
+        trie_backend.insert(vec![(
+            None,
+            vec![
+                (b"k1".to_vec(), Some(to_leaf(b"v1"))),
+                (b"k2".to_vec(), Some(to_leaf(b"v2"))),
+                (b"k3".to_vec(), Some(to_leaf(b"v3"))),
+            ],
+        )]);
+
+        // Check the now root onchain
+        let new_expected_root = trie_backend.root();
+        assert_eq!(DuniterStorage::trie_root(), *new_expected_root);
+
+        // try to (insert k4, modify k3,remove k2) with valid trie proof, should success
+        assert_ok!(DuniterStorage::provider_declare_changes(
+            Origin::signed(1),
+            vec![
+                create_user_proof(b"k4", Some(b"v4")),
+                create_user_proof(b"k3", Some(b"v3.2")),
+                create_user_proof(b"k2", None),
+            ],
+        ),);
+
+        // Try to compute the new root onchain with an empty proof, should fail
+        assert_err!(
+            DuniterStorage::author_update_root(
+                Origin::none(),
+                sp_trie::CompactProof {
+                    encoded_nodes: vec![vec![]]
+                } //empty proof
+            ),
+            Error::<Test>::InvalidCompactProof
+        );
+
+        // Compute the new root onchain
+        assert_ok!(DuniterStorage::author_update_root(
+            Origin::none(),
+            generate_trie_proof(&trie_backend, vec![b"k2", b"k3", b"k4"])
+        ),);
+
+        // Apply changes in the trie
+        trie_backend.insert(vec![(
+            None,
+            vec![
+                (b"k2".to_vec(), None),
+                (b"k3".to_vec(), Some(to_leaf(b"v3.2"))),
+                (b"k4".to_vec(), Some(to_leaf(b"v4"))),
+            ],
+        )]);
+
+        // Check the now root onchain
+        let new_expected_root = trie_backend.root();
+        assert_eq!(DuniterStorage::trie_root(), *new_expected_root);
+    });
+}
+
+#[test]
+fn test_provider_register_ok() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        //println!("{}:?", Balances::free_balance());
+        assert_ok!(DuniterStorage::provider_register(
+            Origin::signed(2),
+            Default::default()
+        ),);
+        assert_eq!(System::events().len(), 3);
+        // Deposit reserved to be à provider
+        assert_eq!(
+            System::events()[0].event,
+            RuntimeEvent::Balances(pallet_balances::Event::Reserved {
+                who: 2,
+                amount: 500
+            })
+        );
+        // Amount pays to generate a random position
+        assert_eq!(
+            System::events()[1].event,
+            RuntimeEvent::Balances(pallet_balances::Event::Withdraw {
+                who: 2,
+                amount: 100
+            })
+        );
+        assert_eq!(
+            System::events()[2].event,
+            RuntimeEvent::DuniterStorage(Event::NewProviderPending {
+                id: 2,
+                endpoint: Default::default(),
+            })
+        );
+    });
+}
+
+#[test]
+fn test_provider_update_deposit_origin_not_registered() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_update_deposit(Origin::signed(3), 1),
+            Error::<Test>::NotRegistered
+        );
+    });
+}
+
+#[test]
+fn test_provider_update_deposit_target_not_registered() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_update_deposit(Origin::signed(1), 3),
+            Error::<Test>::NotRegistered
+        );
+    });
+}
+
+#[test]
+fn test_provider_update_deposit_ok() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_ok!(DuniterStorage::provider_update_deposit(
+            Origin::signed(0),
+            1
+        ),);
+        assert_eq!(System::events().len(), 1);
+        assert_eq!(
+            System::events()[0].event,
+            RuntimeEvent::Balances(pallet_balances::Event::Reserved {
+                who: 1,
+                amount: 500
+            })
+        );
+        run_to_block(2);
+        assert_ok!(DuniterStorage::provider_update_deposit(
+            Origin::signed(0),
+            1
+        ),);
+        assert_eq!(System::events().len(), 0);
+    });
+}
+
+#[test]
+fn test_provider_update_info_not_registered() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_update_info(Origin::signed(3), Default::default()),
+            Error::<Test>::NotRegistered
+        );
+    });
+}
+
+#[test]
+fn test_provider_update_info_ok() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_ok!(DuniterStorage::provider_update_info(
+            Origin::signed(1),
+            Default::default()
+        ),);
+        assert_eq!(System::events().len(), 1);
+        assert_eq!(
+            System::events()[0].event,
+            RuntimeEvent::Balances(pallet_balances::Event::Reserved {
+                who: 1,
+                amount: 500
+            })
+        );
+    });
+}
+
+#[test]
+fn test_unregister_not_registered() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_unregister(Origin::signed(3)),
+            Error::<Test>::NotRegistered
+        );
+    });
+}
+
+#[test]
+fn test_not_unregisterable() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(1);
+        assert_err!(
+            DuniterStorage::provider_unregister(Origin::signed(1)),
+            Error::<Test>::NotUnregisterable
+        );
+    });
+}
+
+#[test]
+fn test_not_enough_providers() {
+    new_test_ext(2).execute_with(|| {
+        run_to_block(10);
+        assert_err!(
+            DuniterStorage::provider_unregister(Origin::signed(1)),
+            Error::<Test>::NotEnoughProviders
+        );
+    });
+}
diff --git a/pallets/duniter-storage/src/types.rs b/pallets/duniter-storage/src/types.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b6ac0d9c0e2ee1f164d4e9f9d6ac63d472ff98c3
--- /dev/null
+++ b/pallets/duniter-storage/src/types.rs
@@ -0,0 +1,132 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+//! Various basic types for use in the duniter-storage pallet.
+
+use codec::{Decode, Encode, MaxEncodedLen};
+use duniter_storage_primitives::Key;
+use frame_support::pallet_prelude::*;
+use scale_info::TypeInfo;
+use sp_core::H256;
+use sp_runtime::traits::{IdentifyAccount, Verify};
+
+#[derive(
+    Encode, Decode, Default, Clone, PartialEq, Eq, MaxEncodedLen, Ord, PartialOrd, RuntimeDebug,
+)]
+pub struct ProviderEndpoint(pub BoundedVec<u8, ConstU32<100>>);
+
+impl scale_info::TypeInfo for ProviderEndpoint {
+    type Identity = str;
+
+    fn type_info() -> scale_info::Type {
+        Self::Identity::type_info()
+    }
+}
+
+#[cfg(feature = "std")]
+impl serde::Serialize for ProviderEndpoint {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(
+            std::str::from_utf8(&self.0[..])
+                .map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?,
+        )
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'de> serde::Deserialize<'de> for ProviderEndpoint {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        use std::convert::TryFrom as _;
+        Ok(ProviderEndpoint(
+            BoundedVec::try_from(String::deserialize(deserializer)?.as_bytes().to_vec())
+                .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?,
+        ))
+    }
+}
+
+#[derive(
+    Clone, Encode, Decode, Default, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo,
+)]
+pub struct ProviderInfo<BlockNumber> {
+    pub endpoint: ProviderEndpoint,
+    pub pos: H256,
+    pub unregisterable_on: BlockNumber,
+}
+
+#[derive(Clone, Encode, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
+pub struct UserProof<Signer, Signature> {
+    pub(super) genesis_hash: H256,
+    pub(super) data_nonce: u32,
+    pub(super) key: Key,
+    pub(super) value: Option<(u32, H256)>,
+    pub(super) signer: Signer,
+    pub(super) signature: Signature,
+}
+
+impl<Signer, Signature> UserProof<Signer, Signature> {
+    pub fn to_delta<BlockNumber: Encode>(&self, block_number: BlockNumber) -> (Key, Option<H256>) {
+        (
+            self.key.clone(),
+            if let Some((_value_size, value_hash)) = self.value {
+                Some(duniter_storage_primitives::compute_leaf_hash(
+                    block_number,
+                    value_hash,
+                ))
+            } else {
+                None
+            },
+        )
+    }
+}
+
+impl<Signer, Signature> UserProof<Signer, Signature> {
+    pub fn key(&self) -> &Key {
+        &self.key
+    }
+    pub fn maybe_value_hash(&self) -> Option<H256> {
+        if let Some((_value_size, value_hash)) = self.value {
+            Some(value_hash)
+        } else {
+            None
+        }
+    }
+}
+
+impl<Signer: Clone + IdentifyAccount, Signature> UserProof<Signer, Signature> {
+    pub fn user_account(&self) -> <Signer as IdentifyAccount>::AccountId {
+        self.signer.clone().into_account()
+    }
+}
+
+impl<Signer, Signature> UserProof<Signer, Signature>
+where
+    Signer: Clone + Encode + IdentifyAccount,
+    Signature: Encode + Verify<Signer = Signer>,
+{
+    pub fn verify_signature(&self) -> bool {
+        self.using_encoded(|bytes| {
+            let sig_begin = bytes.len().saturating_sub(self.signature.encoded_size());
+            self.signature
+                .verify(&bytes[..sig_begin], &self.signer.clone().into_account())
+        })
+    }
+}
diff --git a/primitives/duniter-storage/Cargo.toml b/primitives/duniter-storage/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..cf256358c9cee530d4b643d32a8d3cdd35cd7fba
--- /dev/null
+++ b/primitives/duniter-storage/Cargo.toml
@@ -0,0 +1,78 @@
+[package]
+authors = ['librelois <c@elo.tf>']
+description = 'primitives for duniter-storage.'
+edition = '2018'
+homepage = 'https://substrate.dev'
+license = 'AGPL-3.0'
+name = 'duniter-storage-primitives'
+readme = 'README.md'
+repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
+version = '3.0.0'
+
+[features]
+default = ['std']
+std = [
+	'async-trait',
+    'codec/std',
+    'frame-support/std',
+	'sp-api/std',
+	'sp-core/std',
+	'sp-io/std',
+	'sp-inherents/std',
+	'sp-runtime/std',
+	'sp-std/std',
+	'sp-trie/std',
+]
+
+[dependencies]
+
+# crates.io
+async-trait = { version = "0.1.50", optional = true }
+codec = { package = 'parity-scale-codec', version = "2.3.1", default-features = false, features = ["derive"] }
+scale-info = { version = "1.0", default-features = false, features = ["derive"] }
+
+# substrate
+[dependencies.frame-support]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-api]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-core]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-io]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-inherents]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-runtime]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-std]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+[dependencies.sp-trie]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-02'
+
+### DOC ###
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
diff --git a/primitives/duniter-storage/src/lib.rs b/primitives/duniter-storage/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5c1ae526bdb7361e5012c847ef64cb22abe63ca6
--- /dev/null
+++ b/primitives/duniter-storage/src/lib.rs
@@ -0,0 +1,93 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+//! Duniter Storage primitives.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod util;
+
+pub use sp_inherents::Error;
+pub use util::*;
+
+use codec::{Decode, Encode};
+use frame_support::{traits::ConstU32, BoundedVec};
+use sp_core::H256;
+use sp_inherents::{InherentData, InherentIdentifier, IsFatalError};
+use sp_std::vec::Vec;
+
+pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"dunistor";
+
+pub const MAX_KEY_LEN: u32 = 255;
+
+pub type Key = BoundedVec<u8, ConstU32<MAX_KEY_LEN>>;
+
+pub type Delta = Vec<(BoundedVec<u8, ConstU32<MAX_KEY_LEN>>, Option<H256>)>;
+
+pub type InherentType = sp_trie::CompactProof;
+
+///
+#[derive(Encode, sp_runtime::RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Decode))]
+pub enum InherentError {}
+
+impl IsFatalError for InherentError {
+    fn is_fatal_error(&self) -> bool {
+        true
+    }
+}
+
+pub struct InherentDataProvider {
+    maybe_proof: Option<sp_trie::CompactProof>,
+}
+
+impl InherentDataProvider {
+    pub fn new(maybe_proof: Option<sp_trie::CompactProof>) -> Self {
+        Self { maybe_proof }
+    }
+}
+
+#[cfg(feature = "std")]
+#[async_trait::async_trait]
+impl sp_inherents::InherentDataProvider for InherentDataProvider {
+    fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
+        if let Some(proof) = &self.maybe_proof {
+            inherent_data.put_data(INHERENT_IDENTIFIER, proof)
+        } else {
+            Ok(())
+        }
+    }
+
+    async fn try_handle_error(
+        &self,
+        identifier: &InherentIdentifier,
+        mut error: &[u8],
+    ) -> Option<Result<(), Error>> {
+        if *identifier != INHERENT_IDENTIFIER {
+            return None;
+        }
+
+        let error = InherentError::decode(&mut error).ok()?;
+
+        Some(Err(Error::Application(Box::from(format!("{:?}", error)))))
+    }
+}
+
+pub type HashsRange = (H256, H256);
+
+pub fn compute_leaf_hash<BlockNumber: Encode>(block_number: BlockNumber, value_hash: H256) -> H256 {
+    (block_number, value_hash).using_encoded(|bytes| H256(sp_core::blake2_256(bytes)))
+}
diff --git a/primitives/duniter-storage/src/util.rs b/primitives/duniter-storage/src/util.rs
new file mode 100644
index 0000000000000000000000000000000000000000..558856553e34d7b05956aaf96eb0c6575313074e
--- /dev/null
+++ b/primitives/duniter-storage/src/util.rs
@@ -0,0 +1,34 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+//! Duniter Storage util functions.
+
+use crate::*;
+
+pub fn reverse_hash(mut hash: H256) -> H256 {
+    for i in 0..32 {
+        hash.0[i] = !hash.0[i];
+    }
+    hash
+}
+
+pub fn range_contains(range: HashsRange, key_hash: H256) -> bool {
+    if range.0 > range.1 {
+        key_hash >= range.0 || key_hash < range.1
+    } else {
+        key_hash >= range.0 && key_hash < range.1
+    }
+}