From 68285b5e8cae00491b87c6b2b527b88a21b1f544 Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Mon, 18 Dec 2023 12:35:21 +0100
Subject: [PATCH] automatic membership after distance eval

add more details to idty creation test

cargo check ok

add integration test and fix behavior

fix benchmarks for renaming

cargo check ok

cargo test ok

add certification count check

update benchmarks

update distance setup handler

fix weight info

update metadata and generated doc

fix cucumber

distance pallet unit test stub

update metadata and generate doc after rebase

make membership claim no-op an error

update metadata with new error

wip

add comments

add membership renewal antispam

and clean up pending membership parameters

test it

not compatible with keeping distance result

change renewal antispam

metadata

fix param for other runtimes

fix e2e test

remove membership calls

adjust integration tests accordingly

update metadata

fix cucumber

clean up benchmarks and other tests

wip
---
 docs/api/runtime-calls.md                     |  90 ++----
 docs/api/runtime-errors.md                    |  63 +++-
 docs/api/runtime-events.md                    |  28 +-
 .../identity_creation.feature                 |   2 -
 end2end-tests/cucumber-genesis/default.json   |  14 +-
 end2end-tests/cucumber-genesis/wot.json       |  14 +-
 end2end-tests/tests/common/membership.rs      |  25 +-
 end2end-tests/tests/cucumber_tests.rs         |  35 ---
 node/src/chain_spec/gdev.rs                   |   5 +-
 node/src/chain_spec/gen_genesis_data.rs       |  21 +-
 node/src/chain_spec/gtest.rs                  |   2 +-
 pallets/distance/src/benchmarking.rs          |  54 ++--
 pallets/distance/src/lib.rs                   | 292 ++++++++++--------
 pallets/distance/src/mock.rs                  |  16 +-
 pallets/distance/src/tests.rs                 |  41 +++
 pallets/distance/src/traits.rs                |  24 +-
 pallets/distance/src/weights.rs               | 116 +++----
 pallets/duniter-test-parameters/src/lib.rs    |   2 +-
 pallets/duniter-wot/src/lib.rs                | 123 +++++++-
 pallets/duniter-wot/src/mock.rs               |   5 +-
 pallets/duniter-wot/src/tests.rs              |  46 +--
 pallets/duniter-wot/src/traits.rs             |  14 -
 pallets/membership/src/benchmarking.rs        |  44 +--
 pallets/membership/src/lib.rs                 | 145 +++++----
 pallets/membership/src/mock.rs                |  11 +-
 pallets/membership/src/tests.rs               |  72 ++---
 pallets/universal-dividend/src/lib.rs         |   6 +
 resources/gdev.yaml                           |   7 +-
 resources/metadata.scale                      | Bin 129557 -> 129125 bytes
 runtime/common/src/pallets_config.rs          |   5 +-
 runtime/common/src/providers.rs               |  36 +--
 runtime/common/src/weights/pallet_distance.rs | 122 +++++---
 runtime/g1/src/lib.rs                         |   5 +-
 runtime/g1/src/parameters.rs                  |   2 +-
 runtime/gdev/src/lib.rs                       |  10 +-
 runtime/gdev/src/parameters.rs                |   5 +
 runtime/gdev/tests/common/mod.rs              |   2 +-
 runtime/gdev/tests/integration_tests.rs       | 264 ++++++++++++----
 runtime/gdev/tests/xt_tests.rs                |   1 +
 runtime/gtest/src/lib.rs                      |   2 +-
 runtime/gtest/src/parameters.rs               |   2 +-
 41 files changed, 1028 insertions(+), 745 deletions(-)
 create mode 100644 pallets/distance/src/tests.rs

diff --git a/docs/api/runtime-calls.md b/docs/api/runtime-calls.md
index 033203a40..3c96e8358 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 ebb10eb1a..03b5c31fb 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 9aa809263..c495670a1 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 f8bffeacc..7de97f182 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 245e91e31..cfd66ba93 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 6b1fad670..813fc6fc1 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 d800f3387..c7b3fa56c 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 7832a4a3d..48d7c30be 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 e927d483b..a66be70d2 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 56481245e..2565f3d4a 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 d01ee0cce..e7814f6d0 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 33ba9419b..c762bde19 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 d7d131cd8..b7ee0e214 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 ef477a8c8..6861b3c43 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 000000000..b1be3c04a
--- /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 de9d532d6..6849d9c25 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 b0808361a..0e8f34c45 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 4da26974b..55ca1c612 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 eb127c0cc..079467a3f 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 c043ef764..d2679e1b9 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 2b86ee97f..fc4f4df0e 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 6934ce454..f3474822b 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 1bdf5edf6..84216da89 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 cba39e37e..38f1c491c 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 5173ff716..a13325a0c 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 53da60336..4f22c3213 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 25de8fb02..be744570e 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 a90145753..fcfd98034 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
GIT binary patch
delta 8034
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
Or!Y#g3bOXFvH}25ss%d$

delta 8275
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

diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs
index 6bae43eb5..d0ce866c0 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 ca631988e..504cf92f8 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 27aa94475..c1c3c8c55 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 40a3025ac..8c9bca67c 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 22ef4efa4..adfc0183a 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 04366d5db..d64153d64 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 7dc4e8177..abd164fe6 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 261755f75..0ca8aa74c 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 49755e0c4..6f1dbc89d 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 1663d6221..5caf3a87e 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 a4b6e010a..7fe0cfeba 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 252ae211a..c7731b0a9 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
-- 
GitLab