diff --git a/docs/api/runtime-calls.md b/docs/api/runtime-calls.md
index 033203a40f859cd2c557e50e9e8ef5d2fbebadcc..3c96e8358dfda38ca1dc6434860180de1e675926 100644
--- a/docs/api/runtime-calls.md
+++ b/docs/api/runtime-calls.md
@@ -13,7 +13,7 @@ through on-chain governance mechanisms.
 
 ## User calls
 
-There are **79** user calls from **22** pallets.
+There are **77** user calls from **21** pallets.
 
 ### Account - 1
 
@@ -831,51 +831,6 @@ payload_sig: T::Signature
 
 Link an account to an identity
 
-### Membership - 42
-
-#### claim_membership - 1
-
-<details><summary><code>claim_membership()</code></summary>
-
-Taking 0.0213 % of a block.
-
-```rust
-```
-</details>
-
-
-claim membership
-it must fullfill the requirements (certs, distance)
-TODO #159 for main wot claim_membership is called automatically when distance is evaluated positively
-for smith wot, it means joining the authority members
-
-#### renew_membership - 2
-
-<details><summary><code>renew_membership()</code></summary>
-
-Taking 0.0164 % of a block.
-
-```rust
-```
-</details>
-
-
-extend the validity period of an active membership
-
-#### revoke_membership - 3
-
-<details><summary><code>revoke_membership()</code></summary>
-
-Taking 0.0586 % of a block.
-
-```rust
-```
-</details>
-
-
-revoke an active membership
-(only available for sub wot, automatic for main wot)
-
 ### Certification - 43
 
 #### add_cert - 0
@@ -932,20 +887,37 @@ remove all certifications received by an identity (only root)
 
 <details><summary><code>request_distance_evaluation()</code></summary>
 
-Taking 0.0187 % of a block.
+Taking 0.06 % of a block.
+
+```rust
+```
+</details>
+
+
+Request caller identity to be evaluated
+positive evaluation will result in claim/renew membership
+negative evaluation will result in slash for caller
+
+#### request_distance_evaluation_for - 4
+
+<details><summary><code>request_distance_evaluation_for(target)</code></summary>
+
+Taking 0.0805 % of a block.
 
 ```rust
+target: T::IdtyIndex
 ```
 </details>
 
 
-Request an identity to be evaluated
+Request target identity to be evaluated
+only possible for unvalidated identity
 
 #### update_evaluation - 1
 
 <details><summary><code>update_evaluation(computation_result)</code></summary>
 
-Taking 0.0195 % of a block.
+Taking 0.0914 % of a block.
 
 ```rust
 computation_result: ComputationResult
@@ -954,12 +926,13 @@ computation_result: ComputationResult
 
 
 (Inherent) Push an evaluation result to the pool
+this is called internally by validators (= inherent)
 
 #### force_update_evaluation - 2
 
 <details><summary><code>force_update_evaluation(evaluator, computation_result)</code></summary>
 
-Taking 0.0122 % of a block.
+Taking 0.0759 % of a block.
 
 ```rust
 evaluator: <T as frame_system::Config>::AccountId
@@ -968,28 +941,21 @@ computation_result: ComputationResult
 </details>
 
 
-Push an evaluation result to the pool
+Force push an evaluation result to the pool
 
-#### force_set_distance_status - 3
+#### force_valid_distance_status - 3
 
-<details><summary><code>force_set_distance_status(identity, status)</code></summary>
+<details><summary><code>force_valid_distance_status(identity)</code></summary>
 
-Taking 0.011 % of a block.
+Taking 0.074 % of a block.
 
 ```rust
 identity: <T as pallet_identity::Config>::IdtyIndex
-status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>
 ```
 </details>
 
 
-Set the distance evaluation status of an identity
-
-Removes the status if `status` is `None`.
-
-* `status.0` is the account for whom the price will be unreserved or slashed
-  when the evaluation completes.
-* `status.1` is the status of the evaluation.
+Force set the distance evaluation status of an identity
 
 ### AtomicSwap - 50
 
diff --git a/docs/api/runtime-errors.md b/docs/api/runtime-errors.md
index ebb10eb1a66022ba5bb640d7dc65cbf89a34d0b5..03b5c31fb96e2ecb7fcf408e945b1e79464d17a2 100644
--- a/docs/api/runtime-errors.md
+++ b/docs/api/runtime-errors.md
@@ -1,6 +1,6 @@
 # Runtime errors
 
-There are **178** errors from **35** pallets.
+There are **185** errors from **35** pallets.
 
 <ul>
 <li>System - 0
@@ -775,8 +775,8 @@ Distance evaluation has not been requested
 <li>
 <details>
 <summary>
-<code>IdtyNotAllowedToRequestMembership</code> - 5</summary>
-Identity is not allowed to request membership.
+<code>IdtyNotAllowedToClaimMembership</code> - 5</summary>
+Identity is not allowed to claim membership.
 </details>
 </li>
 <li>
@@ -849,6 +849,20 @@ Cannot issue a certification to a revoked identity
 Issuer or receiver not found.
 </details>
 </li>
+<li>
+<details>
+<summary>
+<code>NotEnoughCertsReceivedToRequestDistanceEvaluation</code> - 16</summary>
+Not enough certs received to request distance evaluation.
+</details>
+</li>
+<li>
+<details>
+<summary>
+<code>MembershipRenewalPeriodNotRespected</code> - 17</summary>
+Membership can only be renewed after an antispam delay
+</details>
+</li>
 </ul>
 </li>
 <li>Identity - 41
@@ -1011,6 +1025,13 @@ Membership already acquired.
 Membership not found.
 </details>
 </li>
+<li>
+<details>
+<summary>
+<code>AlreadyMember</code> - 3</summary>
+Already member, can not claim membership
+</details>
+</li>
 </ul>
 </li>
 <li>Certification - 43
@@ -1085,31 +1106,59 @@ No author for this block.
 <li>
 <details>
 <summary>
-<code>NoIdentity</code> - 4</summary>
+<code>CallerHasNoIdentity</code> - 4</summary>
 Caller has no identity.
 </details>
 </li>
 <li>
 <details>
 <summary>
-<code>QueueFull</code> - 5</summary>
+<code>CallerIdentityNotFound</code> - 5</summary>
+Caller identity not found.
+</details>
+</li>
+<li>
+<details>
+<summary>
+<code>CallerNotMember</code> - 6</summary>
+Caller not member.
+</details>
+</li>
+<li>
+<details>
+<summary>
+<code>TargetIdentityNotFound</code> - 7</summary>
+Target identity not found.
+</details>
+</li>
+<li>
+<details>
+<summary>
+<code>QueueFull</code> - 8</summary>
 Evaluation queue is full.
 </details>
 </li>
 <li>
 <details>
 <summary>
-<code>TooManyEvaluators</code> - 6</summary>
+<code>TooManyEvaluators</code> - 9</summary>
 Too many evaluators in the current evaluation pool.
 </details>
 </li>
 <li>
 <details>
 <summary>
-<code>WrongResultLength</code> - 7</summary>
+<code>WrongResultLength</code> - 10</summary>
 Evaluation result has a wrong length.
 </details>
 </li>
+<li>
+<details>
+<summary>
+<code>DistanceRequestOnlyAllowedForUnvalidated</code> - 11</summary>
+Targeted distance evaluation request is only possible for an unvalidated identity
+</details>
+</li>
 </ul>
 </li>
 <li>AtomicSwap - 50
diff --git a/docs/api/runtime-events.md b/docs/api/runtime-events.md
index 9aa80926367bb4a66c4f20f066063c6975b15769..c495670a16aafa3fe9b3201bb93b2ea3adc843b2 100644
--- a/docs/api/runtime-events.md
+++ b/docs/api/runtime-events.md
@@ -1,6 +1,6 @@
 # Runtime events
 
-There are **127** events from **35** pallets.
+There are **128** events from **35** pallets.
 
 <ul>
 <li>System - 0
@@ -1286,7 +1286,20 @@ expire_on: BlockNumberFor<T>
 <li>
 <details>
 <summary>
-<code>MembershipRemoved(member, reason)</code> - 1</summary>
+<code>MembershipRenewed(member, expire_on)</code> - 1</summary>
+A membership was renewed.
+
+```rust
+member: T::IdtyId
+expire_on: BlockNumberFor<T>
+```
+
+</details>
+</li>
+<li>
+<details>
+<summary>
+<code>MembershipRemoved(member, reason)</code> - 2</summary>
 A membership was removed.
 
 ```rust
@@ -1360,11 +1373,11 @@ who: T::AccountId
 <li>
 <details>
 <summary>
-<code>EvaluationUpdated(evaluator)</code> - 1</summary>
-A distance evaluation was updated.
+<code>EvaluatedValid(idty_index)</code> - 1</summary>
+Distance rule was found valid
 
 ```rust
-evaluator: T::AccountId
+idty_index: T::IdtyIndex
 ```
 
 </details>
@@ -1372,12 +1385,11 @@ evaluator: T::AccountId
 <li>
 <details>
 <summary>
-<code>EvaluationStatusForced(idty_index, status)</code> - 2</summary>
-A distance status was forced.
+<code>EvaluatedInvalid(idty_index)</code> - 2</summary>
+Distance rule was found invalid
 
 ```rust
 idty_index: T::IdtyIndex
