diff --git a/pallets/certification/src/lib.rs b/pallets/certification/src/lib.rs
index 255b6c48c8efd628e782530947304177bd938a30..b64aebf2233bbe18aea9dada4ffb488849f1b81f 100644
--- a/pallets/certification/src/lib.rs
+++ b/pallets/certification/src/lib.rs
@@ -221,7 +221,7 @@ pub mod pallet {
     #[pallet::storage]
     #[pallet::getter(fn certs_by_receiver)]
     pub type StorageCertsByReceiver<T: Config<I>, I: 'static = ()> =
-        StorageMap<_, Blake2_128Concat, T::IdtyIndex, Vec<T::IdtyIndex>, OptionQuery>;
+        StorageMap<_, Blake2_128Concat, T::IdtyIndex, Vec<T::IdtyIndex>, ValueQuery>;
 
     /// Certifications removable on
     #[pallet::storage]
diff --git a/pallets/duniter-wot/Cargo.toml b/pallets/duniter-wot/Cargo.toml
index e23031d7e3d9224681b513320308250685db5a4c..43a8cb644f4de425c13bd2f6526d3b37ca1eb475 100644
--- a/pallets/duniter-wot/Cargo.toml
+++ b/pallets/duniter-wot/Cargo.toml
@@ -22,6 +22,7 @@ std = [
     'pallet-membership/std',
     'serde',
     'sp-core/std',
+    'sp-io/std',
     'sp-membership/std',
     'sp-runtime/std',
 	'sp-std/std',
@@ -69,6 +70,11 @@ default-features = false
 git = 'https://github.com/librelois/substrate.git'
 branch = 'duniter-monthly-2022-01'
 
+[dependencies.sp-io]
+default-features = false
+git = 'https://github.com/librelois/substrate.git'
+branch = 'duniter-monthly-2022-01'
+
 [dependencies.sp-runtime]
 default-features = false
 git = 'https://github.com/librelois/substrate.git'
diff --git a/pallets/duniter-wot/src/lib.rs b/pallets/duniter-wot/src/lib.rs
index 42f25c2acd66a0b98c7a79ecf1ad60e48b6274c8..66750e68545460b545460fc3ad59b1c420f33f53 100644
--- a/pallets/duniter-wot/src/lib.rs
+++ b/pallets/duniter-wot/src/lib.rs
@@ -33,6 +33,7 @@ pub use pallet::*;
 pub use types::*;
 
 use frame_support::pallet_prelude::*;
+use frame_system::pallet_prelude::BlockNumberFor;
 use frame_system::RawOrigin;
 use pallet_certification::traits::SetNextIssuableOn;
 use pallet_identity::{IdtyEvent, IdtyStatus};
@@ -46,6 +47,7 @@ pub mod pallet {
     use super::*;
     use frame_support::dispatch::UnfilteredDispatchable;
     use frame_support::traits::StorageVersion;
+    use sp_std::vec::Vec;
 
     /// The current storage version.
     const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
@@ -70,6 +72,34 @@ pub mod pallet {
         type MinCertForCreateIdtyRight: Get<u32>;
     }
 
+    // STORAGE //
+
+    #[pallet::storage]
+    #[pallet::getter(fn wot_diffs)]
+    pub(super) type WotDiffs<T: Config<I>, I: 'static = ()> =
+        StorageValue<_, Vec<WotDiff>, ValueQuery>;
+
+    // HOOKS //
+
+    #[pallet::hooks]
+    impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
+        fn on_initialize(_: T::BlockNumber) -> Weight {
+            0
+        }
+        fn on_finalize(block_number: T::BlockNumber) {
+            const OFFCHAIN_WOT_DIFFS_KEY: &[u8] = b"ocw/wot-diffs/";
+            let diffs = WotDiffs::<T, I>::take();
+            let key = block_number.using_encoded(|encoded_block_number| {
+                OFFCHAIN_WOT_DIFFS_KEY
+                    .iter()
+                    .chain(encoded_block_number)
+                    .copied()
+                    .collect::<Vec<u8>>()
+            });
+            sp_io::offchain_index::set(&key, &diffs.encode());
+        }
+    }
+
     // INTERNAL FUNCTIONS //
 
     impl<T: Config<I>, I: 'static> Pallet<T, I> {
@@ -197,11 +227,19 @@ where
 {
     fn on_event(membership_event: &sp_membership::Event<IdtyIndex, MetaData>) -> Weight {
         match membership_event {
-            sp_membership::Event::<IdtyIndex, MetaData>::MembershipAcquired(_, _) => {}
-            sp_membership::Event::<IdtyIndex, MetaData>::MembershipExpired(idty_index) => {
+            sp_membership::Event::<IdtyIndex, MetaData>::MembershipAcquired(idty_index, _) => {
+                if !T::IsSubWot::get() {
+                    WotDiffs::<T, I>::append(WotDiff::AddNode(*idty_index));
+                }
+            }
+            sp_membership::Event::<IdtyIndex, MetaData>::MembershipExpired(idty_index)
+            | sp_membership::Event::<IdtyIndex, MetaData>::MembershipRevoked(idty_index) => {
                 Self::dispath_idty_call(pallet_identity::Call::disable_identity {
                     idty_index: *idty_index,
                 });
+                if !T::IsSubWot::get() {
+                    WotDiffs::<T, I>::append(WotDiff::DisableNode(*idty_index));
+                }
             }
             sp_membership::Event::<IdtyIndex, MetaData>::MembershipRenewed(_) => {}
             sp_membership::Event::<IdtyIndex, MetaData>::MembershipRequested(idty_index) => {
@@ -222,7 +260,6 @@ where
                     }
                 }
             }
-            sp_membership::Event::<IdtyIndex, MetaData>::MembershipRevoked(_) => {}
             sp_membership::Event::<IdtyIndex, MetaData>::PendingMembershipExpired(idty_index) => {
                 Self::dispath_idty_call(pallet_identity::Call::remove_identity {
                     idty_index: *idty_index,
@@ -257,7 +294,7 @@ impl<T: Config<I>, I: 'static> pallet_identity::traits::OnIdtyChange<T> for Pall
 
 impl<T: Config<I>, I: 'static> pallet_certification::traits::OnNewcert<IdtyIndex> for Pallet<T, I> {
     fn on_new_cert(
-        _issuer: IdtyIndex,
+        issuer: IdtyIndex,
         _issuer_issued_count: u32,
         receiver: IdtyIndex,
         receiver_received_count: u32,
@@ -266,6 +303,9 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::OnNewcert<IdtyIndex
             if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
                 Self::do_apply_first_issuable_on(receiver);
             }
+            if T::IsSubWot::get() {
+                WotDiffs::<T, I>::append(WotDiff::AddLink(issuer, receiver));
+            }
         } else if pallet_membership::Pallet::<T, I>::is_in_pending_memberships(receiver)
             && receiver_received_count >= T::MinCertForMembership::get()
         {
@@ -283,6 +323,9 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::OnNewcert<IdtyIndex
                 Self::dispath_idty_call(pallet_identity::Call::validate_identity {
                     idty_index: receiver,
                 });
+                for issuer in pallet_certification::Pallet::<T, I>::certs_by_receiver(receiver) {
+                    WotDiffs::<T, I>::append(WotDiff::AddLink(issuer, receiver));
+                }
             }
 
             if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
@@ -297,13 +340,15 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::OnRemovedCert<IdtyI
     for Pallet<T, I>
 {
     fn on_removed_cert(
-        _issuer: IdtyIndex,
+        issuer: IdtyIndex,
         _issuer_issued_count: u32,
         receiver: IdtyIndex,
         receiver_received_count: u32,
         _expiration: bool,
     ) -> Weight {
-        if receiver_received_count < T::MinCertForMembership::get() {
+        if receiver_received_count < T::MinCertForMembership::get()
+            && pallet_membership::Pallet::<T, I>::is_member(&receiver)
+        {
             // Revoke receiver membership and disable his identity
             if let Err(e) = pallet_membership::Pallet::<T, I>::revoke_membership(
                 RawOrigin::Root.into(),
@@ -312,12 +357,11 @@ impl<T: Config<I>, I: 'static> pallet_certification::traits::OnRemovedCert<IdtyI
                 sp_std::if_std! {
                     println!("{:?}", e)
                 }
-            } else {
-                Self::dispath_idty_call(pallet_identity::Call::disable_identity {
-                    idty_index: receiver,
-                });
             }
         }
+        if !T::IsSubWot::get() {
+            WotDiffs::<T, I>::append(WotDiff::DelLink(issuer, receiver));
+        }
         0
     }
 }
diff --git a/pallets/duniter-wot/src/tests.rs b/pallets/duniter-wot/src/tests.rs
index a688a06d7dc80edce5e06ea0ba285b2c7f94b78c..7c001e96abf2414cad86749a58dfe3bc7b063693 100644
--- a/pallets/duniter-wot/src/tests.rs
+++ b/pallets/duniter-wot/src/tests.rs
@@ -16,6 +16,7 @@
 
 use crate::mock::Identity;
 use crate::mock::*;
+use crate::WotDiff;
 use frame_support::assert_err;
 use frame_support::assert_ok;
 use frame_support::error::BadOrigin;
@@ -94,11 +95,12 @@ fn test_create_idty_ok() {
         );
         assert_eq!(Identity::identity(6).unwrap().status, IdtyStatus::Created);
         assert_eq!(Identity::identity(6).unwrap().removable_on, 4);
+        assert!(DuniterWot::wot_diffs().is_empty());
     });
 }
 
 #[test]
-fn test_ud_right_achievement_ok() {
+fn test_new_idty_validation() {
     new_test_ext(5).execute_with(|| {
         // Alice create Ferdie identity
         run_to_block(2);
@@ -117,14 +119,16 @@ fn test_ud_right_achievement_ok() {
             6
         ));
 
+        // Ferdie is not yet validated, so there should be no wot diff
+        assert!(DuniterWot::wot_diffs().is_empty());
+
         // Bob should be able to certify Ferdie
         run_to_block(4);
         assert_ok!(Cert::add_cert(Origin::signed(2), 2, 6));
 
         let events = System::events();
-        // 3 events should have occurred: NewCert, MembershipAcquired, IdtyValidated and IdtyAcquireRight
+        // 3 events should have occurred: NewCert, MembershipAcquired and IdtyValidated
         assert_eq!(events.len(), 3);
-        //println!("{:?}", events[2]);
         assert_eq!(
             events[0],
             EventRecord {
@@ -156,6 +160,17 @@ fn test_ud_right_achievement_ok() {
                 topics: vec![],
             }
         );
+
+        // Ferdie has just been validated, so the wot diff should contain her entry and all her
+        // certifications
+        assert_eq!(
+            DuniterWot::wot_diffs(),
+            vec![
+                WotDiff::AddNode(6),
+                WotDiff::AddLink(1, 6),
+                WotDiff::AddLink(2, 6)
+            ]
+        );
     });
 }
 
@@ -228,6 +243,7 @@ fn test_idty_membership_expire_them_requested() {
                 topics: vec![],
             }
         );
+        assert_eq!(DuniterWot::wot_diffs(), vec![WotDiff::DisableNode(3),]);
 
         // Charlie's identity should be disabled at block #5
         assert_eq!(Identity::identity(3).unwrap().status, IdtyStatus::Disabled);
@@ -268,5 +284,7 @@ fn test_idty_membership_expire_them_requested() {
                 topics: vec![],
             }
         );
+
+        assert_eq!(DuniterWot::wot_diffs(), vec![WotDiff::AddNode(3),]);
     });
 }
diff --git a/pallets/duniter-wot/src/types.rs b/pallets/duniter-wot/src/types.rs
index b6b0675ae5c2e52b83a2923f8ad8f138dc796119..fc72ef21a5b4821325bdac30de0a0a2c9011bb7e 100644
--- a/pallets/duniter-wot/src/types.rs
+++ b/pallets/duniter-wot/src/types.rs
@@ -91,3 +91,19 @@ impl<T: Config<I>, I: 'static> EnsureOrigin<(T::Origin, IdtyIndex, IdtyIndex)>
         }
     }
 }
+
+#[cfg_attr(feature = "std", derive(Debug))]
+#[derive(codec::Decode, codec::Encode, Eq, PartialEq, TypeInfo)]
+pub enum WotDiff {
+    AddNode(IdtyIndex),
+    AddPendingLink(IdtyIndex, IdtyIndex),
+    AddLink(IdtyIndex, IdtyIndex),
+    DelLink(IdtyIndex, IdtyIndex),
+    DisableNode(IdtyIndex),
+}
+
+impl Default for WotDiff {
+    fn default() -> Self {
+        unreachable!()
+    }
+}