-status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>
 ```
 
 </details>
diff --git a/end2end-tests/cucumber-features/identity_creation.feature b/end2end-tests/cucumber-features/identity_creation.feature
index f8bffeacc3017112a3b02485a693d5c56ec3f893..7de97f182dd43c222dd6e737b53693ccabd4ccc1 100644
--- a/end2end-tests/cucumber-features/identity_creation.feature
+++ b/end2end-tests/cucumber-features/identity_creation.feature
@@ -29,6 +29,4 @@ Feature: Identity creation
     Then dave should have distance result in 1 session
     When alice runs distance oracle
     When 30 blocks later
-    Then dave should have distance ok
-    When dave claims membership
     Then dave identity should be member
diff --git a/end2end-tests/cucumber-genesis/default.json b/end2end-tests/cucumber-genesis/default.json
index 245e91e312e06afca31bf3a5023a5e423c8d2e31..cfd66ba939a20409e5c577a657138dd211f69ec7 100644
--- a/end2end-tests/cucumber-genesis/default.json
+++ b/end2end-tests/cucumber-genesis/default.json
@@ -53,7 +53,7 @@
     "idty_confirm_period": 40,
     "idty_creation_period": 50,
     "membership_period": 1000,
-    "pending_membership_period": 500,
+    "membership_renewal_period": 500,
     "ud_creation_period": 60000,
     "ud_reeval_period": 600000,
     "smith_cert_max_by_issuer": 8,
@@ -64,9 +64,15 @@
     "wot_min_cert_for_membership": 2
   },
   "clique_smiths": [
-    { "name":  "Alice" },
-    { "name":  "Bob" },
-    { "name":  "Charlie" }
+    {
+      "name": "Alice"
+    },
+    {
+      "name": "Bob"
+    },
+    {
+      "name": "Charlie"
+    }
   ],
   "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
   "technical_committee": [
diff --git a/end2end-tests/cucumber-genesis/wot.json b/end2end-tests/cucumber-genesis/wot.json
index 6b1fad67065eb3323c083cc7e0e80ae8ce6098ef..813fc6fc13b2a9082f716734835b6b83f1aa27ad 100644
--- a/end2end-tests/cucumber-genesis/wot.json
+++ b/end2end-tests/cucumber-genesis/wot.json
@@ -62,7 +62,7 @@
     "idty_confirm_period": 40,
     "idty_creation_period": 50,
     "membership_period": 1000,
-    "pending_membership_period": 500,
+    "membership_renewal_period": 500,
     "ud_creation_period": 60000,
     "ud_reeval_period": 600000,
     "smith_cert_max_by_issuer": 8,
@@ -73,9 +73,15 @@
     "wot_min_cert_for_membership": 2
   },
   "clique_smiths": [
-    { "name":  "Alice" },
-    { "name":  "Bob" },
-    { "name":  "Charlie" }
+    {
+      "name": "Alice"
+    },
+    {
+      "name": "Bob"
+    },
+    {
+      "name": "Charlie"
+    }
   ],
   "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
   "technical_committee": [
diff --git a/end2end-tests/tests/common/membership.rs b/end2end-tests/tests/common/membership.rs
index d800f3387f756240b64645bda3c2859391b706db..c7b3fa56c291bbd82f08fe3519bd38b721ad5e33 100644
--- a/end2end-tests/tests/common/membership.rs
+++ b/end2end-tests/tests/common/membership.rs
@@ -14,27 +14,4 @@
 // 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 sp_keyring::AccountKeyring;
-use subxt::tx::PairSigner;
-
-pub async fn claim_membership(client: &Client, from: AccountKeyring) -> Result<()> {
-    let from = PairSigner::new(from.pair());
-
-    let _events = create_block_with_extrinsic(
-        client,
-        client
-            .tx()
-            .create_signed(
-                &gdev::tx().membership().claim_membership(),
-                &from,
-                BaseExtrinsicParamsBuilder::new(),
-            )
-            .await
-            .unwrap(),
-    )
-    .await
-    .unwrap();
-
-    Ok(())
-}
+// no more membership calls
diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs
index 7832a4a3d89048a033d61245d2a99cbaa508ea13..48d7c30be44ce6ef8a1c7bfba5cc4a487f4d83c3 100644
--- a/end2end-tests/tests/cucumber_tests.rs
+++ b/end2end-tests/tests/cucumber_tests.rs
@@ -333,14 +333,6 @@ async fn confirm_identity(world: &mut DuniterWorld, from: String, pseudo: String
     common::identity::confirm_identity(world.client(), from, pseudo).await
 }
 
-#[allow(clippy::needless_pass_by_ref_mut)]
-#[when(regex = r#"([a-zA-Z]+) claims membership"#)]
-async fn claim_membership(world: &mut DuniterWorld, from: String) -> Result<()> {
-    // input names to keyrings
-    let from = AccountKeyring::from_str(&from).expect("unknown from");
-    common::membership::claim_membership(world.client(), from).await
-}
-
 #[allow(clippy::needless_pass_by_ref_mut)]
 #[when(regex = r#"([a-zA-Z]+) requests distance evaluation"#)]
 async fn request_distance_evaluation(world: &mut DuniterWorld, who: String) -> Result<()> {
@@ -541,33 +533,6 @@ async fn should_have_distance_result_in_sessions(
     Err(anyhow::anyhow!("no evaluation in given pool").into())
 }
 
-#[allow(clippy::needless_pass_by_ref_mut)]
-#[then(regex = r"([a-zA-Z]+) should have distance ok")]
-async fn should_have_distance_ok(world: &mut DuniterWorld, who: String) -> Result<()> {
-    let who = AccountKeyring::from_str(&who).unwrap().to_account_id();
-
-    let idty_id = world
-        .read(&gdev::storage().identity().identity_index_of(&who.into()))
-        .await
-        .await?
-        .unwrap();
-
-    match world
-        .read(&gdev::storage().distance().identity_distance_status(idty_id))
-        .await
-        .await?
-    {
-        Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Valid)) => Ok(()),
-        Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Invalid)) => {
-            Err(anyhow::anyhow!("invalid distance status").into())
-        }
-        Some((_, gdev::runtime_types::pallet_distance::types::DistanceStatus::Pending)) => {
-            Err(anyhow::anyhow!("pending distance status").into())
-        }
-        None => Err(anyhow::anyhow!("no distance status").into()),
-    }
-}
-
 use gdev::runtime_types::pallet_identity::types::IdtyStatus;
 
 // status from string
diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs
index e927d483bc777d7b21c647ca7d07e3303ed464da..a66be70d21a9446bfd6833e250dab394db805260 100644
--- a/node/src/chain_spec/gdev.rs
+++ b/node/src/chain_spec/gdev.rs
@@ -87,7 +87,7 @@ fn get_parameters(parameters_from_file: &Option<GenesisParameters>) -> CommonPar
         identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(),
         identity_idty_creation_period: parameters_from_file.idty_creation_period,
         membership_membership_period: parameters_from_file.membership_period,
-        membership_pending_membership_period: parameters_from_file.pending_membership_period,
+        membership_membership_renewal_period: parameters_from_file.membership_renewal_period,
         cert_max_by_issuer: parameters_from_file.cert_max_by_issuer,
         cert_min_received_cert_to_be_able_to_issue_cert: parameters_from_file
             .cert_min_received_cert_to_issue_cert,
@@ -373,6 +373,7 @@ fn get_local_chain_parameters() -> Option<GenesisParameters> {
     let babe_epoch_duration = get_env("DUNITER_BABE_EPOCH_DURATION", 30) as u64;
     let cert_validity_period = get_env("DUNITER_CERT_VALIDITY_PERIOD", 1_000);
     let membership_period = get_env("DUNITER_MEMBERSHIP_PERIOD", 1_000);
+    let membership_renewal_period = get_env("DUNITER_MEMBERSHIP_RENEWAL_PERIOD", 1_000);
     let ud_creation_period = get_env("DUNITER_UD_CREATION_PERIOD", 60_000);
     let ud_reeval_period = get_env("DUNITER_UD_REEEVAL_PERIOD", 1_200_000);
     Some(GenesisParameters {
@@ -384,7 +385,7 @@ fn get_local_chain_parameters() -> Option<GenesisParameters> {
         idty_confirm_period: 40,
         idty_creation_period: 50,
         membership_period,
-        pending_membership_period: 500,
+        membership_renewal_period,
         ud_creation_period,
         ud_reeval_period,
         smith_cert_max_by_issuer: 8,
diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs
index 56481245eb257f0fadc84ce85b7969d83b8c6520..2565f3d4a273fdb7999e5be179372fdfc0ac18d4 100644
--- a/node/src/chain_spec/gen_genesis_data.rs
+++ b/node/src/chain_spec/gen_genesis_data.rs
@@ -553,6 +553,15 @@ where
                 G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32
             )
         }
+        if common_parameters.membership_membership_renewal_period / DAYS
+            != G1_DUNITER_V1_MSVALIDITY / DUNITER_V1_DAYS
+        {
+            warn!(
+                "parameter `membership_renewal_period` ({} days) is different from Äž1's ({} days)",
+                common_parameters.membership_membership_renewal_period as f32 / DAYS as f32,
+                G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32
+            )
+        }
         if common_parameters.cert_cert_period / DAYS != G1_DUNITER_V1_SIGPERIOD / DUNITER_V1_DAYS {
             warn!(
                 "parameter `cert_period` ({} days) is different from Äž1's ({} days)",
@@ -807,8 +816,8 @@ fn dump_genesis_info(info: GenesisInfo) {
         get_best_unit_and_diviser_for_blocks(p.identity_idty_creation_period);
     let (membership_membership_period, membership_membership_period_unit) =
         get_best_unit_and_diviser_for_blocks(p.membership_membership_period);
-    let (membership_pending_membership_period, membership_pending_membership_period_unit) =
-        get_best_unit_and_diviser_for_blocks(p.membership_pending_membership_period);
+    let (membership_membership_renewal_period, membership_membership_renewal_period_unit) =
+        get_best_unit_and_diviser_for_blocks(p.membership_membership_renewal_period);
     let (cert_cert_period, cert_cert_period_unit) =
         get_best_unit_and_diviser_for_blocks(p.cert_cert_period);
     let (cert_max_by_issuer, cert_max_by_issuer_unit) =
@@ -857,7 +866,7 @@ fn dump_genesis_info(info: GenesisInfo) {
         - identity.change_owner_key_period: {} {}
         - identity.idty_creation_period: {} {}
         - membership.membership_period: {} {}
-        - membership.pending_membership_period: {} {}
+        - membership.membership_renewal_period: {} {}
         - cert.cert_period: {} {}
         - cert.max_by_issuer: {} {}
         - cert.min_received_cert_to_be_able_to_issue_cert: {} {}
@@ -907,8 +916,8 @@ fn dump_genesis_info(info: GenesisInfo) {
         identity_idty_creation_period_unit,
         membership_membership_period,
         membership_membership_period_unit,
-        membership_pending_membership_period,
-        membership_pending_membership_period_unit,
+        membership_membership_renewal_period,
+        membership_membership_renewal_period_unit,
         cert_cert_period,
         cert_cert_period_unit,
         cert_max_by_issuer,
@@ -1980,7 +1989,7 @@ pub struct CommonParameters {
     pub identity_change_owner_key_period: u32,
     pub identity_idty_creation_period: u32,
     pub membership_membership_period: u32,
-    pub membership_pending_membership_period: u32,
+    pub membership_membership_renewal_period: u32,
     pub cert_cert_period: u32,
     pub cert_max_by_issuer: u32,
     pub cert_min_received_cert_to_be_able_to_issue_cert: u32,
diff --git a/node/src/chain_spec/gtest.rs b/node/src/chain_spec/gtest.rs
index d01ee0ccea092ce251aa1f631b40fd6ac56625a2..e7814f6d0fb4e1e72f37e4610601ec5441e700da 100644
--- a/node/src/chain_spec/gtest.rs
+++ b/node/src/chain_spec/gtest.rs
@@ -91,7 +91,7 @@ fn get_parameters(_: &Option<GenesisParameters>) -> CommonParameters {
         identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(),
         identity_idty_creation_period: parameters::IdtyCreationPeriod::get(),
         membership_membership_period: parameters::MembershipPeriod::get(),
-        membership_pending_membership_period: parameters::PendingMembershipPeriod::get(),
+        membership_membership_renewal_period: parameters::MembershipRenewalPeriod::get(),
         cert_max_by_issuer: parameters::MaxByIssuer::get(),
         cert_min_received_cert_to_be_able_to_issue_cert:
             parameters::MinReceivedCertToBeAbleToIssueCert::get(),
diff --git a/pallets/distance/src/benchmarking.rs b/pallets/distance/src/benchmarking.rs
index 33ba9419b1f9aeb7b4837948bf47e1ce166742a0..c762bde19753caa3c506c546772f495a98d620f9 100644
--- a/pallets/distance/src/benchmarking.rs
+++ b/pallets/distance/src/benchmarking.rs
@@ -50,16 +50,36 @@ benchmarks! {
         T: pallet_balances::Config, T::Balance: From<u64>,
         T::BlockNumber: From<u32>,
     }
+
+    // request distance evaluation
     request_distance_evaluation {
-            let idty = T::IdtyIndex::one();
-            let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
-            let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
-            let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
+        let idty = T::IdtyIndex::one();
+        let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
+        let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
+        let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
     }: _<T::RuntimeOrigin>(caller_origin.clone())
     verify {
-        assert!(IdentityDistanceStatus::<T>::get(idty) == Some((caller.clone(), DistanceStatus::Pending)), "Request not added");
+        assert!(PendingEvaluationRequest::<T>::get(idty) == Some(caller.clone()), "Request not added");
         assert_has_event::<T>(Event::<T>::EvaluationRequested { idty_index: idty, who: caller }.into());
     }
+
+    // request distance evaluation for
+    request_distance_evaluation_for {
+        let idty = T::IdtyIndex::one();
+        let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
+        let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
+        let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
+        let target = 2u32;
+        // set target status since targeted distance evaluation only allowed for unvalidated
+        pallet_identity::Identities::<T>::mutate(target,
+            |idty_val| idty_val.as_mut().unwrap().status = pallet_identity::IdtyStatus::Unvalidated);
+    }: _<T::RuntimeOrigin>(caller_origin.clone(), target)
+    verify {
+        assert!(PendingEvaluationRequest::<T>::get(target) == Some(caller.clone()), "Request not added");
+        assert_has_event::<T>(Event::<T>::EvaluationRequested { idty_index: target, who: caller }.into());
+    }
+
+    // update evaluation
     update_evaluation {
         let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryPlain(
         sp_consensus_babe::digests::SecondaryPlainPreDigest { authority_index: 0u32, slot: Default::default() });
@@ -71,27 +91,25 @@ benchmarks! {
         let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
         let i in 1 .. MAX_EVALUATIONS_PER_SESSION => populate_pool::<T>(i)?;
     }: _<T::RuntimeOrigin>(RawOrigin::None.into(), ComputationResult{distances: vec![Perbill::one(); i as usize]})
-    verify {
-        assert_has_event::<T>(Event::<T>::EvaluationUpdated { evaluator: caller }.into());
-    }
+
+    // force update evaluation
     force_update_evaluation {
             let idty = T::IdtyIndex::one();
             let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
             let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
             let i in 1 .. MAX_EVALUATIONS_PER_SESSION => populate_pool::<T>(i)?;
     }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), caller.clone(), ComputationResult{distances: vec![Perbill::one(); i as usize]})
+
+    // force valid distance status
+    force_valid_distance_status {
+        let idty = T::IdtyIndex::one();
+        let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
+    }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), idty)
     verify {
-        assert_has_event::<T>(Event::<T>::EvaluationUpdated { evaluator: caller }.into());
-    }
-    force_set_distance_status {
-            let idty = T::IdtyIndex::one();
-            let caller: T::AccountId  = pallet_identity::Identities::<T>::get(idty).unwrap().owner_key;
-            let status = Some((caller.clone(), DistanceStatus::Valid));
-    }: _<T::RuntimeOrigin>(RawOrigin::Root.into(), idty, status.clone())
-    verify {
-        assert!(IdentityDistanceStatus::<T>::get(idty) == Some((caller, DistanceStatus::Valid)), "Status not set");
-        assert_has_event::<T>(Event::<T>::EvaluationStatusForced { idty_index: idty, status }.into());
+        assert_has_event::<T>(Event::<T>::EvaluatedValid { idty_index: idty }.into());
     }
+
+    // on finalize
     on_finalize {
         DidUpdate::<T>::set(true);
     }: { Pallet::<T>::on_finalize(Default::default()); }
diff --git a/pallets/distance/src/lib.rs b/pallets/distance/src/lib.rs
index d7d131cd8f00e99d7bcef9b4470ae512a1caa07e..b7ee0e21420bc71d7c316f19d40c7d73f3293fab 100644
--- a/pallets/distance/src/lib.rs
+++ b/pallets/distance/src/lib.rs
@@ -17,7 +17,7 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 mod median;
-mod traits;
+pub mod traits;
 mod types;
 mod weights;
 
@@ -26,6 +26,8 @@ pub mod benchmarking;
 
 #[cfg(test)]
 mod mock;
+#[cfg(test)]
+mod tests;
 
 pub use pallet::*;
 pub use traits::*;
@@ -37,6 +39,7 @@ use pallet_authority_members::SessionIndex;
 use sp_distance::{InherentError, INHERENT_IDENTIFIER};
 use sp_inherents::{InherentData, InherentIdentifier};
 use sp_std::convert::TryInto;
+use sp_std::prelude::*;
 
 type IdtyIndex = u32;
 
@@ -66,6 +69,7 @@ pub mod pallet {
         + pallet_identity::Config<IdtyIndex = IdtyIndex>
         + pallet_session::Config
     {
+        /// Currency type used in this pallet (used for reserve/slash)
         type Currency: ReservableCurrency<Self::AccountId>;
         /// Amount reserved during evaluation
         #[pallet::constant]
@@ -79,12 +83,14 @@ pub mod pallet {
         /// Minimum ratio of accessible referees
         #[pallet::constant]
         type MinAccessibleReferees: Get<Perbill>;
-        /// Number of session to keep a positive evaluation result
-        type ResultExpiration: Get<u32>;
         /// The overarching event type.
         type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
         /// Type representing the weight of this pallet
         type WeightInfo: WeightInfo;
+        /// Handler for successful distance evaluation
+        type OnValidDistanceStatus: OnValidDistanceStatus<Self>;
+        /// Trait to check that distance evaluation request is allowed
+        type CheckRequestDistanceEvaluation: CheckRequestDistanceEvaluation<Self>;
     }
 
     // STORAGE //
@@ -128,35 +134,20 @@ pub mod pallet {
     pub type EvaluationBlock<T: Config> =
         StorageValue<_, <T as frame_system::Config>::Hash, ValueQuery>;
 
-    /// Distance evaluation status by identity
+    /// Pending evaluation requesters
     ///
-    /// * `.0` is the account who requested an evaluation and reserved the price,
+    /// account who requested an evaluation and reserved the price,
     ///   for whom the price will be unreserved or slashed when the evaluation completes.
-    /// * `.1` is the status of the evaluation.
     #[pallet::storage]
-    #[pallet::getter(fn identity_distance_status)]
-    pub type IdentityDistanceStatus<T: Config> = StorageMap<
+    #[pallet::getter(fn pending_evaluation_request)]
+    pub type PendingEvaluationRequest<T: Config> = StorageMap<
         _,
         Twox64Concat,
         <T as pallet_identity::Config>::IdtyIndex,
-        (<T as frame_system::Config>::AccountId, DistanceStatus),
+        <T as frame_system::Config>::AccountId,
         OptionQuery,
     >;
 
-    /// Identities by distance status expiration session index
-    #[pallet::storage]
-    #[pallet::getter(fn distance_status_expire_on)]
-    pub type DistanceStatusExpireOn<T: Config> = StorageMap<
-        _,
-        Twox64Concat,
-        u32,
-        BoundedVec<
-            <T as pallet_identity::Config>::IdtyIndex,
-            ConstU32<MAX_EVALUATIONS_PER_SESSION>,
-        >,
-        ValueQuery,
-    >;
-
     /// Did evaluation get updated in this block?
     #[pallet::storage]
     pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>;
@@ -176,13 +167,10 @@ pub mod pallet {
             idty_index: T::IdtyIndex,
             who: T::AccountId,
         },
-        /// A distance evaluation was updated.
-        EvaluationUpdated { evaluator: T::AccountId },
-        /// A distance status was forced.
-        EvaluationStatusForced {
-            idty_index: T::IdtyIndex,
-            status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
-        },
+        /// Distance rule was found valid
+        EvaluatedValid { idty_index: T::IdtyIndex },
+        /// Distance rule was found invalid
+        EvaluatedInvalid { idty_index: T::IdtyIndex },
     }
 
     // ERRORS //
@@ -198,13 +186,21 @@ pub mod pallet {
         /// No author for this block.
         NoAuthor,
         /// Caller has no identity.
-        NoIdentity,
+        CallerHasNoIdentity,
+        /// Caller identity not found.
+        CallerIdentityNotFound,
+        /// Caller not member.
+        CallerNotMember,
+        /// Target identity not found.
+        TargetIdentityNotFound,
         /// Evaluation queue is full.
         QueueFull,
         /// Too many evaluators in the current evaluation pool.
         TooManyEvaluators,
         /// Evaluation result has a wrong length.
         WrongResultLength,
+        /// Targeted distance evaluation request is only possible for an unvalidated identity
+        DistanceRequestOnlyAllowedForUnvalidated,
     }
 
     #[pallet::hooks]
@@ -228,32 +224,45 @@ pub mod pallet {
 
     #[pallet::call]
     impl<T: Config> Pallet<T> {
-        /// Request an identity to be evaluated
+        /// Request caller identity to be evaluated
+        /// positive evaluation will result in claim/renew membership
+        /// negative evaluation will result in slash for caller
         #[pallet::call_index(0)]
         #[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation())]
         pub fn request_distance_evaluation(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
             let who = ensure_signed(origin)?;
 
-            let idty =
-                pallet_identity::IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?;
+            let idty = Self::check_request_distance_evaluation_self(&who)?;
 
-            ensure!(
-                IdentityDistanceStatus::<T>::get(idty)
-                    != Some((who.clone(), DistanceStatus::Pending)),
-                Error::<T>::AlreadyInEvaluation
-            );
+            Pallet::<T>::do_request_distance_evaluation(&who, idty)?;
+            Ok(().into())
+        }
 
-            Pallet::<T>::do_request_distance_evaluation(who, idty)?;
+        /// Request target identity to be evaluated
+        /// only possible for unvalidated identity
+        #[pallet::call_index(4)]
+        #[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation_for())]
+        pub fn request_distance_evaluation_for(
+            origin: OriginFor<T>,
+            target: T::IdtyIndex,
+        ) -> DispatchResultWithPostInfo {
+            let who = ensure_signed(origin)?;
+
+            Self::check_request_distance_evaluation_for(&who, target)?;
+
+            Pallet::<T>::do_request_distance_evaluation(&who, target)?;
             Ok(().into())
         }
 
         /// (Inherent) Push an evaluation result to the pool
+        /// this is called internally by validators (= inherent)
         #[pallet::call_index(1)]
         #[pallet::weight(<T as pallet::Config>::WeightInfo::update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
         pub fn update_evaluation(
             origin: OriginFor<T>,
             computation_result: ComputationResult,
         ) -> DispatchResult {
+            // no origin = inherent
             ensure_none(origin)?;
             ensure!(
                 !DidUpdate::<T>::exists(),
@@ -267,7 +276,8 @@ pub mod pallet {
             Ok(())
         }
 
-        /// Push an evaluation result to the pool
+        /// Force push an evaluation result to the pool
+        // (it is convenient to have this call in end2end tests)
         #[pallet::call_index(2)]
         #[pallet::weight(<T as pallet::Config>::WeightInfo::force_update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
         pub fn force_update_evaluation(
@@ -280,60 +290,21 @@ pub mod pallet {
             Pallet::<T>::do_update_evaluation(evaluator, computation_result)
         }
 
-        /// Set the distance evaluation status of an identity
-        ///
-        /// Removes the status if `status` is `None`.
-        ///
-        /// * `status.0` is the account for whom the price will be unreserved or slashed
-        ///   when the evaluation completes.
-        /// * `status.1` is the status of the evaluation.
+        /// Force set the distance evaluation status of an identity
+        // (it is convenient to have this in test network)
         #[pallet::call_index(3)]
-        #[pallet::weight(<T as pallet::Config>::WeightInfo::force_set_distance_status())]
-        pub fn force_set_distance_status(
+        #[pallet::weight(<T as pallet::Config>::WeightInfo::force_valid_distance_status())]
+        pub fn force_valid_distance_status(
             origin: OriginFor<T>,
             identity: <T as pallet_identity::Config>::IdtyIndex,
-            status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
         ) -> DispatchResult {
             ensure_root(origin)?;
 
-            IdentityDistanceStatus::<T>::set(identity, status.clone());
-            DistanceStatusExpireOn::<T>::mutate(
-                pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(),
-                move |identities| {
-                    identities
-                        .try_push(identity)
-                        .map_err(|_| Error::<T>::TooManyEvaluationsInBlock)
-                },
-            )?;
-            Self::deposit_event(Event::EvaluationStatusForced {
-                idty_index: identity,
-                status,
-            });
+            Self::do_valid_distance_status(identity);
             Ok(())
         }
     }
 
-    // BENCHMARK FUNCTIONS //
-
-    impl<T: Config> Pallet<T> {
-        /// Force the distance status using IdtyIndex and AccountId
-        /// only to prepare identity for benchmarking.
-        pub fn set_distance_status(
-            identity: <T as pallet_identity::Config>::IdtyIndex,
-            status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
-        ) -> DispatchResult {
-            IdentityDistanceStatus::<T>::set(identity, status);
-            DistanceStatusExpireOn::<T>::mutate(
-                pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(),
-                move |identities| {
-                    identities
-                        .try_push(identity)
-                        .map_err(|_| Error::<T>::TooManyEvaluationsInBlock.into())
-                },
-            )
-        }
-    }
-
     // INTERNAL FUNCTIONS //
 
     impl<T: Config> Pallet<T> {
@@ -398,55 +369,110 @@ pub mod pallet {
             }
         }
 
+        /// check that request distance evaluation is allowed
+        fn check_request_distance_evaluation_self(
+            who: &T::AccountId,
+        ) -> Result<<T as pallet_identity::Config>::IdtyIndex, DispatchError> {
+            // caller has an identity
+            let idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
+                .ok_or(Error::<T>::CallerHasNoIdentity)?;
+            Self::check_request_distance_evaluation_common(idty_index)?;
+            Ok(idty_index)
+        }
+
+        /// check that targeted request distance evaluation is allowed
+        fn check_request_distance_evaluation_for(
+            who: &T::AccountId,
+            target: <T as pallet_identity::Config>::IdtyIndex,
+        ) -> Result<(), DispatchError> {
+            // caller has an identity
+            let caller_idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
+                .ok_or(Error::<T>::CallerHasNoIdentity)?;
+            let caller_idty = pallet_identity::Identities::<T>::get(caller_idty_index)
+                .ok_or(Error::<T>::CallerIdentityNotFound)?;
+            // caller is member
+            ensure!(
+                caller_idty.status == pallet_identity::IdtyStatus::Member,
+                Error::<T>::CallerNotMember
+            );
+            // target has an identity
+            let target_idty = pallet_identity::Identities::<T>::get(target)
+                .ok_or(Error::<T>::TargetIdentityNotFound)?;
+            // target is unvalidated
+            ensure!(
+                target_idty.status == pallet_identity::IdtyStatus::Unvalidated,
+                Error::<T>::DistanceRequestOnlyAllowedForUnvalidated
+            );
+            Self::check_request_distance_evaluation_common(target)?;
+            Ok(())
+        }
+
+        // common checks between check_request_distance_evaluation _self and _for
+        fn check_request_distance_evaluation_common(
+            target: <T as pallet_identity::Config>::IdtyIndex,
+        ) -> Result<(), DispatchError> {
+            // no pending evaluation request
+            ensure!(
+                PendingEvaluationRequest::<T>::get(target).is_none(),
+                Error::<T>::AlreadyInEvaluation
+            );
+            // external validation
+            // target has received enough certifications
+            T::CheckRequestDistanceEvaluation::check_request_distance_evaluation(target)
+        }
+
+        /// request distance evaluation in current pool
         fn do_request_distance_evaluation(
-            who: T::AccountId,
+            who: &T::AccountId,
             idty_index: <T as pallet_identity::Config>::IdtyIndex,
         ) -> Result<(), DispatchError> {
             Pallet::<T>::mutate_current_pool(
-                pallet_session::CurrentIndex::<T>::get(),
+                pallet_session::CurrentIndex::<T>::get(), // TODO look
                 |current_pool| {
+                    // TODO not needed if called in extrinsics only
+                    // since extrinsics are transactional by default
                     ensure!(
                         current_pool.evaluations.len() < (MAX_EVALUATIONS_PER_SESSION as usize),
                         Error::<T>::QueueFull
                     );
 
-                    T::Currency::reserve(&who, <T as Config>::EvaluationPrice::get())?;
+                    T::Currency::reserve(who, <T as Config>::EvaluationPrice::get())?;
 
                     current_pool
                         .evaluations
                         .try_push((idty_index, median::MedianAcc::new()))
                         .map_err(|_| Error::<T>::QueueFull)?;
 
-                    IdentityDistanceStatus::<T>::insert(
-                        idty_index,
-                        (&who, DistanceStatus::Pending),
-                    );
+                    PendingEvaluationRequest::<T>::insert(idty_index, who);
 
-                    DistanceStatusExpireOn::<T>::mutate(
-                        pallet_session::CurrentIndex::<T>::get() + T::ResultExpiration::get(),
-                        move |identities| identities.try_push(idty_index).ok(),
-                    );
-                    Self::deposit_event(Event::EvaluationRequested { idty_index, who });
+                    Self::deposit_event(Event::EvaluationRequested {
+                        idty_index,
+                        who: who.clone(),
+                    });
                     Ok(())
                 },
             )
         }
 
+        /// update distance evaluation in next pool
         fn do_update_evaluation(
             evaluator: <T as frame_system::Config>::AccountId,
             computation_result: ComputationResult,
         ) -> DispatchResult {
             Pallet::<T>::mutate_next_pool(pallet_session::CurrentIndex::<T>::get(), |result_pool| {
+                // evaluation must be provided for all identities (no more, no less)
                 ensure!(
                     computation_result.distances.len() == result_pool.evaluations.len(),
                     Error::<T>::WrongResultLength
                 );
 
+                // insert the evaluator if not already there
                 if result_pool
                     .evaluators
                     .try_insert(evaluator.clone())
                     .map_err(|_| Error::<T>::TooManyEvaluators)?
                 {
+                    // update the median accumulator with the new result
                     for (distance_value, (_identity, median_acc)) in computation_result
                         .distances
                         .into_iter()
@@ -454,23 +480,28 @@ pub mod pallet {
                     {
                         median_acc.push(distance_value);
                     }
-
-                    Self::deposit_event(Event::EvaluationUpdated { evaluator });
                     Ok(())
                 } else {
+                    // one author can only submit one evaluation
                     Err(Error::<T>::TooManyEvaluationsByAuthor.into())
                 }
             })
         }
+
+        /// Set the distance status using IdtyIndex and AccountId
+        pub fn do_valid_distance_status(idty: <T as pallet_identity::Config>::IdtyIndex) {
+            // callback
+            T::OnValidDistanceStatus::on_valid_distance_status(idty);
+            // deposit event
+            Self::deposit_event(Event::EvaluatedValid { idty_index: idty });
+        }
     }
 
     impl<T: Config> pallet_authority_members::OnNewSession for Pallet<T> {
         fn on_new_session(index: SessionIndex) {
+            // set evaluation block
             EvaluationBlock::<T>::set(frame_system::Pallet::<T>::parent_hash());
 
-            // Make results expire
-            DistanceStatusExpireOn::<T>::remove(index);
-
             // Apply the results from the current pool (which was previous session's result pool)
             // We take the results so the pool is left empty for the new session.
             #[allow(clippy::type_complexity)]
@@ -479,35 +510,48 @@ pub mod pallet {
                 <T as pallet_identity::Config>::IdtyIndex,
             > = Pallet::<T>::take_current_pool(index);
             for (idty, median_acc) in current_pool.evaluations.into_iter() {
+                // distance result
+                let mut distance_result: Option<bool> = None;
+
+                // get result of the computation
                 if let Some(median_result) = median_acc.get_median() {
                     let median = match median_result {
                         MedianResult::One(m) => m,
                         MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1)
                     };
-                    IdentityDistanceStatus::<T>::mutate(idty, |entry| {
-                        if let Some((account_id, status)) = entry.as_mut() {
-                            if median >= T::MinAccessibleReferees::get() {
-                                T::Currency::unreserve(
-                                    account_id,
-                                    <T as Config>::EvaluationPrice::get(),
-                                );
-                                *status = DistanceStatus::Valid;
-                            } else {
-                                T::Currency::slash_reserved(
-                                    account_id,
-                                    <T as Config>::EvaluationPrice::get(),
-                                );
-                                *status = DistanceStatus::Invalid;
-                            }
+                    // update distance result
+                    distance_result = Some(median >= T::MinAccessibleReferees::get());
+                }
+
+                // take requester and perform unreserve or slash
+                if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) {
+                    match distance_result {
+                        None => {
+                            // no result, unreserve
+                            T::Currency::unreserve(
+                                &requester,
+                                <T as Config>::EvaluationPrice::get(),
+                            );
                         }
-                    });
-                } else if let Some((account_id, _status)) = IdentityDistanceStatus::<T>::take(idty)
-                {
-                    <T as Config>::Currency::unreserve(
-                        &account_id,
-                        <T as Config>::EvaluationPrice::get(),
-                    );
+                        Some(true) => {
+                            // positive result, unreserve and apply
+                            T::Currency::unreserve(
+                                &requester,
+                                <T as Config>::EvaluationPrice::get(),
+                            );
+                            Self::do_valid_distance_status(idty);
+                        }
+                        Some(false) => {
+                            // negative result, slash and deposit event
+                            T::Currency::slash_reserved(
+                                &requester,
+                                <T as Config>::EvaluationPrice::get(),
+                            );
+                            Self::deposit_event(Event::EvaluatedInvalid { idty_index: idty });
+                        }
+                    }
                 }
+                // if evaluation happened without request, it's ok to do nothing
             }
         }
     }
diff --git a/pallets/distance/src/mock.rs b/pallets/distance/src/mock.rs
index ef477a8c8fb954bf324b2b0936b092492ff05831..6861b3c43ad8a817ba9b04f521203a21d9f2bccd 100644
--- a/pallets/distance/src/mock.rs
+++ b/pallets/distance/src/mock.rs
@@ -19,7 +19,7 @@ use crate::{self as pallet_distance};
 use core::marker::PhantomData;
 use frame_support::{
     parameter_types,
-    traits::{Everything, GenesisBuild},
+    traits::{Everything, GenesisBuild, OnFinalize, OnInitialize},
 };
 use frame_system as system;
 use pallet_balances::AccountData;
@@ -256,9 +256,10 @@ impl pallet_distance::Config for Test {
     type EvaluationPrice = frame_support::traits::ConstU64<1000>;
     type MaxRefereeDistance = frame_support::traits::ConstU32<5>;
     type MinAccessibleReferees = MinAccessibleReferees;
-    type ResultExpiration = frame_support::traits::ConstU32<720>;
     type RuntimeEvent = RuntimeEvent;
     type WeightInfo = ();
+    type OnValidDistanceStatus = ();
+    type CheckRequestDistanceEvaluation = ();
 }
 
 // Build genesis storage according to the mock runtime.
@@ -290,3 +291,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
 
     sp_io::TestExternalities::new(t)
 }
+
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        Session::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());
+        Session::on_initialize(System::block_number());
+    }
+}
diff --git a/pallets/distance/src/tests.rs b/pallets/distance/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b1be3c04a488d49496992b8166235d9229554261
--- /dev/null
+++ b/pallets/distance/src/tests.rs
@@ -0,0 +1,41 @@
+// Copyright 2023 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S 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.
+//
+// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+use crate::mock::*;
+use crate::*;
+use frame_support::assert_ok;
+use frame_support::traits::Currency;
+
+#[test]
+fn test_request_distance_evaluation() {
+    new_test_ext().execute_with(|| {
+        run_to_block(1);
+        // give enough for reserve
+        Balances::make_free_balance_be(&1, 10_000);
+
+        // call request
+        assert_ok!(Distance::request_distance_evaluation(
+            RuntimeOrigin::signed(1)
+        ));
+        System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested {
+            idty_index: 1,
+            who: 1,
+        }));
+
+        // currency was reserved
+        assert_eq!(Balances::reserved_balance(1), 1000);
+    });
+}
diff --git a/pallets/distance/src/traits.rs b/pallets/distance/src/traits.rs
index de9d532d64f4ce64a16fd20fab28455523f18e69..6849d9c250c29421bffb67f4d52c0cf626271a59 100644
--- a/pallets/distance/src/traits.rs
+++ b/pallets/distance/src/traits.rs
@@ -14,8 +14,24 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
 
-pub trait HandleNegativeEvaluation<T: crate::Config> {
-    /// Do something with the reserved amount on the account,
-    /// when distance evaluation result is negative.
-    fn handle_negative_evaluation(account_id: T::AccountId);
+use crate::*;
+use frame_support::pallet_prelude::*;
+
+pub trait OnValidDistanceStatus<T: Config> {
+    /// Handler for valid distance evaluation
+    fn on_valid_distance_status(idty_index: T::IdtyIndex);
+}
+
+impl<T: Config> OnValidDistanceStatus<T> for () {
+    fn on_valid_distance_status(_idty_index: T::IdtyIndex) {}
+}
+
+pub trait CheckRequestDistanceEvaluation<T: Config> {
+    fn check_request_distance_evaluation(idty_index: T::IdtyIndex) -> Result<(), DispatchError>;
+}
+
+impl<T: Config> CheckRequestDistanceEvaluation<T> for () {
+    fn check_request_distance_evaluation(_idty_index: T::IdtyIndex) -> Result<(), DispatchError> {
+        Ok(())
+    }
 }
diff --git a/pallets/distance/src/weights.rs b/pallets/distance/src/weights.rs
index b0808361a6b8d1810b51c159390fe3d8d640c268..0e8f34c45944c330c446ce4ac047c395f157702e 100644
--- a/pallets/distance/src/weights.rs
+++ b/pallets/distance/src/weights.rs
@@ -20,102 +20,82 @@ use frame_support::weights::{constants::RocksDbWeight, Weight};
 
 pub trait WeightInfo {
     fn request_distance_evaluation() -> Weight;
+    fn request_distance_evaluation_for() -> Weight;
     fn update_evaluation(i: u32) -> Weight;
     fn force_update_evaluation(i: u32) -> Weight;
-    fn force_set_distance_status() -> Weight;
+    fn force_valid_distance_status() -> Weight;
     fn on_finalize() -> Weight;
 }
 
 // Insecure weights implementation, use it for tests only!
 impl WeightInfo for () {
-    /// Storage: Identity IdentityIndexOf (r:1 w:0)
-    /// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured)
-    /// Storage: Distance IdentityDistanceStatus (r:1 w:1)
-    /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured)
-    /// Storage: Session CurrentIndex (r:1 w:0)
-    /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Distance EvaluationPool2 (r:1 w:1)
-    /// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: System Account (r:1 w:1)
-    /// Proof: System Account (max_values: None, max_size: Some(121), added: 2596, mode: MaxEncodedLen)
-    /// Storage: Distance DistanceStatusExpireOn (r:1 w:1)
-    /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured)
     fn request_distance_evaluation() -> Weight {
         // Proof Size summary in bytes:
-        //  Measured:  `935`
-        //  Estimated: `4400`
-        // Minimum execution time: 28_469_000 picoseconds.
-        Weight::from_parts(30_905_000, 0)
-            .saturating_add(Weight::from_parts(0, 4400))
-            .saturating_add(RocksDbWeight::get().reads(6))
-            .saturating_add(RocksDbWeight::get().writes(4))
+        //  Measured:  `1280`
+        //  Estimated: `4745`
+        // Minimum execution time: 876_053_000 picoseconds.
+        Weight::from_parts(898_445_000, 0)
+            .saturating_add(Weight::from_parts(0, 4745))
+            .saturating_add(RocksDbWeight::get().reads(8))
+            .saturating_add(RocksDbWeight::get().writes(3))
+    }
+
+    fn request_distance_evaluation_for() -> Weight {
+        // Proof Size summary in bytes:
+        //  Measured:  `1485`
+        //  Estimated: `7425`
+        // Minimum execution time: 1_118_982_000 picoseconds.
+        Weight::from_parts(1_292_782_000, 0)
+            .saturating_add(Weight::from_parts(0, 7425))
+            .saturating_add(RocksDbWeight::get().reads(10))
+            .saturating_add(RocksDbWeight::get().writes(3))
     }
-    /// Storage: Distance DidUpdate (r:1 w:1)
-    /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Authorship Author (r:1 w:1)
-    /// Proof: Authorship Author (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen)
-    /// Storage: System Digest (r:1 w:0)
-    /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Session CurrentIndex (r:1 w:0)
-    /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Distance EvaluationPool0 (r:1 w:1)
-    /// Proof Skipped: Distance EvaluationPool0 (max_values: Some(1), max_size: None, mode: Measured)
-    /// The range of component `i` is `[1, 600]`.
+
     fn update_evaluation(i: u32) -> Weight {
         // Proof Size summary in bytes:
-        //  Measured:  `744 + i * (10 ±0)`
-        //  Estimated: `2228 + i * (10 ±0)`
-        // Minimum execution time: 13_870_000 picoseconds.
-        Weight::from_parts(17_116_748, 0)
-            .saturating_add(Weight::from_parts(0, 2228))
-            // Standard Error: 684
-            .saturating_add(Weight::from_parts(128_989, 0).saturating_mul(i.into()))
-            .saturating_add(RocksDbWeight::get().reads(5))
+        //  Measured:  `773 + i * (10 ±0)`
+        //  Estimated: `2256 + i * (10 ±0)`
+        // Minimum execution time: 463_878_000 picoseconds.
+        Weight::from_parts(743_823_548, 0)
+            .saturating_add(Weight::from_parts(0, 2256))
+            // Standard Error: 292_144
+            .saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into()))
+            .saturating_add(RocksDbWeight::get().reads(6))
             .saturating_add(RocksDbWeight::get().writes(3))
             .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
     }
-    /// Storage: Session CurrentIndex (r:1 w:0)
-    /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Distance EvaluationPool0 (r:1 w:1)
-    /// Proof Skipped: Distance EvaluationPool0 (max_values: Some(1), max_size: None, mode: Measured)
-    /// The range of component `i` is `[1, 600]`.
+
     fn force_update_evaluation(i: u32) -> Weight {
         // Proof Size summary in bytes:
         //  Measured:  `612 + i * (10 ±0)`
-        //  Estimated: `2096 + i * (10 ±0)`
-        // Minimum execution time: 8_392_000 picoseconds.
-        Weight::from_parts(10_825_908, 0)
-            .saturating_add(Weight::from_parts(0, 2096))
-            // Standard Error: 326
-            .saturating_add(Weight::from_parts(123_200, 0).saturating_mul(i.into()))
+        //  Estimated: `2095 + i * (10 ±0)`
+        // Minimum execution time: 208_812_000 picoseconds.
+        Weight::from_parts(257_150_521, 0)
+            .saturating_add(Weight::from_parts(0, 2095))
+            // Standard Error: 53_366
+            .saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into()))
             .saturating_add(RocksDbWeight::get().reads(2))
             .saturating_add(RocksDbWeight::get().writes(1))
             .saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
     }
-    /// Storage: Session CurrentIndex (r:1 w:0)
-    /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
-    /// Storage: Distance DistanceStatusExpireOn (r:1 w:1)
-    /// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured)
-    /// Storage: Distance IdentityDistanceStatus (r:0 w:1)
-    /// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured)
-    fn force_set_distance_status() -> Weight {
+
+    fn force_valid_distance_status() -> Weight {
         // Proof Size summary in bytes:
-        //  Measured:  `586`
-        //  Estimated: `4051`
-        // Minimum execution time: 8_099_000 picoseconds.
-        Weight::from_parts(8_786_000, 0)
-            .saturating_add(Weight::from_parts(0, 4051))
-            .saturating_add(RocksDbWeight::get().reads(2))
-            .saturating_add(RocksDbWeight::get().writes(2))
+        //  Measured:  `1181`
+        //  Estimated: `7121`
+        // Minimum execution time: 873_892_000 picoseconds.
+        Weight::from_parts(1_081_510_000, 0)
+            .saturating_add(Weight::from_parts(0, 7121))
+            .saturating_add(RocksDbWeight::get().reads(7))
+            .saturating_add(RocksDbWeight::get().writes(5))
     }
-    /// Storage: Distance DidUpdate (r:1 w:1)
-    /// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured)
+
     fn on_finalize() -> Weight {
         // Proof Size summary in bytes:
         //  Measured:  `170`
         //  Estimated: `1655`
-        // Minimum execution time: 3_904_000 picoseconds.
-        Weight::from_parts(4_132_000, 0)
+        // Minimum execution time: 93_595_000 picoseconds.
+        Weight::from_parts(109_467_000, 0)
             .saturating_add(Weight::from_parts(0, 1655))
             .saturating_add(RocksDbWeight::get().reads(1))
             .saturating_add(RocksDbWeight::get().writes(1))
diff --git a/pallets/duniter-test-parameters/src/lib.rs b/pallets/duniter-test-parameters/src/lib.rs
index 4da26974ba7202c4dce8476d415e965d54276878..55ca1c612e69e681ef2f76bb877b92f209cca87a 100644
--- a/pallets/duniter-test-parameters/src/lib.rs
+++ b/pallets/duniter-test-parameters/src/lib.rs
@@ -45,7 +45,7 @@ pub mod types {
         pub idty_confirm_period: BlockNumber,
         pub idty_creation_period: BlockNumber,
         pub membership_period: BlockNumber,
-        pub pending_membership_period: BlockNumber,
+        pub membership_renewal_period: BlockNumber,
         pub ud_creation_period: PeriodCount,
         pub ud_reeval_period: PeriodCount,
         pub smith_cert_max_by_issuer: CertCount,
diff --git a/pallets/duniter-wot/src/lib.rs b/pallets/duniter-wot/src/lib.rs
index eb127c0cc0979c1064910b791c89d70df89f44b5..079467a3f8f2d3c3c5c139bfa666d9b46668f428 100644
--- a/pallets/duniter-wot/src/lib.rs
+++ b/pallets/duniter-wot/src/lib.rs
@@ -30,8 +30,6 @@ mod benchmarking;*/
 
 pub use pallet::*;
 
-use traits::*;
-
 use frame_support::pallet_prelude::*;
 use pallet_certification::traits::SetNextIssuableOn;
 use pallet_identity::{IdtyEvent, IdtyStatus};
@@ -61,8 +59,6 @@ pub mod pallet {
         + pallet_identity::Config<IdtyIndex = IdtyIndex>
         + pallet_membership::Config<IdtyId = IdtyIndex>
     {
-        /// Distance evaluation provider
-        type IsDistanceOk: IsDistanceOk<IdtyIndex>;
         #[pallet::constant]
         type FirstIssuableOn: Get<Self::BlockNumber>;
         #[pallet::constant]
@@ -97,8 +93,8 @@ pub mod pallet {
         DistanceEvaluationPending,
         /// Distance evaluation has not been requested
         DistanceEvaluationNotRequested,
-        /// Identity is not allowed to request membership.
-        IdtyNotAllowedToRequestMembership,
+        /// Identity is not allowed to claim membership.
+        IdtyNotAllowedToClaimMembership,
         /// Identity not allowed to renew membership.
         IdtyNotAllowedToRenewMembership,
         /// Identity creation period not respected.
@@ -119,6 +115,10 @@ pub mod pallet {
         CertToRevoked,
         /// Issuer or receiver not found.
         IdtyNotFound,
+        /// Not enough certs received to request distance evaluation.
+        NotEnoughCertsReceivedToRequestDistanceEvaluation,
+        /// Membership can only be renewed after an antispam delay
+        MembershipRenewalPeriodNotRespected,
     }
 }
 
@@ -191,26 +191,34 @@ impl<T: Config> pallet_certification::traits::CheckCertAllowed<IdtyIndex> for Pa
 impl<T: Config> sp_membership::traits::CheckMembershipCallAllowed<IdtyIndex> for Pallet<T> {
     // membership claim is only possible when enough certs are received (both wots) and distance is ok
     fn check_idty_allowed_to_claim_membership(idty_index: &IdtyIndex) -> Result<(), DispatchError> {
+        // check identity status
+        let idty_value =
+            pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
+        ensure!(
+            idty_value.status == IdtyStatus::Unvalidated
+                || idty_value.status == IdtyStatus::NotMember,
+            Error::<T>::IdtyNotAllowedToClaimMembership
+        );
+        // check received certifications
+        // TODO deduplicate
         let idty_cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(idty_index);
         ensure!(
             idty_cert_meta.received_count >= T::MinCertForMembership::get(),
             Error::<T>::NotEnoughCertsToClaimMembership
         );
-        T::IsDistanceOk::is_distance_ok(idty_index)?;
         Ok(())
     }
 
     // membership renewal is only possible when identity is member (otherwise it should claim again)
     fn check_idty_allowed_to_renew_membership(idty_index: &IdtyIndex) -> Result<(), DispatchError> {
-        if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) {
-            ensure!(
-                idty_value.status == IdtyStatus::Member,
-                Error::<T>::IdtyNotAllowedToRenewMembership
-            );
-            T::IsDistanceOk::is_distance_ok(idty_index)?;
-        } else {
-            return Err(Error::<T>::IdtyNotFound.into());
-        }
+        // check identity status
+        let idty_value =
+            pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
+        ensure!(
+            idty_value.status == IdtyStatus::Member,
+            Error::<T>::IdtyNotAllowedToRenewMembership
+        );
+        // no need to check certification count since loosing certifications make membership expire
         Ok(())
     }
 }
@@ -321,3 +329,86 @@ impl<T: Config> pallet_certification::traits::OnRemovedCert<IdtyIndex> for Palle
         }
     }
 }
+
+/// valid distance status handler
+impl<T: Config + pallet_distance::Config> pallet_distance::traits::OnValidDistanceStatus<T>
+    for Pallet<T>
+{
+    fn on_valid_distance_status(idty_index: IdtyIndex) {
+        if let Some(identity) = pallet_identity::Identities::<T>::get(idty_index) {
+            match identity.status {
+                IdtyStatus::Unconfirmed | IdtyStatus::Revoked => {
+                    // IdtyStatus::Unconfirmed
+                    // distance evaluation request should never happen for unconfirmed identity
+                    // IdtyStatus::Revoked
+                    // the identity can have been revoked during distance evaluation by the oracle
+                }
+
+                IdtyStatus::Unvalidated | IdtyStatus::NotMember => {
+                    // IdtyStatus::Unvalidated
+                    // normal scenario for first entry
+                    // IdtyStatus::NotMember
+                    // normal scenario for re-entry
+                    // the following can fail if a certification expired during distance evaluation
+                    // otherwise it should succeed
+                    let _ = pallet_membership::Pallet::<T>::try_add_membership(idty_index);
+                    // sp_std::if_std! {
+                    //     if let Err(e) = r {
+                    //         print!("failed to claim identity when distance status was found ok: ");
+                    //         println!("{:?}", idty_index);
+                    //         println!("reason: {:?}", e);
+                    //     }
+                    // }
+                }
+                IdtyStatus::Member => {
+                    // IdtyStatus::Member
+                    // normal scenario for renewal
+                    // should succeed
+                    let _ = pallet_membership::Pallet::<T>::try_renew_membership(idty_index);
+                    // sp_std::if_std! {
+                    //     if let Err(e) = r {
+                    //         print!("failed to renew identity when distance status was found ok: ");
+                    //         println!("{:?}", idty_index);
+                    //         println!("reason: {:?}", e);
+                    //     }
+                    // }
+                }
+            }
+        } else {
+            // identity was removed before distance status was found
+            // so it's ok to do nothing
+            sp_std::if_std! {
+                println!("identity was removed before distance status was found: {:?}", idty_index);
+            }
+        }
+    }
+}
+
+/// distance evaluation request allowed check
+impl<T: Config + pallet_distance::Config> pallet_distance::traits::CheckRequestDistanceEvaluation<T>
+    for Pallet<T>
+{
+    fn check_request_distance_evaluation(idty_index: IdtyIndex) -> Result<(), DispatchError> {
+        // check membership renewal antispam
+        let maybe_membership_data = pallet_membership::Pallet::<T>::membership(idty_index);
+        if let Some(membership_data) = maybe_membership_data {
+            // if membership data exists, this is for a renewal, apply antispam
+            ensure!(
+                // current_block > expiration block - membership period + renewal period
+                membership_data.expire_on
+                    + <T as pallet_membership::Config>::MembershipRenewalPeriod::get()
+                    < frame_system::Pallet::<T>::block_number()
+                        + <T as pallet_membership::Config>::MembershipPeriod::get(),
+                Error::<T>::MembershipRenewalPeriodNotRespected
+            );
+        };
+        // check cert count
+        // TODO deduplicate
+        let idty_cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(idty_index);
+        ensure!(
+            idty_cert_meta.received_count >= T::MinCertForMembership::get(),
+            Error::<T>::NotEnoughCertsReceivedToRequestDistanceEvaluation
+        );
+        Ok(())
+    }
+}
diff --git a/pallets/duniter-wot/src/mock.rs b/pallets/duniter-wot/src/mock.rs
index c043ef76448f72bcf18013fd168b8d9c232c1bee..d2679e1b92185cd66f0f979401b2bcc0d2063b26 100644
--- a/pallets/duniter-wot/src/mock.rs
+++ b/pallets/duniter-wot/src/mock.rs
@@ -48,7 +48,7 @@ frame_support::construct_runtime!(
         System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
         DuniterWot: pallet_duniter_wot::{Pallet},
         Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>},
-        Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>},
+        Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>},
         Cert: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>},
     }
 );
@@ -97,7 +97,6 @@ impl pallet_duniter_wot::Config for Test {
     type MinCertForMembership = MinCertForMembership;
     type MinCertForCreateIdtyRight = MinCertForCreateIdtyRight;
     type FirstIssuableOn = FirstIssuableOn;
-    type IsDistanceOk = crate::traits::DistanceAlwaysOk;
 }
 
 // Identity
@@ -139,6 +138,7 @@ impl pallet_identity::Config for Test {
 // Membership
 parameter_types! {
     pub const MembershipPeriod: u64 = 8;
+    pub const MembershipRenewalPeriod: u64 = 2;
 }
 
 impl pallet_membership::Config for Test {
@@ -147,6 +147,7 @@ impl pallet_membership::Config for Test {
     type IdtyIdOf = IdentityIndexOf<Self>;
     type AccountIdOf = ();
     type MembershipPeriod = MembershipPeriod;
+    type MembershipRenewalPeriod = MembershipRenewalPeriod;
     type OnEvent = DuniterWot;
     type RuntimeEvent = RuntimeEvent;
     type WeightInfo = ();
diff --git a/pallets/duniter-wot/src/tests.rs b/pallets/duniter-wot/src/tests.rs
index 2b86ee97f6dd1258126a035a3ca166a95b13eb69..fc4f4df0ed1b2543a1028fbb7ca50bd7a9a5e648 100644
--- a/pallets/duniter-wot/src/tests.rs
+++ b/pallets/duniter-wot/src/tests.rs
@@ -123,7 +123,7 @@ fn test_new_idty_validation() {
 
         // Ferdie should be able to claim membership
         run_to_block(5);
-        assert_ok!(Membership::claim_membership(RuntimeOrigin::signed(6)),);
+        assert_ok!(Membership::try_add_membership(6));
         System::assert_has_event(RuntimeEvent::Membership(
             pallet_membership::Event::MembershipAdded {
                 member: 6,
@@ -242,9 +242,9 @@ fn test_idty_membership_expire() {
         run_to_block(4);
 
         // Alice renews her membership
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1)));
+        assert_ok!(Membership::try_renew_membership(1));
         // Bob renews his membership
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2)));
+        assert_ok!(Membership::try_renew_membership(2));
 
         run_to_block(5);
         // renew certifications so that Alice can still issue cert at block 22
@@ -253,7 +253,7 @@ fn test_idty_membership_expire() {
 
         // Charlie's membership should expire at block #8
         run_to_block(8);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1)));
+        assert_ok!(Membership::try_renew_membership(1));
         assert!(Membership::membership(3).is_none());
 
         System::assert_has_event(RuntimeEvent::Membership(
@@ -276,7 +276,7 @@ fn test_idty_membership_expire() {
         // check that identity is added to auto-revoke list (currently IdentityChangeSchedule)
         assert_eq!(Identity::next_scheduled(14), vec!(3));
         run_to_block(14);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1)));
+        assert_ok!(Membership::try_renew_membership(1));
         // Charlie's identity should be auto-revoked at block #11 (8 + 3)
         System::assert_has_event(RuntimeEvent::Identity(
             pallet_identity::Event::IdtyRevoked {
@@ -356,15 +356,15 @@ fn test_certification_expire() {
         assert_ok!(Cert::add_cert(RuntimeOrigin::signed(3), 3, 2));
         // --- BLOCK 7 ---
         run_to_block(7);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1)));
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2)));
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3)));
+        assert_ok!(Membership::try_renew_membership(1));
+        assert_ok!(Membership::try_renew_membership(2));
+        assert_ok!(Membership::try_renew_membership(3));
 
         // --- BLOCK 14 ---
         run_to_block(14);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1)));
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2)));
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3)));
+        assert_ok!(Membership::try_renew_membership(1));
+        assert_ok!(Membership::try_renew_membership(2));
+        assert_ok!(Membership::try_renew_membership(3));
 
         // normal cert Bob → Alice expires at block 20
         run_to_block(20);
@@ -387,18 +387,18 @@ fn test_certification_expire() {
         // --- BLOCK 21 ---
         // Bob and Charlie can renew their membership
         run_to_block(21);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2)));
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3)));
+        assert_ok!(Membership::try_renew_membership(2));
+        assert_ok!(Membership::try_renew_membership(3));
 
         // Alice can not renew her membership which does not exist
         assert_noop!(
-            Membership::renew_membership(RuntimeOrigin::signed(1)),
+            Membership::try_renew_membership(1),
             pallet_membership::Error::<Test>::MembershipNotFound
         );
 
         // Alice can not claim her membership because she does not have enough certifications
         assert_noop!(
-            Membership::claim_membership(RuntimeOrigin::signed(1)),
+            Membership::try_add_membership(1),
             pallet_duniter_wot::Error::<Test>::NotEnoughCertsToClaimMembership
         );
 
@@ -436,16 +436,16 @@ fn test_cert_can_not_be_issued() {
         assert_ok!(Cert::add_cert(RuntimeOrigin::signed(4), 4, 3)); // +20
                                                                     // --- BLOCK 7 ---
         run_to_block(7);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(4))); // + 8
+        assert_ok!(Membership::try_renew_membership(1)); // + 8
+        assert_ok!(Membership::try_renew_membership(2)); // + 8
+        assert_ok!(Membership::try_renew_membership(3)); // + 8
+        assert_ok!(Membership::try_renew_membership(4)); // + 8
 
         run_to_block(14);
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(1))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(2))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(3))); // + 8
-        assert_ok!(Membership::renew_membership(RuntimeOrigin::signed(4))); // + 8
+        assert_ok!(Membership::try_renew_membership(1)); // + 8
+        assert_ok!(Membership::try_renew_membership(2)); // + 8
+        assert_ok!(Membership::try_renew_membership(3)); // + 8
+        assert_ok!(Membership::try_renew_membership(4)); // + 8
 
         run_to_block(20);
         // println!("{:?}", System::events());
diff --git a/pallets/duniter-wot/src/traits.rs b/pallets/duniter-wot/src/traits.rs
index 6934ce454bdf2c1cbc318158ba8647671804dca7..f3474822b34656f1c99a9b12d34a548316c87941 100644
--- a/pallets/duniter-wot/src/traits.rs
+++ b/pallets/duniter-wot/src/traits.rs
@@ -13,17 +13,3 @@
 //
 // You should have received a copy of the GNU Affero General Public License
 // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
-
-use crate::DispatchError;
-
-pub trait IsDistanceOk<IdtyId> {
-    fn is_distance_ok(idty_id: &IdtyId) -> Result<(), DispatchError>;
-}
-
-pub struct DistanceAlwaysOk;
-
-impl<IdtyId> IsDistanceOk<IdtyId> for DistanceAlwaysOk {
-    fn is_distance_ok(_idty_id: &IdtyId) -> Result<(), DispatchError> {
-        Ok(())
-    }
-}
diff --git a/pallets/membership/src/benchmarking.rs b/pallets/membership/src/benchmarking.rs
index 1bdf5edf6c75666c3c2fc1acd901d2b3ad642733..84216da894acd5049049f33b48ec40bf6d756317 100644
--- a/pallets/membership/src/benchmarking.rs
+++ b/pallets/membership/src/benchmarking.rs
@@ -20,17 +20,15 @@ use super::*;
 
 use frame_benchmarking::benchmarks;
 use frame_system::pallet_prelude::BlockNumberFor;
-use frame_system::RawOrigin;
-use sp_runtime::traits::{Convert, One};
 
 #[cfg(test)]
 use maplit::btreemap;
 
 use crate::Pallet;
 
-fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
-    frame_system::Pallet::<T>::assert_has_event(generic_event.into());
-}
+// fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
+//     frame_system::Pallet::<T>::assert_has_event(generic_event.into());
+// }
 
 benchmarks! {
     where_clause {
@@ -39,39 +37,7 @@ benchmarks! {
             <T as frame_system::Config>::BlockNumber: From<u32>,
     }
 
-    // claim membership
-    claim_membership {
-        let idty: T::IdtyId = 3.into();
-        Membership::<T>::take(idty);
-        let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap();
-        let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
-        T::BenchmarkSetupHandler::force_status_ok(&idty, &caller);
-    }: _<T::RuntimeOrigin>(caller_origin)
-    verify {
-        assert_has_event::<T>(Event::<T>::MembershipAdded{member: idty, expire_on: BlockNumberFor::<T>::one() + T::MembershipPeriod::get()}.into());
-    }
-
-    // renew membership
-    renew_membership {
-        let idty: T::IdtyId = 3.into();
-        let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap();
-        let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
-        T::BenchmarkSetupHandler::force_status_ok(&idty, &caller);
-    }: _<T::RuntimeOrigin>(caller_origin)
-    verify {
-        assert_has_event::<T>(Event::<T>::MembershipAdded{member: idty, expire_on: BlockNumberFor::<T>::one() + T::MembershipPeriod::get()}.into());
-    }
-
-    // revoke membership
-    revoke_membership {
-        let idty: T::IdtyId = 3.into();
-        let caller: T::AccountId = T::AccountIdOf::convert(idty).unwrap();
-        let caller_origin: <T as frame_system::Config>::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into();
-        frame_system::pallet::Pallet::<T>::set_block_number(10_000_000.into()); // Arbitrarily high, to be in the worst case of wot instance.
-    }: _<T::RuntimeOrigin>(caller_origin)
-    verify {
-        assert_has_event::<T>(Event::<T>::MembershipRemoved{member: idty, reason: MembershipRemovalReason::Revoked}.into());
-    }
+    // TODO membership add and renewal should be included to distance on_new_session as worst case scenario
 
     // Base weight of an empty initialize
     on_initialize {
@@ -98,7 +64,7 @@ benchmarks! {
 
     impl_benchmark_test_suite!(
         Pallet,
-        crate::mock::new_test_ext(crate::mock::DefaultMembershipConfig {
+        crate::mock::new_test_ext(crate::mock::MembershipConfig {
         memberships: btreemap![
             3 => MembershipData {
                 expire_on: 3,
diff --git a/pallets/membership/src/lib.rs b/pallets/membership/src/lib.rs
index cba39e37ee5abb5fc058fcdea588e74f7dced747..38f1c491c231635abb6bb6c1777b432275cd9882 100644
--- a/pallets/membership/src/lib.rs
+++ b/pallets/membership/src/lib.rs
@@ -32,9 +32,7 @@ pub use pallet::*;
 pub use weights::WeightInfo;
 
 use frame_support::dispatch::Weight;
-use frame_support::error::BadOrigin;
 use frame_support::pallet_prelude::*;
-use frame_system::RawOrigin;
 use sp_membership::traits::*;
 use sp_membership::MembershipData;
 use sp_runtime::traits::Zero;
@@ -44,13 +42,13 @@ use std::collections::BTreeMap;
 
 #[cfg(feature = "runtime-benchmarks")]
 pub trait SetupBenchmark<IdtyId, AccountId> {
-    fn force_status_ok(idty_index: &IdtyId, account: &AccountId);
+    fn force_valid_distance_status(idty_index: &IdtyId);
     fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId);
 }
 
 #[cfg(feature = "runtime-benchmarks")]
 impl<IdtyId, AccountId> SetupBenchmark<IdtyId, AccountId> for () {
-    fn force_status_ok(_idty_id: &IdtyId, _account: &AccountId) {}
+    fn force_valid_distance_status(_idty_id: &IdtyId) {}
     fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId) {}
 }
 
@@ -94,8 +92,12 @@ pub mod pallet {
         /// Something that gives the AccountId of an IdtyId
         type AccountIdOf: Convert<Self::IdtyId, Option<Self::AccountId>>;
         #[pallet::constant]
-        /// Maximum life span of a non-renewable membership (in number of blocks)
+        /// Maximum life span of a single membership (in number of blocks)
+        // TODO this could be renamed "validity" or "duration"
         type MembershipPeriod: Get<Self::BlockNumber>;
+        /// Minimum delay to wait before renewing membership
+        // i.e. asking for distance evaluation
+        type MembershipRenewalPeriod: Get<Self::BlockNumber>;
         /// On event handler
         type OnEvent: OnEvent<Self::IdtyId>;
         /// Because this pallet emits events, it depends on the runtime's definition of an event.
@@ -156,6 +158,11 @@ pub mod pallet {
             member: T::IdtyId,
             expire_on: BlockNumberFor<T>,
         },
+        /// A membership was renewed.
+        MembershipRenewed {
+            member: T::IdtyId,
+            expire_on: BlockNumberFor<T>,
+        },
         /// A membership was removed.
         MembershipRemoved {
             member: T::IdtyId,
@@ -173,6 +180,8 @@ pub mod pallet {
         MembershipAlreadyAcquired,
         /// Membership not found.
         MembershipNotFound,
+        /// Already member, can not claim membership
+        AlreadyMember,
     }
 
     // HOOKS //
@@ -188,61 +197,13 @@ pub mod pallet {
         }
     }
 
-    // CALLS //
-
-    #[pallet::call]
-    impl<T: Config> Pallet<T> {
-        /// claim membership
-        /// it must fullfill the requirements (certs, distance)
-        /// TODO #159 for main wot claim_membership is called automatically when distance is evaluated positively
-        /// for smith wot, it means joining the authority members
-        #[pallet::call_index(1)]
-        #[pallet::weight(T::WeightInfo::claim_membership())]
-        pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
-            // get identity
-            let idty_id = Self::get_idty_id(origin)?;
-
-            Self::check_allowed_to_claim(idty_id)?;
-            Self::do_add_membership(idty_id);
-            Ok(().into())
-        }
-
-        /// extend the validity period of an active membership
-        #[pallet::call_index(2)]
-        #[pallet::weight(T::WeightInfo::renew_membership())]
-        pub fn renew_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
-            // Verify phase
-            let idty_id = Self::get_idty_id(origin)?;
-            let membership_data =
-                Membership::<T>::get(idty_id).ok_or(Error::<T>::MembershipNotFound)?;
-
-            T::CheckMembershipCallAllowed::check_idty_allowed_to_renew_membership(&idty_id)?;
-
-            // apply phase
-            Self::unschedule_membership_expiry(idty_id, membership_data.expire_on);
-            Self::insert_membership_and_schedule_expiry(idty_id);
-            T::OnEvent::on_event(&sp_membership::Event::MembershipRenewed(idty_id));
-
-            Ok(().into())
-        }
-
-        /// revoke an active membership
-        /// (only available for sub wot, automatic for main wot)
-        #[pallet::call_index(3)]
-        #[pallet::weight(T::WeightInfo::revoke_membership())]
-        pub fn revoke_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
-            // Verify phase
-            let idty_id = Self::get_idty_id(origin)?;
-
-            // Apply phase
-            Self::do_remove_membership(idty_id, MembershipRemovalReason::Revoked);
-
-            Ok(().into())
-        }
-    }
+    // // CALLS //
+    // #[pallet::call]
+    // impl<T: Config> Pallet<T> {
+    //     // no calls for membership pallet
+    // }
 
     // INTERNAL FUNCTIONS //
-
     impl<T: Config> Pallet<T> {
         /// unschedule membership expiry
         fn unschedule_membership_expiry(idty_id: T::IdtyId, block_number: T::BlockNumber) {
@@ -254,31 +215,78 @@ pub mod pallet {
             }
         }
         /// schedule membership expiry
-        fn insert_membership_and_schedule_expiry(idty_id: T::IdtyId) {
+        fn insert_membership_and_schedule_expiry(idty_id: T::IdtyId) -> T::BlockNumber {
             let block_number = frame_system::pallet::Pallet::<T>::block_number();
             let expire_on = block_number + T::MembershipPeriod::get();
 
             Membership::<T>::insert(idty_id, MembershipData { expire_on });
             MembershipsExpireOn::<T>::append(expire_on, idty_id);
-            Self::deposit_event(Event::MembershipAdded {
-                member: idty_id,
-                expire_on,
-            });
+            expire_on
         }
 
         /// check that membership can be claimed
         pub fn check_allowed_to_claim(idty_id: T::IdtyId) -> Result<(), DispatchError> {
+            // no-op is error
+            ensure!(
+                Membership::<T>::get(idty_id).is_none(),
+                Error::<T>::AlreadyMember
+            );
+
             // enough certifications and distance rule for example
             T::CheckMembershipCallAllowed::check_idty_allowed_to_claim_membership(&idty_id)?;
             Ok(())
         }
 
+        /// check that membership can be renewed
+        pub fn check_allowed_to_renew(
+            idty_id: T::IdtyId,
+        ) -> Result<MembershipData<T::BlockNumber>, DispatchError> {
+            let membership_data =
+                Membership::<T>::get(idty_id).ok_or(Error::<T>::MembershipNotFound)?;
+
+            // enough certifications and distance rule for example
+            T::CheckMembershipCallAllowed::check_idty_allowed_to_renew_membership(&idty_id)?;
+            Ok(membership_data)
+        }
+
+        /// try claim membership
+        pub fn try_add_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> {
+            Self::check_allowed_to_claim(idty_id)?;
+            Self::do_add_membership(idty_id);
+            Ok(())
+        }
+
+        /// try renew membership
+        pub fn try_renew_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> {
+            let membership_data = Self::check_allowed_to_renew(idty_id)?;
+            Self::do_renew_membership(idty_id, membership_data);
+            Ok(())
+        }
+
         /// perform membership addition
         fn do_add_membership(idty_id: T::IdtyId) {
-            Self::insert_membership_and_schedule_expiry(idty_id);
+            let expire_on = Self::insert_membership_and_schedule_expiry(idty_id);
+            Self::deposit_event(Event::MembershipAdded {
+                member: idty_id,
+                expire_on,
+            });
             T::OnEvent::on_event(&sp_membership::Event::MembershipAdded(idty_id));
         }
 
+        /// perform membership renewal
+        fn do_renew_membership(
+            idty_id: T::IdtyId,
+            membership_data: MembershipData<T::BlockNumber>,
+        ) {
+            Self::unschedule_membership_expiry(idty_id, membership_data.expire_on);
+            let expire_on = Self::insert_membership_and_schedule_expiry(idty_id);
+            Self::deposit_event(Event::MembershipRenewed {
+                member: idty_id,
+                expire_on,
+            });
+            T::OnEvent::on_event(&sp_membership::Event::MembershipRenewed(idty_id));
+        }
+
         /// perform membership removal
         pub fn do_remove_membership(idty_id: T::IdtyId, reason: MembershipRemovalReason) {
             if let Some(membership_data) = Membership::<T>::take(idty_id) {
@@ -291,15 +299,6 @@ pub mod pallet {
             }
         }
 
-        /// check the origin and get identity id if valid
-        fn get_idty_id(origin: OriginFor<T>) -> Result<T::IdtyId, DispatchError> {
-            if let Ok(RawOrigin::Signed(account_id)) = origin.into() {
-                T::IdtyIdOf::convert(account_id).ok_or_else(|| Error::<T>::IdtyIdNotFound.into())
-            } else {
-                Err(BadOrigin.into())
-            }
-        }
-
         /// perform the membership expiry scheduled at given block
         pub fn expire_memberships(block_number: T::BlockNumber) -> Weight {
             let mut expired_idty_count = 0u32;
diff --git a/pallets/membership/src/mock.rs b/pallets/membership/src/mock.rs
index 5173ff716833722a3269e9c65bc5a04e721fbaab..a13325a0c29ce3e32fa7d16cfc5af80de48f06e4 100644
--- a/pallets/membership/src/mock.rs
+++ b/pallets/membership/src/mock.rs
@@ -41,7 +41,7 @@ frame_support::construct_runtime!(
         UncheckedExtrinsic = UncheckedExtrinsic,
     {
         System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
-        DefaultMembership: pallet_membership::{Pallet, Call, Event<T>, Storage, Config<T>},
+        Membership: pallet_membership::{Pallet, Event<T>, Storage, Config<T>},
     }
 );
 
@@ -79,7 +79,7 @@ impl system::Config for Test {
 
 parameter_types! {
     pub const MembershipPeriod: BlockNumber = 5;
-    pub const PendingMembershipPeriod: BlockNumber = 3;
+    pub const MembershipRenewalPeriod: BlockNumber = 2;
 }
 
 impl pallet_membership::Config for Test {
@@ -88,6 +88,7 @@ impl pallet_membership::Config for Test {
     type IdtyIdOf = ConvertInto;
     type AccountIdOf = ConvertInto;
     type MembershipPeriod = MembershipPeriod;
+    type MembershipRenewalPeriod = MembershipRenewalPeriod;
     type OnEvent = ();
     type RuntimeEvent = RuntimeEvent;
     type WeightInfo = ();
@@ -99,7 +100,7 @@ impl pallet_membership::Config for Test {
 pub fn new_test_ext(gen_conf: pallet_membership::GenesisConfig<Test>) -> sp_io::TestExternalities {
     GenesisConfig {
         system: SystemConfig::default(),
-        default_membership: gen_conf,
+        membership: gen_conf,
     }
     .build_storage()
     .unwrap()
@@ -108,11 +109,11 @@ pub fn new_test_ext(gen_conf: pallet_membership::GenesisConfig<Test>) -> sp_io::
 
 pub fn run_to_block(n: u64) {
     while System::block_number() < n {
-        DefaultMembership::on_finalize(System::block_number());
+        Membership::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());
-        DefaultMembership::on_initialize(System::block_number());
+        Membership::on_initialize(System::block_number());
     }
 }
diff --git a/pallets/membership/src/tests.rs b/pallets/membership/src/tests.rs
index 53da60336bb6cefa94a5897836a38871929f7503..4f22c32135af2cd04f215261307b0ad5278dfba2 100644
--- a/pallets/membership/src/tests.rs
+++ b/pallets/membership/src/tests.rs
@@ -22,11 +22,8 @@ use maplit::btreemap;
 use sp_membership::traits::*;
 use sp_membership::MembershipData;
 
-// alias
-type RtEvent = RuntimeEvent;
-
-fn default_gen_conf() -> DefaultMembershipConfig {
-    DefaultMembershipConfig {
+fn default_gen_conf() -> MembershipConfig {
+    MembershipConfig {
         memberships: btreemap![
             0 => MembershipData {
                 expire_on: 3,
@@ -41,10 +38,10 @@ fn test_genesis_build() {
         run_to_block(1);
         // Verify state
         assert_eq!(
-            DefaultMembership::membership(0),
+            Membership::membership(0),
             Some(MembershipData { expire_on: 3 })
         );
-        assert_eq!(DefaultMembership::members_count(), 1);
+        assert_eq!(Membership::members_count(), 1);
     });
 }
 
@@ -55,42 +52,39 @@ fn test_membership_expiration() {
     new_test_ext(default_gen_conf()).execute_with(|| {
         // Membership 0 should not expired on block #2
         run_to_block(2);
-        assert!(DefaultMembership::is_member(&0));
+        assert!(Membership::is_member(&0));
         // Membership 0 should expire on block #3
         run_to_block(3);
-        assert!(!DefaultMembership::is_member(&0));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved {
+        assert!(!Membership::is_member(&0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved {
             member: 0,
             reason: MembershipRemovalReason::Expired,
         }));
     });
 }
 
-/// test membership renewal
-// there is no limit for membership renewal outside wot rules (number of certs, distance rule)
+/// test membership renewal (triggered automatically after distance evaluation)
 #[test]
 fn test_membership_renewal() {
     new_test_ext(default_gen_conf()).execute_with(|| {
         // membership still valid at block 2
         run_to_block(2);
-        assert!(DefaultMembership::is_member(&0));
+        assert!(Membership::is_member(&0));
         // Membership 0 can be renewed
-        assert_ok!(DefaultMembership::renew_membership(RuntimeOrigin::signed(
-            0
-        ),));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded {
+        assert_ok!(Membership::try_renew_membership(0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRenewed {
             member: 0,
             expire_on: 2 + <Test as crate::Config>::MembershipPeriod::get(),
         }));
         // membership should not expire at block 3 to 6 because it has been renewed
         run_to_block(3);
-        assert!(DefaultMembership::is_member(&0));
+        assert!(Membership::is_member(&0));
         run_to_block(6);
-        assert!(DefaultMembership::is_member(&0));
+        assert!(Membership::is_member(&0));
         // membership should expire at block 7 (2+5)
         run_to_block(7);
-        assert!(!DefaultMembership::is_member(&0));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved {
+        assert!(!Membership::is_member(&0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved {
             member: 0,
             reason: MembershipRemovalReason::Expired,
         }));
@@ -102,14 +96,14 @@ fn test_membership_renewal() {
 fn test_membership_renewal_nope() {
     new_test_ext(default_gen_conf()).execute_with(|| {
         run_to_block(2);
-        assert!(!DefaultMembership::is_member(&1));
+        assert!(!Membership::is_member(&1));
         // Membership 1 can not be renewed
         assert_noop!(
-            DefaultMembership::renew_membership(RuntimeOrigin::signed(1)),
+            Membership::try_renew_membership(1),
             Error::<Test>::MembershipNotFound,
         );
         run_to_block(3);
-        assert!(!DefaultMembership::is_member(&1));
+        assert!(!Membership::is_member(&1));
     });
 }
 
@@ -119,21 +113,17 @@ fn test_membership_revocation() {
     new_test_ext(default_gen_conf()).execute_with(|| {
         run_to_block(1);
         // Membership 0 can be revocable on block #1
-        assert_ok!(DefaultMembership::revoke_membership(RuntimeOrigin::signed(
-            0
-        ),));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved {
+        Membership::do_remove_membership(0, MembershipRemovalReason::Revoked);
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved {
             member: 0,
             reason: MembershipRemovalReason::Revoked,
         }));
-        assert_eq!(DefaultMembership::membership(0), None);
+        assert_eq!(Membership::membership(0), None);
 
         // Membership 0 can re-claim membership
         run_to_block(5);
-        assert_ok!(DefaultMembership::claim_membership(RuntimeOrigin::signed(
-            0
-        ),));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded {
+        assert_ok!(Membership::try_add_membership(0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipAdded {
             member: 0,
             expire_on: 5 + <Test as crate::Config>::MembershipPeriod::get(),
         }));
@@ -149,28 +139,24 @@ fn test_membership_workflow() {
     new_test_ext(Default::default()).execute_with(|| {
         // - Then, idty 0 claim membership
         run_to_block(2);
-        assert_ok!(DefaultMembership::claim_membership(RuntimeOrigin::signed(
-            0
-        ),));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipAdded {
+        assert_ok!(Membership::try_add_membership(0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipAdded {
             member: 0,
             expire_on: 2 + <Test as crate::Config>::MembershipPeriod::get(),
         }));
 
         // - Then, idty 0 claim renewal, should success
         run_to_block(2);
-        assert_ok!(DefaultMembership::renew_membership(RuntimeOrigin::signed(
-            0
-        ),));
+        assert_ok!(Membership::try_renew_membership(0));
 
         // idty 0 should still be member until membership period ended
         run_to_block(6); // 2 + 5 - 1
-        assert!(DefaultMembership::is_member(&0));
+        assert!(Membership::is_member(&0));
 
         // - Then, idty 0 should expire after membership period
         run_to_block(7); // 2 + 5
-        assert!(!DefaultMembership::is_member(&0));
-        System::assert_has_event(RtEvent::DefaultMembership(Event::MembershipRemoved {
+        assert!(!Membership::is_member(&0));
+        System::assert_has_event(RuntimeEvent::Membership(Event::MembershipRemoved {
             member: 0,
             reason: MembershipRemovalReason::Expired,
         }));
diff --git a/pallets/universal-dividend/src/lib.rs b/pallets/universal-dividend/src/lib.rs
index 25de8fb027cd88c57244465d9a0292b9c937f591..be744570ea9d9e85ec20262738c2e7149eadb01d 100644
--- a/pallets/universal-dividend/src/lib.rs
+++ b/pallets/universal-dividend/src/lib.rs
@@ -99,6 +99,8 @@ pub mod pallet {
 
     #[pallet::type_value]
     pub fn DefaultForCurrentUdIndex() -> UdIndex {
+        // FIXME seems off by 1
+        // or rename to "next" ud index instead of "current" ud index
         1
     }
 
@@ -110,6 +112,9 @@ pub mod pallet {
 
     #[cfg(test)]
     #[pallet::storage]
+    // UD should be linked to idtyid instead of accountid
+    // if it is convenient in test, why not have it in runtime also?
+    // storing it in idty_value.data is strange
     pub type TestMembers<T: Config> = StorageMap<
         _,
         Blake2_128Concat,
@@ -263,6 +268,7 @@ pub mod pallet {
 
             // Increment ud index
             let ud_index = CurrentUdIndex::<T>::mutate(|next_ud_index| {
+                // FIXME seems off by 1 due to default to 1
                 core::mem::replace(next_ud_index, next_ud_index.saturating_add(1))
             });
 
diff --git a/resources/gdev.yaml b/resources/gdev.yaml
index a901457535966732d744522220a1b26ce5dfe1ee..fcfd98034cb67c96d53cdc9991f43184fdffd02d 100644
--- a/resources/gdev.yaml
+++ b/resources/gdev.yaml
@@ -27,8 +27,8 @@ parameters:
   cert_validity_period: 2102400
   # Validity duration of a membership. 1051200 blocks = 73 days.
   membership_period: 1051200
-  # Validity duration of a pending membership. 172800 blocks = 12 days.
-  pending_membership_period: 172800
+  # Period to wait before membership renewal. 1051200 blocks = 1 days.
+  membership_renewal_period: 14400
   # Delay a new member must observe before being able to emit a certification
   wot_first_cert_issuable_on: 0
   # Number of required received certs to become a member
@@ -68,4 +68,5 @@ sudo_key: "5CfodrEFe64MJtWvfhTHYBuUySr4WXLv2B41mZFucTXSGMFA"
 treasury_funder_address: "5E6q47RRGZU15LjUiBTm2DZjpqFKAjRNafYS8YV8AzTQZtLG"
 
 # The technical committee members, to act as sudo
-technical_committee: ["Pini", "moul", "HugoTrentesaux", "tuxmain", "1000i100", "vit", "cgeek"]
\ No newline at end of file
+technical_committee:
+  ["Pini", "moul", "HugoTrentesaux", "tuxmain", "1000i100", "vit", "cgeek"]
diff --git a/resources/metadata.scale b/resources/metadata.scale
index 15ad56874e147a9b94c30468250afd196b8504ec..5908806517d61badcdd60a81e867552b7a21ea01 100644
Binary files a/resources/metadata.scale and b/resources/metadata.scale differ
diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs
index 6bae43eb554b952e97e8f7e23b226f4002bf0b70..d0ce866c0d4bf0bde2f48c5155b05ecdc9f1bdd6 100644
--- a/runtime/common/src/pallets_config.rs
+++ b/runtime/common/src/pallets_config.rs
@@ -453,7 +453,6 @@ macro_rules! pallets_config {
 
         impl pallet_duniter_wot::Config for Runtime {
             type FirstIssuableOn = WotFirstCertIssuableOn;
-            type IsDistanceOk = common_runtime::providers::MainWotIsDistanceOk<Runtime>;
             type MinCertForMembership = WotMinCertForMembership;
             type MinCertForCreateIdtyRight = WotMinCertForCreateIdtyRight;
         }
@@ -488,6 +487,7 @@ macro_rules! pallets_config {
             type IdtyIdOf = common_runtime::providers::IdentityIndexOf<Self>;
             type AccountIdOf = common_runtime::providers::IdentityAccountIdProvider<Self>;
             type MembershipPeriod = MembershipPeriod;
+            type MembershipRenewalPeriod = MembershipRenewalPeriod;
             type OnEvent = (OnMembershipEventHandler<Wot, Runtime>, Wot);
             type RuntimeEvent = RuntimeEvent;
             type WeightInfo = common_runtime::weights::pallet_membership::WeightInfo<Runtime>;
@@ -516,9 +516,10 @@ macro_rules! pallets_config {
             type EvaluationPrice = frame_support::traits::ConstU64<1000>;
             type MaxRefereeDistance = frame_support::traits::ConstU32<5>;
             type MinAccessibleReferees = MinAccessibleReferees;
-            type ResultExpiration = frame_support::traits::ConstU32<720>;
             type RuntimeEvent = RuntimeEvent;
             type WeightInfo = common_runtime::weights::pallet_distance::WeightInfo<Runtime>;
+            type OnValidDistanceStatus = Wot;
+            type CheckRequestDistanceEvaluation = Wot;
         }
 
         // SMITH-MEMBERS
diff --git a/runtime/common/src/providers.rs b/runtime/common/src/providers.rs
index ca631988e74cfd3b19417be348605c4bd58d5574..504cf92f8db91313aa780b7e8c5b2f927bc35863 100644
--- a/runtime/common/src/providers.rs
+++ b/runtime/common/src/providers.rs
@@ -17,7 +17,6 @@
 use crate::{entities::IdtyData, AccountId, IdtyIndex};
 use core::marker::PhantomData;
 use pallet_universal_dividend::FirstEligibleUd;
-use sp_runtime::DispatchError;
 
 pub struct IdentityAccountIdProvider<Runtime>(PhantomData<Runtime>);
 
@@ -71,31 +70,6 @@ where
     }
 }
 
-pub struct MainWotIsDistanceOk<T>(PhantomData<T>);
-
-impl<T> pallet_duniter_wot::traits::IsDistanceOk<<T as pallet_identity::Config>::IdtyIndex>
-    for MainWotIsDistanceOk<T>
-where
-    T: pallet_distance::Config + pallet_duniter_wot::Config,
-{
-    fn is_distance_ok(
-        idty_id: &<T as pallet_identity::Config>::IdtyIndex,
-    ) -> Result<(), DispatchError> {
-        match pallet_distance::Pallet::<T>::identity_distance_status(idty_id) {
-            Some((_, status)) => match status {
-                pallet_distance::DistanceStatus::Valid => Ok(()),
-                pallet_distance::DistanceStatus::Invalid => {
-                    Err(pallet_duniter_wot::Error::<T>::DistanceIsInvalid.into())
-                }
-                pallet_distance::DistanceStatus::Pending => {
-                    Err(pallet_duniter_wot::Error::<T>::DistanceEvaluationPending.into())
-                }
-            },
-            None => Err(pallet_duniter_wot::Error::<T>::DistanceEvaluationNotRequested.into()),
-        }
-    }
-}
-
 pub struct IsWoTMemberProvider<T>(PhantomData<T>);
 impl<T: pallet_smith_members::Config>
     sp_runtime::traits::IsMember<<T as pallet_membership::Config>::IdtyId>
@@ -121,14 +95,8 @@ macro_rules! impl_benchmark_setup_handler {
             T: pallet_certification::Config,
             <T as pallet_certification::Config>::IdtyIndex: From<u32>,
         {
-            fn force_status_ok(
-                idty_id: &IdtyIndex,
-                account: &<T as frame_system::Config>::AccountId,
-            ) -> () {
-                let _ = pallet_distance::Pallet::<T>::set_distance_status(
-                    *idty_id,
-                    Some((account.clone(), pallet_distance::DistanceStatus::Valid)),
-                );
+            fn force_valid_distance_status(idty_id: &IdtyIndex) -> () {
+                let _ = pallet_distance::Pallet::<T>::do_valid_distance_status(*idty_id);
             }
             fn add_cert(issuer: &IdtyIndex, receiver: &IdtyIndex) {
                 let _ = pallet_certification::Pallet::<T>::do_add_cert_checked(
diff --git a/runtime/common/src/weights/pallet_distance.rs b/runtime/common/src/weights/pallet_distance.rs
index 27aa944753e7c172da0a2c05b5493358dcd3008c..c1c3c8c557f2b26e50632388bc65d06780c999fd 100644
--- a/runtime/common/src/weights/pallet_distance.rs
+++ b/runtime/common/src/weights/pallet_distance.rs
@@ -17,19 +17,19 @@
 //! Autogenerated weights for `pallet_distance`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2024-01-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-12-20, STEPS: `8`, REPEAT: `4`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `bgallois-ms7d43`, CPU: `12th Gen Intel(R) Core(TM) i3-12100F`
+//! HOSTNAME: `squirrel`, CPU: `Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz`
 //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
 
 // Executed Command:
-// target/release/duniter
+// ./target/debug/duniter
 // benchmark
 // pallet
 // --chain=dev
-// --steps=50
-// --repeat=20
-// --pallet=*
+// --steps=8
+// --repeat=4
+// --pallet=pallet-distance
 // --extrinsic=*
 // --execution=wasm
 // --wasm-execution=compiled
@@ -50,25 +50,57 @@ pub struct WeightInfo<T>(PhantomData<T>);
 impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> {
 	/// Storage: Identity IdentityIndexOf (r:1 w:0)
 	/// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured)
-	/// Storage: Distance IdentityDistanceStatus (r:1 w:1)
-	/// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Distance PendingEvaluationRequest (r:1 w:1)
+	/// Proof Skipped: Distance PendingEvaluationRequest (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Distance ValidEvaluationResult (r:1 w:0)
+	/// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Cert StorageIdtyCertMeta (r:1 w:0)
+	/// Proof Skipped: Cert StorageIdtyCertMeta (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Parameters ParametersStorage (r:1 w:0)
+	/// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured)
 	/// Storage: Session CurrentIndex (r:1 w:0)
 	/// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
 	/// Storage: Distance EvaluationPool2 (r:1 w:1)
 	/// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured)
 	/// Storage: System Account (r:1 w:1)
 	/// Proof: System Account (max_values: None, max_size: Some(126), added: 2601, mode: MaxEncodedLen)
-	/// Storage: Distance DistanceStatusExpireOn (r:1 w:1)
-	/// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured)
 	fn request_distance_evaluation() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `939`
-		//  Estimated: `4404`
-		// Minimum execution time: 33_043_000 picoseconds.
-		Weight::from_parts(33_977_000, 0)
-			.saturating_add(Weight::from_parts(0, 4404))
-			.saturating_add(T::DbWeight::get().reads(6))
-			.saturating_add(T::DbWeight::get().writes(4))
+		//  Measured:  `1280`
+		//  Estimated: `4745`
+		// Minimum execution time: 876_053_000 picoseconds.
+		Weight::from_parts(898_445_000, 0)
+			.saturating_add(Weight::from_parts(0, 4745))
+			.saturating_add(T::DbWeight::get().reads(8))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: Identity IdentityIndexOf (r:1 w:0)
+	/// Proof Skipped: Identity IdentityIndexOf (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Identity Identities (r:2 w:0)
+	/// Proof Skipped: Identity Identities (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Distance PendingEvaluationRequest (r:1 w:1)
+	/// Proof Skipped: Distance PendingEvaluationRequest (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Distance ValidEvaluationResult (r:1 w:0)
+	/// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Cert StorageIdtyCertMeta (r:1 w:0)
+	/// Proof Skipped: Cert StorageIdtyCertMeta (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Parameters ParametersStorage (r:1 w:0)
+	/// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Session CurrentIndex (r:1 w:0)
+	/// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Distance EvaluationPool2 (r:1 w:1)
+	/// Proof Skipped: Distance EvaluationPool2 (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: System Account (r:1 w:1)
+	/// Proof: System Account (max_values: None, max_size: Some(126), added: 2601, mode: MaxEncodedLen)
+	fn request_distance_evaluation_for() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1485`
+		//  Estimated: `7425`
+		// Minimum execution time: 1_118_982_000 picoseconds.
+		Weight::from_parts(1_292_782_000, 0)
+			.saturating_add(Weight::from_parts(0, 7425))
+			.saturating_add(T::DbWeight::get().reads(10))
+			.saturating_add(T::DbWeight::get().writes(3))
 	}
 	/// Storage: Distance DidUpdate (r:1 w:1)
 	/// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured)
@@ -85,13 +117,13 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> {
 	/// The range of component `i` is `[1, 600]`.
 	fn update_evaluation(i: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `772 + i * (10 ±0)`
+		//  Measured:  `773 + i * (10 ±0)`
 		//  Estimated: `2256 + i * (10 ±0)`
-		// Minimum execution time: 22_135_000 picoseconds.
-		Weight::from_parts(27_043_883, 0)
+		// Minimum execution time: 463_878_000 picoseconds.
+		Weight::from_parts(743_823_548, 0)
 			.saturating_add(Weight::from_parts(0, 2256))
-			// Standard Error: 1_421
-			.saturating_add(Weight::from_parts(125_435, 0).saturating_mul(i.into()))
+			// Standard Error: 292_144
+			.saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into()))
 			.saturating_add(T::DbWeight::get().reads(6))
 			.saturating_add(T::DbWeight::get().writes(3))
 			.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
@@ -103,32 +135,40 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> {
 	/// The range of component `i` is `[1, 600]`.
 	fn force_update_evaluation(i: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `611 + i * (10 ±0)`
+		//  Measured:  `612 + i * (10 ±0)`
 		//  Estimated: `2095 + i * (10 ±0)`
-		// Minimum execution time: 13_033_000 picoseconds.
-		Weight::from_parts(15_741_933, 0)
+		// Minimum execution time: 208_812_000 picoseconds.
+		Weight::from_parts(257_150_521, 0)
 			.saturating_add(Weight::from_parts(0, 2095))
-			// Standard Error: 693
-			.saturating_add(Weight::from_parts(121_989, 0).saturating_mul(i.into()))
+			// Standard Error: 53_366
+			.saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into()))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
 			.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
 	}
 	/// Storage: Session CurrentIndex (r:1 w:0)
 	/// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured)
-	/// Storage: Distance DistanceStatusExpireOn (r:1 w:1)
-	/// Proof Skipped: Distance DistanceStatusExpireOn (max_values: None, max_size: None, mode: Measured)
-	/// Storage: Distance IdentityDistanceStatus (r:0 w:1)
-	/// Proof Skipped: Distance IdentityDistanceStatus (max_values: None, max_size: None, mode: Measured)
-	fn force_set_distance_status() -> Weight {
+	/// Storage: Distance ValidEvaluationExpireOn (r:1 w:1)
+	/// Proof Skipped: Distance ValidEvaluationExpireOn (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Identity Identities (r:1 w:0)
+	/// Proof Skipped: Identity Identities (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Membership Membership (r:1 w:1)
+	/// Proof Skipped: Membership Membership (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Membership MembershipsExpireOn (r:2 w:2)
+	/// Proof Skipped: Membership MembershipsExpireOn (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Parameters ParametersStorage (r:1 w:0)
+	/// Proof Skipped: Parameters ParametersStorage (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Distance ValidEvaluationResult (r:0 w:1)
+	/// Proof Skipped: Distance ValidEvaluationResult (max_values: None, max_size: None, mode: Measured)
+	fn force_valid_distance_status() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `585`
-		//  Estimated: `4050`
-		// Minimum execution time: 12_886_000 picoseconds.
-		Weight::from_parts(13_465_000, 0)
-			.saturating_add(Weight::from_parts(0, 4050))
-			.saturating_add(T::DbWeight::get().reads(2))
-			.saturating_add(T::DbWeight::get().writes(2))
+		//  Measured:  `1181`
+		//  Estimated: `7121`
+		// Minimum execution time: 873_892_000 picoseconds.
+		Weight::from_parts(1_081_510_000, 0)
+			.saturating_add(Weight::from_parts(0, 7121))
+			.saturating_add(T::DbWeight::get().reads(7))
+			.saturating_add(T::DbWeight::get().writes(5))
 	}
 	/// Storage: Distance DidUpdate (r:1 w:1)
 	/// Proof Skipped: Distance DidUpdate (max_values: Some(1), max_size: None, mode: Measured)
@@ -136,8 +176,8 @@ impl<T: frame_system::Config> pallet_distance::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `170`
 		//  Estimated: `1655`
-		// Minimum execution time: 3_830_000 picoseconds.
-		Weight::from_parts(4_065_000, 0)
+		// Minimum execution time: 93_595_000 picoseconds.
+		Weight::from_parts(109_467_000, 0)
 			.saturating_add(Weight::from_parts(0, 1655))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs
index 40a3025acac0603b7069edfef33d2c977b0497b5..8c9bca67c4336aaf69d36a807bf07a27cb6f6657 100644
--- a/runtime/g1/src/lib.rs
+++ b/runtime/g1/src/lib.rs
@@ -174,9 +174,6 @@ impl Contains<RuntimeCall> for BaseCallFilter {
             call,
             RuntimeCall::System(
                 frame_system::Call::remark { .. } | frame_system::Call::remark_with_event { .. }
-            ) | RuntimeCall::Membership(
-                pallet_membership::Call::claim_membership { .. }
-                    | pallet_membership::Call::revoke_membership { .. }
             ) | RuntimeCall::Session(_)
         )
     }
@@ -287,7 +284,7 @@ construct_runtime!(
         // Web Of Trust
         Wot: pallet_duniter_wot::{Pallet} = 40,
         Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41,
-        Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42,
+        Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42,
         Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43,
         Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44,
 
diff --git a/runtime/g1/src/parameters.rs b/runtime/g1/src/parameters.rs
index 22ef4efa43e1bf4424bbd86c5b6bf00daeb62586..adfc0183ad11ca025a18b29e335966fbdb9257ff 100644
--- a/runtime/g1/src/parameters.rs
+++ b/runtime/g1/src/parameters.rs
@@ -105,7 +105,7 @@ parameter_types! {
 // Membership
 parameter_types! {
     pub const MembershipPeriod: BlockNumber = YEARS;
-    pub const PendingMembershipPeriod: BlockNumber = 2 * MONTHS;
+    pub const MembershipRenewalPeriod: BlockNumber = 2 * MONTHS;
 }
 
 // Certification
diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs
index 04366d5dbe9b44698365b1531d33653819e914bd..d64153d645e659cc59d1a789cbfd253115618e62 100644
--- a/runtime/gdev/src/lib.rs
+++ b/runtime/gdev/src/lib.rs
@@ -177,10 +177,9 @@ impl Contains<RuntimeCall> for BaseCallFilter {
     fn contains(call: &RuntimeCall) -> bool {
         !matches!(
             call,
-            // in main web of trust, membership request and revoke are handeled through identity pallet
-            // the user can not call them directly
-            RuntimeCall::Membership(pallet_membership::Call::revoke_membership { .. })
-                | RuntimeCall::Session(_)
+            // session calls can not be called directly
+            // it should be done through authority-members pallet
+            RuntimeCall::Session(_)
         )
     }
 }
@@ -257,6 +256,7 @@ common_runtime::pallets_config! {
     pub type ConfirmPeriod = pallet_duniter_test_parameters::IdtyConfirmPeriod<Runtime>;
     pub type IdtyCreationPeriod = pallet_duniter_test_parameters::IdtyCreationPeriod<Runtime>;
     pub type MembershipPeriod = pallet_duniter_test_parameters::MembershipPeriod<Runtime>;
+    pub type MembershipRenewalPeriod = pallet_duniter_test_parameters::MembershipRenewalPeriod<Runtime>;
     pub type UdCreationPeriod = pallet_duniter_test_parameters::UdCreationPeriod<Runtime>;
     pub type UdReevalPeriod = pallet_duniter_test_parameters::UdReevalPeriod<Runtime>;
     pub type WotFirstCertIssuableOn = pallet_duniter_test_parameters::WotFirstCertIssuableOn<Runtime>;
@@ -329,7 +329,7 @@ construct_runtime!(
         // Web Of Trust
         Wot: pallet_duniter_wot::{Pallet} = 40,
         Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41,
-        Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42,
+        Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42,
         Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43,
         Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44,
 
diff --git a/runtime/gdev/src/parameters.rs b/runtime/gdev/src/parameters.rs
index 7dc4e8177245114e7df93fb3cd8a2d4e7e7e29c2..abd164fe652d74a58e2d90d8cfd61b3c224534ac 100644
--- a/runtime/gdev/src/parameters.rs
+++ b/runtime/gdev/src/parameters.rs
@@ -90,6 +90,11 @@ frame_support::parameter_types! {
     pub const ChangeOwnerKeyPeriod: BlockNumber = 7 * DAYS;
 }
 
+// Membership
+frame_support::parameter_types! {
+    pub const SmithMembershipRenewalPeriod: BlockNumber = MONTHS;
+}
+
 /*************/
 /* UTILITIES */
 /*************/
diff --git a/runtime/gdev/tests/common/mod.rs b/runtime/gdev/tests/common/mod.rs
index 261755f759209283c2acbbfa4dd0de41c26f24c8..0ca8aa74cc02be2f068724ebafd8811ce6b58cb1 100644
--- a/runtime/gdev/tests/common/mod.rs
+++ b/runtime/gdev/tests/common/mod.rs
@@ -105,7 +105,7 @@ impl ExtBuilder {
                 idty_confirm_period: 40,
                 idty_creation_period: 50,
                 membership_period: 100,
-                pending_membership_period: 500,
+                membership_renewal_period: 10,
                 ud_creation_period: 60_000,
                 ud_reeval_period: 60_000 * 20,
                 smith_cert_max_by_issuer: 8,
diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs
index 49755e0c49b7b054aa61f21cceed8cd5ca94a649..6f1dbc89d94f221a40058afef6c28a7383bb8188 100644
--- a/runtime/gdev/tests/integration_tests.rs
+++ b/runtime/gdev/tests/integration_tests.rs
@@ -311,7 +311,7 @@ fn test_remove_identity() {
     });
 }
 
-/// test identity is validated when membership is claimed
+/// test identity is "validated" (= membership is claimed) when distance is evaluated positively
 #[test]
 fn test_validate_identity_when_claim() {
     ExtBuilder::new(1, 3, 4)
@@ -346,11 +346,14 @@ fn test_validate_identity_when_claim() {
                 5
             ));
 
+            // eve request distance evaluation for herself
             assert_ok!(Distance::request_distance_evaluation(
                 frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
             ));
 
-            run_to_block(51); // Pass 2 sessions
+            // Pass 2 sessions
+            run_to_block(51);
+            // simulate an evaluation published by smith Alice
             assert_ok!(Distance::force_update_evaluation(
                 frame_system::RawOrigin::Root.into(),
                 AccountKeyring::Alice.to_account_id(),
@@ -358,20 +361,142 @@ fn test_validate_identity_when_claim() {
                     distances: vec![Perbill::one()],
                 }
             ));
-            run_to_block(76); // Pass 1 session
+            run_to_block(75); // Pass 1 session
+            System::assert_has_event(RuntimeEvent::Distance(
+                pallet_distance::Event::EvaluatedValid { idty_index: 5 },
+            ));
+
+            // eve can not claim her membership manually because it is done automatically
+            // the following call does not exist anymore
+            // assert_noop!(
+            //     Membership::claim_membership(
+            //         frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+            //     ),
+            //     pallet_membership::Error::<Runtime>::AlreadyMember
+            // );
+
+            // println!("{:?}", System::events());
+            System::assert_has_event(RuntimeEvent::Membership(
+                pallet_membership::Event::MembershipAdded {
+                    member: 5,
+                    expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
+                },
+            ));
+        });
+}
 
-            // eve can claim her membership
-            assert_ok!(Membership::claim_membership(
+/// test identity creation workflow
+// with distance requested by last certifier
+#[test]
+fn test_identity_creation_workflow() {
+    ExtBuilder::new(1, 3, 4)
+        .with_initial_balances(vec![
+            (AccountKeyring::Charlie.to_account_id(), 10_000), // necessary for evalation distance reserve
+            (AccountKeyring::Eve.to_account_id(), 2_000),
+            (AccountKeyring::Ferdie.to_account_id(), 1_000),
+        ])
+        .build()
+        .execute_with(|| {
+            run_to_block(1);
+            // alice create identity for Eve
+            assert_ok!(Identity::create_identity(
+                frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
+                AccountKeyring::Eve.to_account_id(),
+            ));
+            assert_eq!(
+                Identity::identity(5),
+                Some(pallet_identity::IdtyValue {
+                    data: Default::default(),
+                    next_creatable_identity_on: 0u32,
+                    old_owner_key: None,
+                    owner_key: AccountKeyring::Eve.to_account_id(),
+                    next_scheduled: 1 + 40,
+                    status: pallet_identity::IdtyStatus::Unconfirmed,
+                })
+            );
+            run_to_block(2);
+            // eve confirms her identity
+            assert_ok!(Identity::confirm_identity(
                 frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                "Eeeeeveeeee".into(),
+            ));
+            assert_eq!(
+                Identity::identity(5),
+                Some(pallet_identity::IdtyValue {
+                    data: Default::default(),
+                    next_creatable_identity_on: 0u32,
+                    old_owner_key: None,
+                    owner_key: AccountKeyring::Eve.to_account_id(),
+                    next_scheduled: 2 + 876600,
+                    status: pallet_identity::IdtyStatus::Unvalidated,
+                })
+            );
+            run_to_block(3);
+            // eve gets certified by bob and charlie
+            assert_ok!(Certification::add_cert(
+                frame_system::RawOrigin::Signed(AccountKeyring::Bob.to_account_id()).into(),
+                2,
+                5
             ));
+            assert_ok!(Certification::add_cert(
+                frame_system::RawOrigin::Signed(AccountKeyring::Charlie.to_account_id()).into(),
+                3,
+                5
+            ));
+            // charlie also request distance evaluation for eve
+            // (could be done in batch)
+            assert_ok!(Distance::request_distance_evaluation_for(
+                frame_system::RawOrigin::Signed(AccountKeyring::Charlie.to_account_id()).into(),
+                5
+            ));
+            // then the evaluation is pending
+            assert_eq!(
+                Distance::pending_evaluation_request(5),
+                Some(AccountKeyring::Charlie.to_account_id(),)
+            );
 
+            // Pass 2 sessions
+            run_to_block(51);
+            // simulate evaluation published by smith Alice
+            assert_ok!(Distance::force_update_evaluation(
+                frame_system::RawOrigin::Root.into(),
+                AccountKeyring::Alice.to_account_id(),
+                pallet_distance::ComputationResult {
+                    distances: vec![Perbill::one()],
+                }
+            ));
+            // Pass 1 session
+            run_to_block(75);
+
+            // eve should not even have to claim her membership
             System::assert_has_event(RuntimeEvent::Membership(
                 pallet_membership::Event::MembershipAdded {
                     member: 5,
-                    expire_on: 76 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
+                    expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
                 },
             ));
-            // not possible anymore to validate identity of someone else
+
+            // test state coherence
+            assert_eq!(
+                Identity::identity(5),
+                Some(pallet_identity::IdtyValue {
+                    data: IdtyData {
+                        // ud creation period is 60_000 ms ~ 10 blocks
+                        // block time is 6_000 ms
+                        // first ud is at 24_000 ms ~ 4 blocks
+                        // at block 75 this should be the UD number 7, so next is 8 not 9
+                        // FIXME wrong UD count
+                        first_eligible_ud: pallet_universal_dividend::FirstEligibleUd(Some(
+                            sp_std::num::NonZeroU16::new(9).unwrap()
+                        ))
+                    },
+                    next_creatable_identity_on: 0u32,
+                    old_owner_key: None,
+                    owner_key: AccountKeyring::Eve.to_account_id(),
+                    next_scheduled: 0,
+                    status: pallet_identity::IdtyStatus::Member,
+                })
+            );
         });
 }
 
@@ -424,6 +549,16 @@ fn test_membership_renewal() {
         .with_initial_balances(vec![(AccountKeyring::Alice.to_account_id(), 2000)])
         .build()
         .execute_with(|| {
+            // can not renew membership immediately
+            assert_noop!(
+                Distance::request_distance_evaluation(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
+                ),
+                pallet_duniter_wot::Error::<Runtime>::MembershipRenewalPeriodNotRespected,
+            );
+
+            // but ok after waiting 10 blocks delay
+            run_to_block(11);
             assert_ok!(Distance::request_distance_evaluation(
                 frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
             ));
@@ -436,33 +571,28 @@ fn test_membership_renewal() {
                     distances: vec![Perbill::one()],
                 }
             ));
-            run_to_block(76); // Pass 1 session
-
-            // renew at block 76
-            assert_ok!(Membership::renew_membership(
-                frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
-            ));
+            // Pass 1 session, membership is renewed automatically
+            run_to_block(75);
             System::assert_has_event(RuntimeEvent::Membership(
-                pallet_membership::Event::MembershipAdded {
+                pallet_membership::Event::MembershipRenewed {
                     member: 1,
-                    expire_on: 76 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
+                    expire_on: 75 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
                 },
             ));
 
-            // renew at block 77
-            run_to_block(77);
-            assert_ok!(Membership::renew_membership(
-                frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
-            ));
-            System::assert_has_event(RuntimeEvent::Membership(
-                pallet_membership::Event::MembershipAdded {
-                    member: 1,
-                    expire_on: 77 + <Runtime as pallet_membership::Config>::MembershipPeriod::get(),
-                },
-            ));
+            run_to_block(76);
+            // not possible to renew manually
+            // can not ask renewal when period is not respected
+            // TODO check that it is possible again within the right delay
+            assert_noop!(
+                Distance::request_distance_evaluation(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
+                ),
+                pallet_duniter_wot::Error::<Runtime>::MembershipRenewalPeriodNotRespected,
+            );
 
-            // should expire at block 177 = 77+100
-            run_to_block(177);
+            // should expire at block 175 = 75+100
+            run_to_block(175);
             System::assert_has_event(RuntimeEvent::Membership(
                 pallet_membership::Event::MembershipRemoved {
                     member: 1,
@@ -583,18 +713,19 @@ fn test_ud_claimed_membership_on_and_off() {
             },
         ));
 
-        // alice claims back her membership
-        assert_ok!(Distance::force_set_distance_status(
+        // alice claims back her membership through distance evaluation
+        assert_ok!(Distance::force_valid_distance_status(
             frame_system::RawOrigin::Root.into(),
             1,
-            Some((
-                AccountKeyring::Alice.to_account_id(),
-                pallet_distance::DistanceStatus::Valid
-            ))
-        ));
-        assert_ok!(Membership::claim_membership(
-            frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into()
         ));
+        // it can not be done manually
+        // because the call does not exist anymore
+        // assert_noop!(
+        //     Membership::claim_membership(
+        //         frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
+        //     ),
+        //     pallet_membership::Error::<Runtime>::AlreadyMember
+        // );
         System::assert_has_event(RuntimeEvent::Membership(
             pallet_membership::Event::MembershipAdded {
                 member: 1,
@@ -963,12 +1094,22 @@ fn test_create_new_idty_without_founds() {
         .build()
         .execute_with(|| {
             run_to_block(2);
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Eve.to_account_id()),
+                0
+            );
 
             // Should be able to create an identity without founds
             assert_ok!(Identity::create_identity(
                 frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
                 AccountKeyring::Eve.to_account_id(),
             ));
+            System::assert_has_event(RuntimeEvent::Identity(
+                pallet_identity::Event::IdtyCreated {
+                    idty_index: 5,
+                    owner_key: AccountKeyring::Eve.to_account_id(),
+                },
+            ));
 
             // At next block, nothing should be preleved
             run_to_block(3);
@@ -1029,24 +1170,26 @@ fn test_validate_new_idty_after_few_uds() {
                 pallet_identity::IdtyName::from("Eve"),
             ));
 
-            // At next block, Bob should be able to certify and validate the new identity
+            // At next block, Bob should be able to certify the new identity
             run_to_block(23);
             assert_ok!(Certification::add_cert(
                 frame_system::RawOrigin::Signed(AccountKeyring::Bob.to_account_id()).into(),
                 2,
                 5,
             ));
-            assert_ok!(Distance::force_set_distance_status(
+            // valid distance status should trigger identity validation
+            assert_ok!(Distance::force_valid_distance_status(
                 frame_system::RawOrigin::Root.into(),
                 5,
-                Some((
-                    AccountKeyring::Bob.to_account_id(),
-                    pallet_distance::DistanceStatus::Valid
-                ))
-            ));
-            assert_ok!(Membership::claim_membership(
-                frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
             ));
+            // and it is not possible to call it manually
+            // because the call does not exist anymore
+            // assert_noop!(
+            //     Membership::claim_membership(
+            //         frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+            //     ),
+            //     pallet_membership::Error::<Runtime>::AlreadyMember
+            // );
 
             // The new member should have first_eligible_ud equal to three
             assert!(Identity::identity(5).is_some());
@@ -1095,18 +1238,19 @@ fn test_claim_memberhsip_after_few_uds() {
                 5,
             ));
 
-            // eve should be able to claim her membership
-            assert_ok!(Distance::force_set_distance_status(
+            // eve membership should be able to be claimed through distance evaluation
+            assert_ok!(Distance::force_valid_distance_status(
                 frame_system::RawOrigin::Root.into(),
                 5,
-                Some((
-                    AccountKeyring::Eve.to_account_id(),
-                    pallet_distance::DistanceStatus::Valid
-                ))
-            ));
-            assert_ok!(Membership::claim_membership(
-                frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
             ));
+            // but not manually
+            // because the call does not exist
+            // assert_noop!(
+            //     Membership::claim_membership(
+            //         frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+            //     ),
+            //     pallet_membership::Error::<Runtime>::AlreadyMember
+            // );
 
             // The new member should have first_eligible_ud equal to three
             assert!(Identity::identity(5).is_some());
@@ -1451,16 +1595,6 @@ fn test_new_account_linked() {
         );
     })
 }
-#[test]
-#[ignore = "what was this test supposed to do?"]
-fn smith_data_problem() {
-    ExtBuilder::new(1, 3, 4)
-        .change_parameters(|_parameters| {})
-        .build()
-        .execute_with(|| {
-            run_to_block(4);
-        });
-}
 
 /// test killed account
 // The only way to kill an account is to kill  the identity
diff --git a/runtime/gdev/tests/xt_tests.rs b/runtime/gdev/tests/xt_tests.rs
index 1663d6221475aaea797b388399d8f0d1364aeeca..5caf3a87e26d69a6514446862cf9c86a7a7b55d2 100644
--- a/runtime/gdev/tests/xt_tests.rs
+++ b/runtime/gdev/tests/xt_tests.rs
@@ -15,6 +15,7 @@
 // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
 
 // these integration tests aim to test fees and extrinsic-related externalities
+// they do not work with runtim-benchmark because it has a different fees model
 
 mod common;
 
diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs
index a4b6e010a2133efd043cbb7a05ae5f6dd9fa3e13..7fe0cfeba60b0c77c6746d931b272e13e582f3d3 100644
--- a/runtime/gtest/src/lib.rs
+++ b/runtime/gtest/src/lib.rs
@@ -292,7 +292,7 @@ construct_runtime!(
         // Web Of Trust
         Wot: pallet_duniter_wot::{Pallet} = 40,
         Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41,
-        Membership: pallet_membership::{Pallet, Call, Config<T>, Storage, Event<T>} = 42,
+        Membership: pallet_membership::{Pallet, Config<T>, Storage, Event<T>} = 42,
         Certification: pallet_certification::{Pallet, Call, Config<T>, Storage, Event<T>} = 43,
         Distance: pallet_distance::{Pallet, Call, Storage, Inherent, Event<T>} = 44,
 
diff --git a/runtime/gtest/src/parameters.rs b/runtime/gtest/src/parameters.rs
index 252ae211a2571ead87fa67ab4d13f64b98ae8c88..c7731b0a9950fcee4ae933a7c43438949d980b29 100644
--- a/runtime/gtest/src/parameters.rs
+++ b/runtime/gtest/src/parameters.rs
@@ -107,7 +107,7 @@ parameter_types! {
 // Membership
 parameter_types! {
     pub const MembershipPeriod: BlockNumber = 73 * DAYS;
-    pub const PendingMembershipPeriod: BlockNumber = 12 * DAYS;
+    pub const MembershipRenewalPeriod: BlockNumber = 56 * DAYS;
 }
 
 // Certification