diff --git a/.gitignore b/.gitignore
index 819eefefc43c6c0ef26aa4f9c4588e7819ed1081..97a264558d9f4232baf1f9c2bf6b39fedf8e766c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 **/*.rs.bk
 *.db
 *.wot
+.project
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c289d5ad60777028afc38aa70b28abe33a35301b..ada7a6f75247bf36b0008d26c77f596a094ace09 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,6 +12,7 @@ variables:
   CARGO_HOME: $CI_PROJECT_DIR/cargo
 
 .rust_stable_lin64: &rust_stable_lin64
+  image: registry.duniter.org/docker/duniter-rs-ci:v0.0.6
   tags:
     - redshift-rs-stable
   before_script:
@@ -36,14 +37,14 @@ variables:
     - rustc --version && cargo --version
 
 .rust_stable_armv7: &rust_stable_armv7
-  image: registry.duniter.org/docker/rust/armv7-builder:v0.0.3
+  image: registry.duniter.org/docker/rust/armv7-builder:v0.0.4
   tags:
     - redshift-rs-stable
   before_script:
     - rustc --version && cargo --version
 
 .rust_stable_win64: &rust_stable_win64
-  image: registry.duniter.org/docker/rust/win64-builder:v0.0.4
+  image: registry.duniter.org/docker/rust/win64-builder:v0.0.7
   tags:
     - redshift-rs-stable
   before_script:
diff --git a/Cargo.lock b/Cargo.lock
index 9d5feab3c0c49df44bdc1e7d604f4be71e507561..d36ea327c769176277a2048a22c802886ab28153 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,7 +73,7 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -166,6 +166,11 @@ dependencies = [
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "dirs"
 version = "1.0.2"
@@ -198,7 +203,7 @@ dependencies = [
  "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustbreak 2.0.0-rc2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -211,9 +216,10 @@ version = "0.1.0-a0.1"
 dependencies = [
  "dirs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "duniter-crypto 0.2.0-a0.1",
+ "duniter-documents 0.8.0-a0.1",
  "duniter-module 0.1.0-a0.1",
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -236,7 +242,7 @@ dependencies = [
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "simplelog 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -250,8 +256,10 @@ version = "0.2.0-a0.1"
 dependencies = [
  "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -270,7 +278,7 @@ dependencies = [
  "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustbreak 2.0.0-rc2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -281,12 +289,13 @@ version = "0.8.0-a0.1"
 dependencies = [
  "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "duniter-crypto 0.2.0-a0.1",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -299,7 +308,7 @@ dependencies = [
  "duniter-documents 0.8.0-a0.1",
  "duniter-module 0.1.0-a0.1",
  "duniter-network 0.1.0-a0.1",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -310,7 +319,7 @@ version = "0.1.0-a0.1"
 dependencies = [
  "duniter-crypto 0.2.0-a0.1",
  "duniter-documents 0.8.0-a0.1",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -319,13 +328,17 @@ dependencies = [
 name = "duniter-network"
 version = "0.1.0-a0.1"
 dependencies = [
+ "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "duniter-crypto 0.2.0-a0.1",
  "duniter-documents 0.8.0-a0.1",
  "duniter-module 0.1.0-a0.1",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -342,7 +355,7 @@ dependencies = [
  "duniter-module 0.1.0-a0.1",
  "duniter-network 0.1.0-a0.1",
  "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -355,14 +368,58 @@ dependencies = [
  "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "duniter-ws2p"
+name = "durs"
+version = "0.1.0-a0.1"
+dependencies = [
+ "duniter-core 0.1.0-a0.1",
+ "duniter-tui 0.1.0-a0.1",
+ "durs-ws2p 0.1.0-a0.1",
+ "durs-ws2p-v1-legacy 0.1.0-a0.1",
+]
+
+[[package]]
+name = "durs-ws2p"
 version = "0.1.0-a0.1"
 dependencies = [
+ "duniter-conf 0.1.0-a0.1",
+ "duniter-crypto 0.2.0-a0.1",
+ "duniter-message 0.1.0-a0.1",
+ "duniter-module 0.1.0-a0.1",
+ "duniter-network 0.1.0-a0.1",
+ "durs-ws2p-messages 0.1.0-a0.1",
+ "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "durs-ws2p-messages"
+version = "0.1.0-a0.1"
+dependencies = [
+ "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "duniter-crypto 0.2.0-a0.1",
+ "duniter-documents 0.8.0-a0.1",
+ "duniter-network 0.1.0-a0.1",
+ "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "durs-ws2p-v1-legacy"
+version = "0.1.0-a0.1"
+dependencies = [
+ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "duniter-conf 0.1.0-a0.1",
  "duniter-crypto 0.2.0-a0.1",
  "duniter-dal 0.1.0-a0.1",
@@ -376,22 +433,13 @@ dependencies = [
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "ws 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "durs"
-version = "0.1.0-a0.1"
-dependencies = [
- "duniter-core 0.1.0-a0.1",
- "duniter-tui 0.1.0-a0.1",
- "duniter-ws2p 0.1.0-a0.1",
-]
-
 [[package]]
 name = "either"
 version = "1.5.0"
@@ -661,6 +709,15 @@ name = "pkg-config"
 version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "pretty_assertions"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "0.4.6"
@@ -796,7 +853,7 @@ dependencies = [
  "base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -821,7 +878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
-version = "1.0.66"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -841,7 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1146,6 +1203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
 "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
 "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
+"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 "checksum dirs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "37a76dd8b997af7107d0bb69d43903cf37153a18266f8b3fdb9911f28efb5444"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
@@ -1183,6 +1241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
 "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
+"checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6"
 "checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
 "checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
@@ -1202,7 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
-"checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95"
+"checksum serde 1.0.78 (registry+https://github.com/rust-lang/crates.io-index)" = "92ec94e2754699adddbbc4f555791bd3acc2a2f5574cba16c93a4a9cf4a04415"
 "checksum serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79"
 "checksum serde_json 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "eb40600c756f02d7ea34943626cefa85732fdae5f95b90b31f9797b3c526d1e6"
 "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
diff --git a/Cargo.toml b/Cargo.toml
index 905e909efa42949396f84eee7c127690aefa7dba..8e8d7b437b3f4ff8074a889a640bc17f4c31e09f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,11 +8,12 @@ license = "AGPL-3.0"
 [dependencies]
 duniter-core = { path = "./core" }
 duniter-tui = { path = "./tui", optional = true }
-duniter-ws2p = { path = "./ws2p" }
+durs-ws2p = { path = "./ws2p" }
+durs-ws2p-v1-legacy = { path = "./ws2p-v1-legacy" }
 
 [features]
 default = ["tui","ssl"]
-ssl = ["duniter-ws2p/ssl"]
+ssl = ["durs-ws2p-v1-legacy/ssl"]
 tui = ["duniter-tui"]
 # Treat warnings as a build error.
 strict = []
@@ -30,5 +31,7 @@ members = [
     "network",
     "tui",
     "wotb",
-    "ws2p"
+    "ws2p",
+    "ws2p-messages",
+    "ws2p-v1-legacy"
 ]
diff --git a/blockchain/clippy.toml b/blockchain/clippy.toml
index 16934920c7fa627dc091b94ed08687bea1afe437..e2f58873aedd07c86ef471451ced9f64f5e95c9a 100644
--- a/blockchain/clippy.toml
+++ b/blockchain/clippy.toml
@@ -1 +1 @@
-cyclomatic-complexity-threshold = 38
\ No newline at end of file
+cyclomatic-complexity-threshold = 41
\ No newline at end of file
diff --git a/blockchain/lib.rs b/blockchain/lib.rs
index 33df82665db46a718fcccdb22be5ac9692543c95..8bf1fc90f9d549f8b1cc3c6242d019277fab2c0c 100644
--- a/blockchain/lib.rs
+++ b/blockchain/lib.rs
@@ -16,10 +16,7 @@
 //! Module managing the Duniter blockchain.
 
 #![cfg_attr(feature = "strict", deny(warnings))]
-#![cfg_attr(
-    feature = "cargo-clippy",
-    allow(unused_collect, duration_subsec)
-)]
+#![cfg_attr(feature = "cargo-clippy", allow(duration_subsec))]
 #![deny(
     missing_docs,
     missing_debug_implementations,
@@ -102,7 +99,7 @@ pub struct BlockchainModule {
     /// Name of the user datas profile
     pub profile: String,
     /// Currency
-    pub currency: Currency,
+    pub currency: CurrencyName,
     // Currency parameters
     currency_params: CurrencyParameters,
     /// Wots Databases
@@ -330,24 +327,18 @@ impl BlockchainModule {
                         Ok(ValidBlockApplyReqs(block_req, wot_dbs_reqs, currency_dbs_reqs)) => {
                             let block_doc = network_block.uncompleted_block_doc().clone();
                             // Apply wot dbs requests
-                            wot_dbs_reqs
-                                .iter()
-                                .map(|req| {
-                                    req.apply(&self.wot_databases, &self.currency_params)
-                                            .expect(
-                                            "Fatal error : fail to apply WotsDBsWriteQuery : DALError !",
-                                        )
-                                })
-                                .collect::<()>();
+                            for req in &wot_dbs_reqs {
+                                req.apply(&self.wot_databases, &self.currency_params)
+                                    .expect(
+                                    "Fatal error : fail to apply WotsDBsWriteQuery : DALError !",
+                                );
+                            }
                             // Apply currency dbs requests
-                            currency_dbs_reqs
-                                .iter()
-                                .map(|req| {
-                                    req.apply(&self.currency_databases).expect(
-                                            "Fatal error : fail to apply CurrencyDBsWriteQuery : DALError !",
-                                        )
-                                })
-                                .collect::<()>();
+                            for req in currency_dbs_reqs {
+                                req.apply(&self.currency_databases).expect(
+                                    "Fatal error : fail to apply CurrencyDBsWriteQuery : DALError !",
+                                );
+                            }
                             // Write block
                             block_req.apply(&self.blocks_databases, false).expect(
                                 "Fatal error : fail to write block in BlocksDBs : DALError !",
@@ -477,18 +468,16 @@ impl BlockchainModule {
                 bc_db_query
                     .apply(&self.blocks_databases, false)
                     .expect("Fatal error : Fail to apply DBWriteRequest !");
-                wot_dbs_queries
-                    .iter()
-                    .map(|req| {
-                        req.apply(&self.wot_databases, &self.currency_params)
-                            .expect("Fatal error : Fail to apply WotsDBsWriteRequest !");
-                    }).collect::<()>();
-                tx_dbs_queries
-                    .iter()
-                    .map(|req| {
-                        req.apply(&self.currency_databases)
-                            .expect("Fatal error : Fail to apply CurrencyDBsWriteRequest !");
-                    }).collect::<()>();
+                for query in &wot_dbs_queries {
+                    query
+                        .apply(&self.wot_databases, &self.currency_params)
+                        .expect("Fatal error : Fail to apply WotsDBsWriteRequest !");
+                }
+                for query in &tx_dbs_queries {
+                    query
+                        .apply(&self.currency_databases)
+                        .expect("Fatal error : Fail to apply CurrencyDBsWriteRequest !");
+                }
                 save_blocks_dbs = true;
                 if !wot_dbs_queries.is_empty() {
                     save_wots_dbs = true;
@@ -757,21 +746,18 @@ impl BlockchainModule {
                                 bc_db_query
                                     .apply(&self.blocks_databases, false)
                                     .expect("Fatal error : Fail to apply DBWriteRequest !");
-                                wot_dbs_queries
-                                    .iter()
-                                    .map(|req| {
-                                        req.apply(&self.wot_databases, &self.currency_params)
-                                            .expect(
-                                                "Fatal error : Fail to apply WotsDBsWriteRequest !",
-                                            );
-                                    }).collect::<()>();
-                                tx_dbs_queries
-                                    .iter()
-                                    .map(|req| {
-                                        req.apply(&self.currency_databases).expect(
-                                            "Fatal error : Fail to apply CurrencyDBsWriteRequest !",
+                                for query in &wot_dbs_queries {
+                                    query
+                                        .apply(&self.wot_databases, &self.currency_params)
+                                        .expect(
+                                            "Fatal error : Fail to apply WotsDBsWriteRequest !",
                                         );
-                                    }).collect::<()>();
+                                }
+                                for query in &tx_dbs_queries {
+                                    query.apply(&self.currency_databases).expect(
+                                        "Fatal error : Fail to apply CurrencyDBsWriteRequest !",
+                                    );
+                                }
                                 // Save databases
                                 self.blocks_databases.save_dbs();
                                 if !wot_dbs_queries.is_empty() {
@@ -789,10 +775,7 @@ impl BlockchainModule {
                                 find_valid_block = true;
                                 break;
                             } else {
-                                warn!(
-                                    "DEBUG: fail to stackable_block({})",
-                                    stackable_block.block.number
-                                );
+                                warn!("fail to stackable_block({})", stackable_block.block.number);
                                 // Delete this fork
                                 DALBlock::delete_fork(
                                     &self.blocks_databases.forks_db,
diff --git a/blockchain/sync.rs b/blockchain/sync.rs
index 655227f2179627b93ccc7bd96c29c5eb420e1d19..5fa7e31299119eb3e793738fc97f7a1a436ed07d 100644
--- a/blockchain/sync.rs
+++ b/blockchain/sync.rs
@@ -20,11 +20,12 @@ extern crate threadpool;
 
 use self::pbr::ProgressBar;
 use self::threadpool::ThreadPool;
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
 use duniter_dal::currency_params::CurrencyParameters;
 use duniter_dal::writers::requests::*;
 use duniter_dal::ForkId;
-use duniter_documents::{BlockHash, BlockId, Hash};
+use duniter_documents::{BlockHash, BlockId};
 use duniter_network::NetworkBlock;
 use duniter_wotb::NodeId;
 use std::collections::{HashMap, VecDeque};
@@ -50,7 +51,7 @@ pub struct BlockHeader {
 #[derive(Debug)]
 /// Message for main sync thread
 enum MessForSyncThread {
-    Target(Currency, Blockstamp),
+    Target(CurrencyName, Blockstamp),
     NetworkBlock(NetworkBlock),
     DownloadFinish(),
     ApplyFinish(),
@@ -137,7 +138,7 @@ pub fn sync_ts<DC: DuniterConf>(
                     ).expect("Fail to parse current ts blockstamp !"),
                 );
                 (
-                    Currency::Str(String::from(
+                    CurrencyName(String::from(
                         row[2]
                             .as_string()
                             .expect("Fatal error :Fail to get currency !"),
@@ -550,42 +551,41 @@ pub fn sync_ts<DC: DuniterConf>(
                     "Fail to communicate with blocks worker thread, please reset data & resync !",
                 );
             // Send wot requests to wot worker thread
-            wot_db_reqs
-                .iter()
-                .map(|req| {
-                    if let WotsDBsWriteQuery::CreateCert(
-                        ref _source_pubkey,
-                        ref source,
-                        ref target,
-                        ref created_block_id,
-                        ref _median_time,
-                    ) = req
-                    {
-                        certs_count += 1;
-                        // Add cert in certs_db
-                        certs_db
-                            .write(|db| {
-                                let mut created_certs =
-                                    db.get(&created_block_id).cloned().unwrap_or_default();
-                                created_certs.insert((*source, *target));
-                                db.insert(*created_block_id, created_certs);
-                            })
-                            .expect("RustBreakError : please reset data and resync !");
-                    }
-                    sender_wot_thread
-                        .send(SyncJobsMess::WotsDBsWriteQuery(req.clone(), Box::new(currency_params)))
-                        .expect("Fail to communicate with tx worker thread, please reset data & resync !")
-                })
-                .collect::<()>();
+            for req in wot_db_reqs {
+                if let WotsDBsWriteQuery::CreateCert(
+                    ref _source_pubkey,
+                    ref source,
+                    ref target,
+                    ref created_block_id,
+                    ref _median_time,
+                ) = req
+                {
+                    certs_count += 1;
+                    // Add cert in certs_db
+                    certs_db
+                        .write(|db| {
+                            let mut created_certs =
+                                db.get(&created_block_id).cloned().unwrap_or_default();
+                            created_certs.insert((*source, *target));
+                            db.insert(*created_block_id, created_certs);
+                        }).expect("RustBreakError : please reset data and resync !");
+                }
+                sender_wot_thread
+                    .send(SyncJobsMess::WotsDBsWriteQuery(
+                        req.clone(),
+                        Box::new(currency_params),
+                    )).expect(
+                        "Fail to communicate with tx worker thread, please reset data & resync !",
+                    )
+            }
             // Send blocks and wot requests to wot worker thread
-            currency_db_reqs
-                .iter()
-                .map(|req| {
-                    sender_tx_thread
-                        .send(SyncJobsMess::CurrencyDBsWriteQuery(req.clone()))
-                        .expect("Fail to communicate with tx worker thread, please reset data & resync !")
-                })
-                .collect::<()>();
+            for req in currency_db_reqs {
+                sender_tx_thread
+                    .send(SyncJobsMess::CurrencyDBsWriteQuery(req.clone()))
+                    .expect(
+                        "Fail to communicate with tx worker thread, please reset data & resync !",
+                    );
+            }
             debug!("Success to apply block #{}", current_blockstamp.id.0);
             if current_blockstamp.id.0 >= target_blockstamp.id.0 {
                 if current_blockstamp == target_blockstamp {
diff --git a/blockchain/ts_parsers.rs b/blockchain/ts_parsers.rs
index 030b96e7e9a677c4d9758d40f48bd3c1b4ba2994..27782b1cbcbe1db3d066d8a8bc6f9ce5fb2376db 100644
--- a/blockchain/ts_parsers.rs
+++ b/blockchain/ts_parsers.rs
@@ -16,16 +16,16 @@
 extern crate serde_json;
 extern crate sqlite;
 
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
-use duniter_documents::blockchain::v10::documents::block::{
-    BlockV10Parameters, CurrencyName, TxDocOrTxHash,
-};
+use duniter_documents::blockchain::v10::documents::block::{BlockV10Parameters, TxDocOrTxHash};
 use duniter_documents::blockchain::v10::documents::identity::IdentityDocumentBuilder;
 use duniter_documents::blockchain::v10::documents::membership::*;
 use duniter_documents::blockchain::v10::documents::transaction::*;
 use duniter_documents::blockchain::v10::documents::*;
 use duniter_documents::blockchain::DocumentBuilder;
-use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use duniter_documents::CurrencyName;
+use duniter_documents::{BlockHash, BlockId, Blockstamp};
 use duniter_network::{NetworkBlock, NetworkBlockV10};
 use std::str::FromStr;
 use sync::BlockHeader;
diff --git a/conf/Cargo.toml b/conf/Cargo.toml
index 12e9ed3462894553001895bd11fcf65c8023d663..d11af79ae5461f8474faa574ae3a87b67b470841 100644
--- a/conf/Cargo.toml
+++ b/conf/Cargo.toml
@@ -15,6 +15,7 @@ serde_derive = "1.0.*"
 serde_json = "1.0.*"
 dirs = "1.0.2"
 duniter-crypto = { path = "../crypto" }
+duniter-documents = { path = "../documents" }
 duniter-module = { path = "../module" }
 
 [features]
diff --git a/conf/lib.rs b/conf/lib.rs
index 7ebf86f278e7d2eb19d43fd89d21dc18b257d291..c75889d29c18fcc4935722e5373c53c81800a859 100644
--- a/conf/lib.rs
+++ b/conf/lib.rs
@@ -36,11 +36,13 @@ extern crate serde_json;
 
 extern crate dirs;
 extern crate duniter_crypto;
+extern crate duniter_documents;
 extern crate duniter_module;
 extern crate rand;
 extern crate serde;
 use duniter_crypto::keys::*;
-use duniter_module::{Currency, DuniterConf, ModuleId, RequiredKeys, RequiredKeysContent};
+use duniter_documents::CurrencyName;
+use duniter_module::{DuniterConf, ModuleId, RequiredKeys, RequiredKeysContent};
 use rand::Rng;
 use serde::ser::{Serialize, SerializeStruct, Serializer};
 use std::collections::HashSet;
@@ -58,7 +60,7 @@ pub static DEFAULT_CURRRENCY: &'static str = "g1";
 /// User request on global conf
 pub enum ChangeGlobalConf {
     /// Change currency
-    ChangeCurrency(Currency),
+    ChangeCurrency(CurrencyName),
     /// Disable module
     DisableModule(ModuleId),
     /// Enable module
@@ -70,8 +72,8 @@ pub enum ChangeGlobalConf {
 #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
 /// Duniter configuration v1
 pub struct DuRsConfV1 {
-    /// Currency
-    pub currency: Currency,
+    /// Currency name
+    pub currency: CurrencyName,
     /// Duniter node unique identifier
     pub my_node_id: u32,
     /// Configuration of modules in json format (obtained from the conf.json file)
@@ -85,7 +87,7 @@ pub struct DuRsConfV1 {
 impl Default for DuRsConfV1 {
     fn default() -> Self {
         DuRsConfV1 {
-            currency: Currency::Str(String::from("g1")),
+            currency: CurrencyName(String::from("g1")),
             my_node_id: generate_random_node_id(),
             modules: serde_json::Value::Null,
             disabled: HashSet::with_capacity(0),
@@ -116,13 +118,13 @@ impl DuniterConf for DuRsConf {
             _ => panic!("Fail to load duniter conf : conf version not supported !"),
         }
     }
-    fn currency(&self) -> Currency {
+    fn currency(&self) -> CurrencyName {
         match *self {
             DuRsConf::V1(ref conf_v1) => conf_v1.currency.clone(),
             _ => panic!("Fail to load duniter conf : conf version not supported !"),
         }
     }
-    fn set_currency(&mut self, new_currency: Currency) {
+    fn set_currency(&mut self, new_currency: CurrencyName) {
         match *self {
             DuRsConf::V1(ref mut conf_v1) => conf_v1.currency = new_currency,
             _ => panic!("Fail to load duniter conf : conf version not supported !"),
@@ -265,7 +267,7 @@ pub fn get_user_datas_folder() -> &'static str {
 }
 
 /// Returns the path to the folder containing the currency datas of the running profile
-pub fn datas_path(profile: &str, currency: &Currency) -> PathBuf {
+pub fn datas_path(profile: &str, currency: &CurrencyName) -> PathBuf {
     let mut datas_path = get_profile_path(profile);
     datas_path.push(currency.to_string());
     if !datas_path.as_path().exists() {
@@ -416,7 +418,7 @@ pub fn write_conf_file<DC: DuniterConf>(profile: &str, conf: &DC) -> Result<(),
 }
 
 /// Returns the path to the database containing the blockchain
-pub fn get_blockchain_db_path(profile: &str, currency: &Currency) -> PathBuf {
+pub fn get_blockchain_db_path(profile: &str, currency: &CurrencyName) -> PathBuf {
     let mut db_path = datas_path(profile, &currency);
     db_path.push("blockchain/");
     if !db_path.as_path().exists() {
@@ -426,7 +428,7 @@ pub fn get_blockchain_db_path(profile: &str, currency: &Currency) -> PathBuf {
 }
 
 /// Returns the path to the binary file containing the state of the web of trust
-pub fn get_wot_path(profile: String, currency: &Currency) -> PathBuf {
+pub fn get_wot_path(profile: String, currency: &CurrencyName) -> PathBuf {
     let mut wot_path = match dirs::config_dir() {
         Some(path) => path,
         None => panic!("Impossible to get your home dir!"),
diff --git a/conf/test/conf.json b/conf/test/conf.json
index 64f117db007e568194dc5def0c4dc222b2290949..c343225948f0425a2d608e35c0541a4a5d72f91c 100644
--- a/conf/test/conf.json
+++ b/conf/test/conf.json
@@ -1,8 +1,6 @@
 {
     "V1": {
-        "currency": {
-            "Str": "g1"
-        },
+        "currency":"g1",
         "my_node_id": 1191678020,
         "disabled": [],
         "enabled": [],
diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml
index e072a9f08521d02b65111df6bdf4e7351b92d923..1351c4fb2bcdbb74e7f62e2c4176489b0e9bb555 100644
--- a/crypto/Cargo.toml
+++ b/crypto/Cargo.toml
@@ -14,6 +14,8 @@ path = "lib.rs"
 [dependencies]
 base58 = "0.1.*"
 base64 = "0.9.*"
+bincode = "1.0.1"
+rand = "0.4.*"
 rust-crypto = "0.2.*"
 serde = "1.0.*"
 serde_derive = "1.0.*"
diff --git a/crypto/hashs/mod.rs b/crypto/hashs/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d04070dc0bbb33afd4649da4ab4a4c7a3fc50112
--- /dev/null
+++ b/crypto/hashs/mod.rs
@@ -0,0 +1,121 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Provide wrappers for cryptographic building blocks used by Duniter.
+
+use crypto::digest::Digest;
+use crypto::sha2::Sha256;
+use keys::BaseConvertionError;
+use rand::{thread_rng, Rng};
+use std::fmt::{Debug, Display, Error, Formatter};
+
+/// A hash wrapper.
+///
+/// A hash is often provided as string composed of 64 hexadecimal character (0 to 9 then A to F).
+#[derive(Copy, Clone, Deserialize, Eq, Ord, PartialEq, PartialOrd, Hash, Serialize)]
+pub struct Hash(pub [u8; 32]);
+
+impl Display for Hash {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+        write!(f, "{}", self.to_hex())
+    }
+}
+
+impl Debug for Hash {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+        write!(f, "Hash({})", self)
+    }
+}
+
+impl Default for Hash {
+    fn default() -> Hash {
+        Hash([0; 32])
+    }
+}
+
+impl Hash {
+    /// Hash size (in bytes).
+    pub const SIZE_IN_BYTES: usize = 32;
+
+    /// Generate a random Hash
+    pub fn random() -> Self {
+        let mut rng = thread_rng();
+        let mut hash_bytes = Vec::with_capacity(32);
+        for _ in 0..32 {
+            hash_bytes.push(rng.gen::<u8>());
+        }
+        let mut hash_bytes_arr = [0; 32];
+        hash_bytes_arr.copy_from_slice(&hash_bytes);
+        Hash(hash_bytes_arr)
+    }
+
+    /// Compute hash of any binary datas
+    pub fn compute(datas: &[u8]) -> Hash {
+        let mut sha = Sha256::new();
+        sha.input(datas);
+        let mut hash_buffer = [0u8; 32];
+        sha.result(&mut hash_buffer);
+        Hash(hash_buffer)
+    }
+
+    /// Convert Hash into bytes vector
+    pub fn to_bytes_vector(&self) -> Vec<u8> {
+        self.0.to_vec()
+    }
+
+    /// Convert a `Hash` to an hex string.
+    pub fn to_hex(&self) -> String {
+        let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
+
+        strings.join("")
+    }
+
+    /// Convert a hex string in a `Hash`.
+    ///
+    /// The hex string must only contains hex characters
+    /// and produce a 32 bytes value.
+    pub fn from_hex(text: &str) -> Result<Hash, BaseConvertionError> {
+        if text.len() != 64 {
+            Err(BaseConvertionError::InvalidKeyLendth(text.len(), 64))
+        } else {
+            let mut hash = Hash([0u8; 32]);
+
+            let chars: Vec<char> = text.chars().collect();
+
+            for i in 0..64 {
+                if i % 2 != 0 {
+                    continue;
+                }
+
+                let byte1 = chars[i].to_digit(16);
+                let byte2 = chars[i + 1].to_digit(16);
+
+                if byte1.is_none() {
+                    return Err(BaseConvertionError::InvalidCharacter(chars[i], i));
+                } else if byte2.is_none() {
+                    return Err(BaseConvertionError::InvalidCharacter(chars[i + 1], i + 1));
+                }
+
+                let byte1 = byte1.unwrap() as u8;
+                let byte2 = byte2.unwrap() as u8;
+
+                let byte = (byte1 << 4) | byte2;
+                hash.0[i / 2] = byte;
+            }
+
+            Ok(hash)
+        }
+    }
+}
diff --git a/crypto/keys/bin_signable.rs b/crypto/keys/bin_signable.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3c572131ba63f4659149dd725f96f5c5165c08ad
--- /dev/null
+++ b/crypto/keys/bin_signable.rs
@@ -0,0 +1,108 @@
+//  Copyright (C) 2018  The Durs Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Generic code for signing data in binary format
+
+use super::*;
+use bincode;
+use hashs::Hash;
+use serde::{Deserialize, Serialize};
+
+/// Signatureable in binary format
+pub trait BinSignable<'de>: Serialize + Deserialize<'de> {
+    /// Return message issuer pubkey
+    fn issuer_pubkey(&self) -> PubKey;
+    /// Return true if message store is hash
+    fn store_hash(&self) -> bool {
+        false
+    }
+    /// Return message hash
+    fn hash(&self) -> Option<Hash> {
+        None
+    }
+    /// Change hash (redefine ly by messages with hash field)
+    fn set_hash(&mut self, _hash: Hash) {}
+    /// Return message signature
+    fn signature(&self) -> Option<Sig>;
+    /// Change signature
+    fn set_signature(&mut self, _signature: Sig);
+    /// Compute hash
+    fn compute_hash(&self) -> Result<(Hash, Vec<u8>), bincode::Error> {
+        let mut bin_msg = bincode::serialize(&self)?;
+        let sig_size = bincode::serialized_size(&self.signature())?;
+        let bin_msg_len = bin_msg.len();
+        bin_msg.truncate(bin_msg_len - (sig_size as usize));
+        if self.store_hash() {
+            bin_msg.pop(); // Delete hash: None
+        }
+        // Compute hash of binary datas without signature
+        let hash = Hash::compute(&bin_msg);
+        Ok((hash, bin_msg))
+    }
+    /// Sign bin message
+    fn sign(&mut self, priv_key: PrivKey) -> Result<Vec<u8>, SignError> {
+        if self.signature().is_some() {
+            return Err(SignError::AlreadySign());
+        }
+        match self.issuer_pubkey() {
+            PubKey::Ed25519(_) => match priv_key {
+                PrivKey::Ed25519(priv_key) => {
+                    let (hash, mut bin_msg) = self.compute_hash().expect("Fail to compute hash !");
+                    self.set_hash(hash);
+                    let bin_sig = priv_key.sign(&hash.0);
+                    let sig = Sig::Ed25519(bin_sig);
+                    self.set_signature(sig);
+                    if self.hash().is_some() {
+                        bin_msg.extend_from_slice(
+                            &bincode::serialize(&Some(hash)).expect("Fail to binarize hash !"),
+                        );
+                    }
+                    bin_msg.extend_from_slice(
+                        &bincode::serialize(&Some(sig)).expect("Fail to binarize sig !"),
+                    );
+                    Ok(bin_msg)
+                }
+                _ => Err(SignError::WrongAlgo()),
+            },
+            _ => Err(SignError::WrongAlgo()),
+        }
+    }
+    /// Check signature of bin message
+    fn verify(&self) -> Result<(), SigError> {
+        if let Some(signature) = self.signature() {
+            match self.issuer_pubkey() {
+                PubKey::Ed25519(pubkey) => match signature {
+                    Sig::Ed25519(sig) => {
+                        let (hash, _) = if let Some(hash) = self.hash() {
+                            (hash, vec![])
+                        } else {
+                            self.compute_hash()?
+                        };
+                        println!("DEBUG: verified hash={:?}", hash.0);
+                        if pubkey.verify(&hash.0, &sig) {
+                            Ok(())
+                        } else {
+                            Err(SigError::InvalidSig())
+                        }
+                    }
+                    _ => Err(SigError::NotSameAlgo()),
+                },
+                _ => Err(SigError::NotSameAlgo()),
+            }
+        } else {
+            Err(SigError::NotSig())
+        }
+    }
+}
diff --git a/crypto/keys/ed25519.rs b/crypto/keys/ed25519.rs
index ef762c258616e224de2b7fcce3d6d85f5fa59dad..3e36cc38d0b620963b1a67e02d1173dcfb5924d6 100644
--- a/crypto/keys/ed25519.rs
+++ b/crypto/keys/ed25519.rs
@@ -19,20 +19,24 @@
 //!
 //! [`KeyPairGenerator`]: struct.KeyPairGenerator.html
 
-extern crate serde;
-
-use self::serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
-use self::serde::ser::{Serialize, SerializeSeq, Serializer};
 use super::{BaseConvertionError, PrivateKey as PrivateKeyMethods, PublicKey as PublicKeyMethods};
 use base58::{FromBase58, FromBase58Error, ToBase58};
 use base64;
 use base64::DecodeError;
 use crypto;
+use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor};
+use serde::ser::{Serialize, SerializeTuple, Serializer};
 use std::collections::hash_map::DefaultHasher;
-use std::fmt::{self, Debug, Display, Error, Formatter};
+use std::fmt;
+use std::fmt::{Debug, Display, Formatter};
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 
+/// Size of a public key in bytes
+pub static PUBKEY_SIZE_IN_BYTES: &'static usize = &32;
+/// Size of a signature in bytes
+pub static SIG_SIZE_IN_BYTES: &'static usize = &64;
+
 /// Store a ed25519 signature.
 #[derive(Clone, Copy)]
 pub struct Signature(pub [u8; 64]);
@@ -45,6 +49,56 @@ impl Hash for Signature {
 }
 
 impl Serialize for Signature {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut seq = serializer.serialize_tuple(self.0.len())?;
+        for elem in &self.0[..] {
+            seq.serialize_element(elem)?;
+        }
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for Signature {
+    fn deserialize<D>(deserializer: D) -> Result<Signature, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct ArrayVisitor<u8> {
+            element: PhantomData<u8>,
+        }
+
+        impl<'de, u8> Visitor<'de> for ArrayVisitor<u8> {
+            type Value = Signature;
+
+            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+                formatter.write_str(concat!("an array of length ", 64))
+            }
+
+            fn visit_seq<A>(self, mut seq: A) -> Result<Signature, A::Error>
+            where
+                A: SeqAccess<'de>,
+            {
+                let mut arr = [0u8; 64];
+                for (i, byte) in arr.iter_mut().take(64).enumerate() {
+                    *byte = seq
+                        .next_element()?
+                        .ok_or_else(|| Error::invalid_length(i, &self))?;
+                }
+                Ok(Signature(arr))
+            }
+        }
+
+        let visitor: ArrayVisitor<u8> = ArrayVisitor {
+            element: PhantomData,
+        };
+        deserializer.deserialize_tuple(64, visitor)
+    }
+}
+
+/*impl Serialize for Signature {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -109,7 +163,7 @@ impl<'de> Deserialize<'de> for Signature {
         // it over the input data, resulting in an instance of Signature.
         deserializer.deserialize_seq(SignatureVisitor::new())
     }
-}
+}*/
 
 impl super::Signature for Signature {
     fn from_base64(base64_data: &str) -> Result<Signature, BaseConvertionError> {
@@ -134,13 +188,17 @@ impl super::Signature for Signature {
         }
     }
 
+    fn to_bytes_vector(&self) -> Vec<u8> {
+        self.0.to_vec()
+    }
+
     fn to_base64(&self) -> String {
         base64::encode(&self.0[..]) // need to take a slice for required trait `AsRef<[u8]>`
     }
 }
 
 impl Display for Signature {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         use super::Signature;
 
         write!(f, "{}", self.to_base64())
@@ -178,14 +236,14 @@ impl ToBase58 for PublicKey {
 }
 
 impl Display for PublicKey {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         write!(f, "{}", self.to_base58())
     }
 }
 
 impl Debug for PublicKey {
     // PublicKey { DNann1L... }
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         write!(f, "PublicKey {{ {} }}", self)
     }
 }
@@ -215,6 +273,10 @@ impl super::PublicKey for PublicKey {
         }
     }
 
+    fn to_bytes_vector(&self) -> Vec<u8> {
+        self.0.to_vec()
+    }
+
     fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool {
         crypto::ed25519::verify(message, &self.0, &signature.0)
     }
@@ -235,14 +297,14 @@ impl ToBase58 for PrivateKey {
 }
 
 impl Display for PrivateKey {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         write!(f, "{}", self.to_base58())
     }
 }
 
 impl Debug for PrivateKey {
     // PrivateKey { 468Q1XtT... }
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         write!(f, "PrivateKey {{ {} }}", self)
     }
 }
@@ -297,7 +359,7 @@ pub struct KeyPair {
 }
 
 impl Display for KeyPair {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
         write!(f, "({}, hidden)", self.pubkey.to_base58())
     }
 }
diff --git a/crypto/keys/mod.rs b/crypto/keys/mod.rs
index 1bd615e17ff431f1fb7ab0fd6963f7ea5459a0d8..b110b95eb32c5c7d91bca71f7732eccb0231a8cb 100644
--- a/crypto/keys/mod.rs
+++ b/crypto/keys/mod.rs
@@ -49,16 +49,18 @@
 extern crate serde;
 
 use base58::ToBase58;
+use bincode;
 use std::fmt::Debug;
 use std::fmt::Display;
 use std::fmt::Error;
 use std::fmt::Formatter;
 use std::hash::Hash;
 
+pub mod bin_signable;
 pub mod ed25519;
 
 /// Cryptographic keys algorithms list
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
 pub enum KeysAlgo {
     /// Ed25519 algorithm
     Ed25519 = 0,
@@ -83,6 +85,36 @@ pub enum BaseConvertionError {
     InvalidBaseConverterLength(),
 }
 
+/// Errors enumeration for signature verification.
+#[derive(Debug)]
+pub enum SigError {
+    /// Signature and pubkey are not the same algo
+    NotSameAlgo(),
+    /// Invalid signature
+    InvalidSig(),
+    /// Absence of signature
+    NotSig(),
+    /// Deserialization error
+    DeserError(bincode::Error),
+}
+
+impl From<bincode::Error> for SigError {
+    fn from(e: bincode::Error) -> Self {
+        SigError::DeserError(e)
+    }
+}
+
+/// SignError
+#[derive(Debug, Copy, Clone)]
+pub enum SignError {
+    /// WrongAlgo
+    WrongAlgo(),
+    /// WrongPrivkey
+    WrongPrivkey(),
+    /// AlreadySign
+    AlreadySign(),
+}
+
 /// Define the operations that can be performed on a cryptographic signature.
 ///
 /// A signature can be converted from/to Base64 format.
@@ -103,6 +135,9 @@ pub trait Signature: Clone + Display + Debug + PartialEq + Eq + Hash {
     /// [`BaseConvertionError`]: enum.BaseConvertionError.html
     fn from_base64(base64_string: &str) -> Result<Self, BaseConvertionError>;
 
+    /// Convert Signature into butes vector
+    fn to_bytes_vector(&self) -> Vec<u8>;
+
     /// Encode the signature into Base64 string format.
     fn to_base64(&self) -> String;
 }
@@ -116,6 +151,16 @@ pub enum Sig {
     Schnorr(),
 }
 
+impl Sig {
+    /// Get Sig size in bytes
+    pub fn size_in_bytes(&self) -> usize {
+        match *self {
+            Sig::Ed25519(_) => *ed25519::SIG_SIZE_IN_BYTES + 2,
+            Sig::Schnorr() => panic!("Schnorr algo not yet supported !"),
+        }
+    }
+}
+
 impl Display for Sig {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         write!(f, "{}", self.to_base64())
@@ -135,6 +180,12 @@ impl Signature for Sig {
     fn from_base64(_base64_string: &str) -> Result<Self, BaseConvertionError> {
         unimplemented!()
     }
+    fn to_bytes_vector(&self) -> Vec<u8> {
+        match *self {
+            Sig::Ed25519(ed25519_sig) => ed25519_sig.to_bytes_vector(),
+            Sig::Schnorr() => panic!("Schnorr algo not yet supported !"),
+        }
+    }
     fn to_base64(&self) -> String {
         match *self {
             Sig::Ed25519(ed25519_sig) => ed25519_sig.to_base64(),
@@ -165,6 +216,9 @@ pub trait PublicKey: Clone + Display + Debug + PartialEq + Eq + Hash + ToBase58
     /// [`BaseConvertionError`]: enum.BaseConvertionError.html
     fn from_base58(base58_string: &str) -> Result<Self, BaseConvertionError>;
 
+    /// Convert into bytes vector
+    fn to_bytes_vector(&self) -> Vec<u8>;
+
     /// Verify a signature with this public key.
     fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool;
 }
@@ -178,6 +232,22 @@ pub enum PubKey {
     Schnorr(),
 }
 
+impl PubKey {
+    /// Compute PubKey size in bytes
+    pub fn size_in_bytes(&self) -> usize {
+        match *self {
+            PubKey::Ed25519(_) => ed25519::PUBKEY_SIZE_IN_BYTES + 3,
+            PubKey::Schnorr() => panic!("Schnorr algo not yet supported !"),
+        }
+    }
+}
+
+impl Default for PubKey {
+    fn default() -> Self {
+        PubKey::Schnorr()
+    }
+}
+
 impl GetKeysAlgo for PubKey {
     fn algo(&self) -> KeysAlgo {
         match *self {
@@ -208,6 +278,12 @@ impl PublicKey for PubKey {
     fn from_base58(_base58_string: &str) -> Result<Self, BaseConvertionError> {
         unimplemented!()
     }
+    fn to_bytes_vector(&self) -> Vec<u8> {
+        match *self {
+            PubKey::Ed25519(ed25519_pubkey) => ed25519_pubkey.to_bytes_vector(),
+            PubKey::Schnorr() => panic!("Schnorr algo not yet supported !"),
+        }
+    }
     fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool {
         match *self {
             PubKey::Ed25519(ed25519_pubkey) => if let Sig::Ed25519(ed25519_sig) = signature {
diff --git a/crypto/lib.rs b/crypto/lib.rs
index c05b5fc79217ec29b634dc90fa6a851124c2d55a..3620eb994bb08f6ec328d1e3aa412e0b2418d565 100644
--- a/crypto/lib.rs
+++ b/crypto/lib.rs
@@ -16,6 +16,7 @@
 //! Provide wrappers for cryptographic building blocks used by Duniter.
 
 #![cfg_attr(feature = "strict", deny(warnings))]
+#![cfg_attr(feature = "cargo-clippy", allow(builtin_type_shadow))]
 #![deny(
     missing_docs,
     missing_debug_implementations,
@@ -27,12 +28,17 @@
     unused_import_braces,
     unused_qualifications
 )]
+#![allow(non_camel_case_types)]
 
 #[macro_use]
 extern crate serde_derive;
 
 extern crate base58;
 extern crate base64;
+extern crate bincode;
 extern crate crypto;
+extern crate rand;
+extern crate serde;
 
+pub mod hashs;
 pub mod keys;
diff --git a/dal/currency_params.rs b/dal/currency_params.rs
index 77fb3f8175428a5464ff237c09a15971918cfa03..e0ab8a0ce3d4474036c780b5660b5b8a01d7294e 100644
--- a/dal/currency_params.rs
+++ b/dal/currency_params.rs
@@ -14,7 +14,8 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use constants::*;
-use duniter_documents::blockchain::v10::documents::block::{BlockV10Parameters, CurrencyName};
+use duniter_documents::blockchain::v10::documents::block::BlockV10Parameters;
+use duniter_documents::CurrencyName;
 use *;
 
 #[derive(Debug, Copy, Clone)]
diff --git a/dal/dal_requests.rs b/dal/dal_requests.rs
index 8f9e76c6fb28fddc3bd20f7fe17a3ad0a4df4283..bc90b8c90f8980a422ee1026eded82ee347785d7 100644
--- a/dal/dal_requests.rs
+++ b/dal/dal_requests.rs
@@ -17,11 +17,12 @@ extern crate duniter_module;
 extern crate serde;
 
 use self::duniter_module::ModuleReqFullId;
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
 use duniter_documents::blockchain::v10::documents::{
     BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument,
 };
-use duniter_documents::{Blockstamp, Hash};
+use duniter_documents::Blockstamp;
 use std::collections::HashMap;
 
 #[derive(Debug, Clone)]
diff --git a/dal/lib.rs b/dal/lib.rs
index e116f53534ffb7cb046fddcd1dcc7cb1566f0bd1..d7c837568b80907c8b6786a76c9e01934ab8e166 100644
--- a/dal/lib.rs
+++ b/dal/lib.rs
@@ -79,10 +79,12 @@ pub mod tools;
 /// Contains all write databases functions
 pub mod writers;
 
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
-use duniter_documents::blockchain::v10::documents::block::{BlockV10Parameters, CurrencyName};
+use duniter_documents::blockchain::v10::documents::block::BlockV10Parameters;
 use duniter_documents::blockchain::v10::documents::transaction::*;
-use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash, PreviousBlockstamp};
+use duniter_documents::CurrencyName;
+use duniter_documents::{BlockHash, BlockId, Blockstamp, PreviousBlockstamp};
 use duniter_wotb::{NodeId, WebOfTrust};
 use rustbreak::backend::{FileBackend, MemoryBackend};
 use rustbreak::error::{RustbreakError, RustbreakErrorKind};
diff --git a/dal/sources.rs b/dal/sources.rs
index 7148a819a31f2b2bd73b47ca6e907cf3fed2735a..54a40a54ff021c8d698c78d05d9e3ec340e4c184 100644
--- a/dal/sources.rs
+++ b/dal/sources.rs
@@ -16,9 +16,10 @@
 extern crate duniter_crypto;
 extern crate duniter_documents;
 
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::PubKey;
 use duniter_documents::blockchain::v10::documents::transaction::*;
-use duniter_documents::{BlockId, Hash};
+use duniter_documents::BlockId;
 use std::cmp::Ordering;
 use std::ops::{Add, Sub};
 
diff --git a/documents/Cargo.toml b/documents/Cargo.toml
index 4c541dcb0bbaacf5f805952a63a81cd377e02389..0f6fd6ff01c1ff0d37fab7097e9a8d19ec22358e 100644
--- a/documents/Cargo.toml
+++ b/documents/Cargo.toml
@@ -14,6 +14,7 @@ path = "lib.rs"
 [dependencies]
 base58 = "0.1.*"
 base64 = "0.9.*"
+byteorder = "1.2.3"
 duniter-crypto = { path = "../crypto" }
 lazy_static = "1.0.*"
 linked-hash-map = "0.5.*"
diff --git a/documents/blockchain/v10/documents/block.rs b/documents/blockchain/v10/documents/block.rs
index 5ddc7b51c7c96d0de67b19db1f49814c5a657a42..6d9dfb59742252016f3e3c3c6645f87e8bf35cc1 100644
--- a/documents/blockchain/v10/documents/block.rs
+++ b/documents/blockchain/v10/documents/block.rs
@@ -17,6 +17,7 @@
 
 use crypto::digest::Digest;
 use crypto::sha2::Sha256;
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
 
 use blockchain::v10::documents::certification::CertificationDocument;
@@ -26,25 +27,9 @@ use blockchain::v10::documents::revocation::RevocationDocument;
 use blockchain::v10::documents::transaction::TransactionDocument;
 use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, IntoSpecializedDocument};
-use std::fmt::{Display, Error, Formatter};
 use std::ops::Deref;
-use {BlockHash, BlockId, Blockstamp, Hash};
-
-/// Currency name
-#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
-pub struct CurrencyName(pub String);
-
-impl Default for CurrencyName {
-    fn default() -> CurrencyName {
-        CurrencyName(String::from("default_currency"))
-    }
-}
-
-impl Display for CurrencyName {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
-        write!(f, "{}", self.0)
-    }
-}
+use CurrencyName;
+use {BlockHash, BlockId, Blockstamp};
 
 #[derive(Debug, Clone)]
 /// Store error in block parameters parsing
@@ -172,7 +157,7 @@ impl ::std::str::FromStr for BlockV10Parameters {
 }
 
 /// Store a transaction document or just its hash.
-#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub enum TxDocOrTxHash {
     /// Transaction document
     TxDoc(Box<TransactionDocument>),
@@ -267,6 +252,14 @@ pub struct BlockDocument {
     pub inner_hash_and_nonce_str: String,
 }
 
+impl PartialEq for BlockDocument {
+    fn eq(&self, other: &BlockDocument) -> bool {
+        self.hash == other.hash
+    }
+}
+
+impl Eq for BlockDocument {}
+
 impl BlockDocument {
     /// Return previous blockstamp
     pub fn previous_blockstamp(&self) -> Blockstamp {
@@ -322,14 +315,21 @@ impl BlockDocument {
         //self.hash = None;
         self.inner_hash = None;
         self.inner_hash_and_nonce_str = String::with_capacity(0);
-        self.identities
-            .iter_mut()
-            .map(|i| i.reduce())
-            .collect::<()>();
-        self.joiners.iter_mut().map(|i| i.reduce()).collect::<()>();
-        self.actives.iter_mut().map(|i| i.reduce()).collect::<()>();
-        self.leavers.iter_mut().map(|i| i.reduce()).collect::<()>();
-        self.transactions = self.transactions.iter_mut().map(|t| t.reduce()).collect();
+        for i in &mut self.identities {
+            i.reduce();
+        }
+        for i in &mut self.joiners {
+            i.reduce();
+        }
+        for i in &mut self.actives {
+            i.reduce();
+        }
+        for i in &mut self.leavers {
+            i.reduce();
+        }
+        for i in &mut self.transactions {
+            i.reduce();
+        }
     }
     /// Generate compact inner text (for compute inner_hash)
     pub fn generate_compact_inner_text(&self) -> String {
diff --git a/documents/blockchain/v10/documents/certification.rs b/documents/blockchain/v10/documents/certification.rs
index ec94e36098cde2ab75eb375350338459908f4a39..0d2b889e0f0dd89cee75dc4ab0904af685a7b93c 100644
--- a/documents/blockchain/v10/documents/certification.rs
+++ b/documents/blockchain/v10/documents/certification.rs
@@ -29,7 +29,7 @@ lazy_static! {
     ).unwrap();
 }
 
-#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
+#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
 /// Wrap an Compact Revocation document (in block content)
 pub struct CompactCertificationDocument {
     /// Issuer
@@ -57,7 +57,7 @@ impl CompactTextDocument for CompactCertificationDocument {
 /// Wrap an Certification document.
 ///
 /// Must be created by parsing a text document or using a builder.
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
 pub struct CertificationDocument {
     /// Document as text.
     ///
diff --git a/documents/blockchain/v10/documents/identity.rs b/documents/blockchain/v10/documents/identity.rs
index c4e8c6a7c6fde5f64ca5b6c62192836902cf66d4..efa612facf7dca786bd8a87c2588a8305a94b949 100644
--- a/documents/blockchain/v10/documents/identity.rs
+++ b/documents/blockchain/v10/documents/identity.rs
@@ -29,7 +29,7 @@ lazy_static! {
 /// Wrap an Identity document.
 ///
 /// Must be created by parsing a text document or using a builder.
-#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
+#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
 pub struct IdentityDocument {
     /// Document as text.
     ///
@@ -90,12 +90,25 @@ impl Document for IdentityDocument {
     }
 }
 
-impl CompactTextDocument for IdentityDocument {
+/// CompactIdentityDocument
+#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
+pub struct CompactIdentityDocument {
+    /// Unique ID
+    username: String,
+    /// Blockstamp
+    blockstamp: Blockstamp,
+    /// Document issuer
+    pubkey: PubKey,
+    /// Document signature
+    signature: Sig,
+}
+
+impl CompactTextDocument for CompactIdentityDocument {
     fn as_compact_text(&self) -> String {
         format!(
             "{issuer}:{signature}:{blockstamp}:{username}",
-            issuer = self.issuers[0],
-            signature = self.signatures[0],
+            issuer = self.pubkey,
+            signature = self.signature,
             blockstamp = self.blockstamp,
             username = self.username,
         )
@@ -103,7 +116,7 @@ impl CompactTextDocument for IdentityDocument {
 }
 
 impl TextDocument for IdentityDocument {
-    type CompactTextDocument_ = IdentityDocument;
+    type CompactTextDocument_ = CompactIdentityDocument;
 
     fn as_text(&self) -> &str {
         if let Some(ref text) = self.text {
@@ -114,7 +127,12 @@ impl TextDocument for IdentityDocument {
     }
 
     fn to_compact_document(&self) -> Self::CompactTextDocument_ {
-        self.clone()
+        CompactIdentityDocument {
+            username: self.username.clone(),
+            blockstamp: self.blockstamp,
+            pubkey: self.issuers[0],
+            signature: self.signatures[0],
+        }
     }
 }
 
diff --git a/documents/blockchain/v10/documents/membership.rs b/documents/blockchain/v10/documents/membership.rs
index 2c339b68b2aa907ad7d3c2fc14010e7e7a07372e..2d30391a8c2431b6e5f6ef6665bb7cc3e88bda5f 100644
--- a/documents/blockchain/v10/documents/membership.rs
+++ b/documents/blockchain/v10/documents/membership.rs
@@ -33,7 +33,7 @@ lazy_static! {
 }
 
 /// Type of a Membership.
-#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Hash, Serialize)]
+#[derive(Debug, Deserialize, Clone, Copy, Hash, Serialize, PartialEq, Eq)]
 pub enum MembershipType {
     /// The member wishes to opt-in.
     In(),
@@ -44,7 +44,7 @@ pub enum MembershipType {
 /// Wrap an Membership document.
 ///
 /// Must be created by parsing a text document or using a builder.
-#[derive(Debug, Clone, PartialEq, Hash, Deserialize, Serialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
 pub struct MembershipDocument {
     /// Document as text.
     ///
@@ -152,6 +152,15 @@ impl CompactTextDocument for MembershipDocument {
     }
 }
 
+/// CompactPoolMembershipDoc
+#[derive(Copy, Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
+pub struct CompactPoolMembershipDoc {
+    /// Document creation blockstamp
+    pub blockstamp: Blockstamp,
+    /// Signature
+    pub signature: Sig,
+}
+
 impl TextDocument for MembershipDocument {
     type CompactTextDocument_ = MembershipDocument;
 
diff --git a/documents/blockchain/v10/documents/revocation.rs b/documents/blockchain/v10/documents/revocation.rs
index 00f738d7199f71f334d4b952821be086d30f0780..41032871f1e1a3236e62e31d4b86906057162a40 100644
--- a/documents/blockchain/v10/documents/revocation.rs
+++ b/documents/blockchain/v10/documents/revocation.rs
@@ -31,7 +31,7 @@ lazy_static! {
     ).unwrap();
 }
 
-#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
+#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq)]
 /// Wrap an Compact Revocation document (in block content)
 pub struct CompactRevocationDocument {
     /// Issuer
@@ -53,7 +53,7 @@ impl CompactTextDocument for CompactRevocationDocument {
 /// Wrap an Revocation document.
 ///
 /// Must be created by parsing a text document or using a builder.
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
 pub struct RevocationDocument {
     /// Document as text.
     ///
diff --git a/documents/blockchain/v10/documents/transaction.rs b/documents/blockchain/v10/documents/transaction.rs
index 0ed63eed529dca9448b6cd56368c5442755f70ef..30b0207acc95b7e366f3736b2f0b677fdb3866ab 100644
--- a/documents/blockchain/v10/documents/transaction.rs
+++ b/documents/blockchain/v10/documents/transaction.rs
@@ -528,7 +528,9 @@ impl TransactionDocument {
     pub fn reduce(&mut self) {
         self.text = None;
         self.hash = None;
-        self.outputs.iter_mut().map(|o| o.reduce()).collect::<()>();
+        for output in &mut self.outputs {
+            output.reduce()
+        }
     }
 }
 
diff --git a/documents/currencies_codes.rs b/documents/currencies_codes.rs
new file mode 100644
index 0000000000000000000000000000000000000000..879498312685db86c06f4c23a025f9c32ff7c86b
--- /dev/null
+++ b/documents/currencies_codes.rs
@@ -0,0 +1,23 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Implements the Duniter Documents Protocol.
+
+/// CURRENCY_NULL
+pub static CURRENCY_NULL: &'static u16 = &0x_0000;
+/// CURRENCY_G1
+pub static CURRENCY_G1: &'static u16 = &0x_0001;
+/// CURRENCY_G1_TEST
+pub static CURRENCY_G1_TEST: &'static u16 = &0x_1000;
diff --git a/documents/lib.rs b/documents/lib.rs
index 39f8166116c646e562312b2240d88e38df17e1a1..e36b341210665b0c0e46fb127710266bfbbf4c52 100644
--- a/documents/lib.rs
+++ b/documents/lib.rs
@@ -16,7 +16,6 @@
 //! Implements the Duniter Documents Protocol.
 
 #![cfg_attr(feature = "strict", deny(warnings))]
-#![cfg_attr(feature = "cargo-clippy", allow(unused_collect))]
 #![deny(
     missing_docs,
     missing_debug_implementations,
@@ -36,96 +35,96 @@ extern crate serde_derive;
 
 extern crate base58;
 extern crate base64;
+extern crate byteorder;
 extern crate crypto;
 extern crate duniter_crypto;
 extern crate linked_hash_map;
 extern crate regex;
 extern crate serde;
 
-use duniter_crypto::keys::BaseConvertionError;
+use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use currencies_codes::*;
+use duniter_crypto::hashs::Hash;
 use std::cmp::Ordering;
 use std::fmt::{Debug, Display, Error, Formatter};
+use std::io::Cursor;
+use std::mem;
 
 pub mod blockchain;
+mod currencies_codes;
 
-/// A block Id.
-#[derive(Copy, Clone, Debug, Deserialize, Ord, PartialEq, PartialOrd, Eq, Hash, Serialize)]
-pub struct BlockId(pub u32);
+/// Currency name
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
+pub struct CurrencyName(pub String);
 
-impl Display for BlockId {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
-        write!(f, "{}", self.0)
+impl Default for CurrencyName {
+    fn default() -> CurrencyName {
+        CurrencyName(String::from("default_currency"))
     }
 }
 
-/// A hash wrapper.
-///
-/// A hash is often provided as string composed of 64 hexadecimal character (0 to 9 then A to F).
-#[derive(Copy, Clone, Deserialize, Eq, Ord, PartialEq, PartialOrd, Hash, Serialize)]
-pub struct Hash(pub [u8; 32]);
-
-impl Display for Hash {
+impl Display for CurrencyName {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
-        write!(f, "{}", self.to_hex())
+        write!(f, "{}", self.0)
     }
 }
 
-impl Debug for Hash {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
-        write!(f, "Hash({})", self)
-    }
+/// CurrencyCodeError
+#[derive(Debug)]
+pub enum CurrencyCodeError {
+    /// UnknowCurrencyCode
+    UnknowCurrencyCode(),
+    /// IoError
+    IoError(::std::io::Error),
+    /// UnknowCurrencyName
+    UnknowCurrencyName(),
 }
 
-impl Default for Hash {
-    fn default() -> Hash {
-        let default: [u8; 32] = [0; 32];
-        Hash(default)
+impl From<::std::io::Error> for CurrencyCodeError {
+    fn from(error: ::std::io::Error) -> Self {
+        CurrencyCodeError::IoError(error)
     }
 }
 
-impl Hash {
-    /// Convert a `Hash` to an hex string.
-    pub fn to_hex(&self) -> String {
-        let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
-
-        strings.join("")
+impl CurrencyName {
+    /// Convert bytes to CurrencyName
+    pub fn from(currency_code: [u8; 2]) -> Result<Self, CurrencyCodeError> {
+        let mut currency_code_bytes = Cursor::new(currency_code.to_vec());
+        let currency_code = currency_code_bytes.read_u16::<BigEndian>()?;
+        Self::from_u16(currency_code)
     }
+    /// Convert u16 to CurrencyName
+    pub fn from_u16(currency_code: u16) -> Result<Self, CurrencyCodeError> {
+        match currency_code {
+            tmp if tmp == *CURRENCY_NULL => Ok(CurrencyName(String::from(""))),
+            tmp if tmp == *CURRENCY_G1 => Ok(CurrencyName(String::from("g1"))),
+            tmp if tmp == *CURRENCY_G1_TEST => Ok(CurrencyName(String::from("g1-test"))),
+            _ => Err(CurrencyCodeError::UnknowCurrencyCode()),
+        }
+    }
+    /// Convert CurrencyName to bytes
+    pub fn to_bytes(&self) -> Result<[u8; 2], CurrencyCodeError> {
+        let currency_code = match self.0.as_str() {
+            "g1" => *CURRENCY_G1,
+            "g1-test" => *CURRENCY_G1_TEST,
+            _ => return Err(CurrencyCodeError::UnknowCurrencyName()),
+        };
+        let mut buffer = [0u8; mem::size_of::<u16>()];
+        buffer
+            .as_mut()
+            .write_u16::<BigEndian>(currency_code)
+            .expect("Unable to write");
+        Ok(buffer)
+    }
+}
 
-    /// Convert a hex string in a `Hash`.
-    ///
-    /// The hex string must only contains hex characters
-    /// and produce a 32 bytes value.
-    pub fn from_hex(text: &str) -> Result<Hash, BaseConvertionError> {
-        if text.len() != 64 {
-            Err(BaseConvertionError::InvalidKeyLendth(text.len(), 64))
-        } else {
-            let mut hash = Hash([0u8; 32]);
-
-            let chars: Vec<char> = text.chars().collect();
-
-            for i in 0..64 {
-                if i % 2 != 0 {
-                    continue;
-                }
-
-                let byte1 = chars[i].to_digit(16);
-                let byte2 = chars[i + 1].to_digit(16);
-
-                if byte1.is_none() {
-                    return Err(BaseConvertionError::InvalidCharacter(chars[i], i));
-                } else if byte2.is_none() {
-                    return Err(BaseConvertionError::InvalidCharacter(chars[i + 1], i + 1));
-                }
-
-                let byte1 = byte1.unwrap() as u8;
-                let byte2 = byte2.unwrap() as u8;
-
-                let byte = (byte1 << 4) | byte2;
-                hash.0[i / 2] = byte;
-            }
+/// A block Id.
+#[derive(Copy, Clone, Debug, Deserialize, Ord, PartialEq, PartialOrd, Eq, Hash, Serialize)]
+pub struct BlockId(pub u32);
 
-            Ok(hash)
-        }
+impl Display for BlockId {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+        write!(f, "{}", self.0)
     }
 }
 
@@ -168,6 +167,7 @@ pub enum BlockUIdParseError {
 ///
 /// [`BlockId`]: struct.BlockId.html
 /// [`BlockHash`]: struct.BlockHash.html
+
 #[derive(Copy, Clone, Deserialize, PartialEq, Eq, Hash, Serialize)]
 pub struct Blockstamp {
     /// Block Id.
@@ -179,6 +179,11 @@ pub struct Blockstamp {
 /// Previous blockstamp (BlockId-1, previous_hash)
 pub type PreviousBlockstamp = Blockstamp;
 
+impl Blockstamp {
+    /// Blockstamp size (in bytes).
+    pub const SIZE_IN_BYTES: usize = 36;
+}
+
 impl Display for Blockstamp {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         write!(f, "{}-{}", self.id, self.hash)
@@ -216,6 +221,58 @@ impl Ord for Blockstamp {
     }
 }
 
+#[derive(Debug)]
+/// Error when converting a byte vector to Blockstamp
+pub enum ReadBytesBlockstampError {
+    /// Bytes vector is too short
+    TooShort(),
+    /// Bytes vector is too long
+    TooLong(),
+    /// IoError
+    IoError(::std::io::Error),
+}
+
+impl From<::std::io::Error> for ReadBytesBlockstampError {
+    fn from(e: ::std::io::Error) -> Self {
+        ReadBytesBlockstampError::IoError(e)
+    }
+}
+
+/*
+impl BinMessage for Blockstamp {
+    type ReadBytesError = ReadBytesBlockstampError;
+    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::ReadBytesError> {
+        if bytes.len() > 36 {
+            Err(ReadBytesBlockstampError::TooLong())
+        } else if bytes.len() < 36 {
+            Err(ReadBytesBlockstampError::TooShort())
+        } else {
+            // read id
+            let mut id_bytes = Cursor::new(bytes[0..4].to_vec());
+            let id = BlockId(id_bytes.read_u32::<BigEndian>()?);
+            // read hash
+            let mut hash_datas: [u8; 32] = [0u8; 32];
+            hash_datas.copy_from_slice(&bytes[4..36]);
+            let hash = BlockHash(Hash(hash_datas));
+            // return Blockstamp
+            Ok(Blockstamp { id, hash })
+        }
+    }
+    fn to_bytes_vector(&self) -> Vec<u8> {
+        let mut bytes = Vec::with_capacity(36);
+        // BlockId
+        let mut buffer = [0u8; mem::size_of::<u32>()];
+        buffer
+            .as_mut()
+            .write_u32::<BigEndian>(self.id.0)
+            .expect("Unable to write");
+        bytes.extend_from_slice(&buffer);
+        // BlockHash
+        bytes.extend(self.hash.0.to_bytes_vector());
+        bytes
+    }
+}*/
+
 impl Blockstamp {
     /// Create a `BlockUId` from a text.
     pub fn from_string(src: &str) -> Result<Blockstamp, BlockUIdParseError> {
diff --git a/message/lib.rs b/message/lib.rs
index d95c506b5ab2a7b38c1291c0104ad22c17d48ada..b4bc903f3361139e416e199d6cb3c1ea6bdfbfcc 100644
--- a/message/lib.rs
+++ b/message/lib.rs
@@ -39,11 +39,12 @@ extern crate serde_json;
 
 use std::sync::mpsc;
 
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::Sig;
 use duniter_dal::dal_event::DALEvent;
 use duniter_dal::dal_requests::{DALRequest, DALResponse};
 use duniter_documents::blockchain::BlockchainProtocol;
-use duniter_documents::{BlockId, Hash};
+use duniter_documents::BlockId;
 use duniter_module::{ModuleId, ModuleMessage};
 use duniter_network::{NetworkEvent, NetworkRequest};
 
diff --git a/module/lib.rs b/module/lib.rs
index a11db06413dd4726088e093f385be59ba634601e..2122c1a2afa8ffb0882174378d6eaed223d31da1 100644
--- a/module/lib.rs
+++ b/module/lib.rs
@@ -34,34 +34,18 @@
 extern crate serde_derive;
 
 extern crate duniter_crypto;
+extern crate duniter_documents;
 extern crate serde;
 extern crate serde_json;
 
 use duniter_crypto::keys::{KeyPair, KeyPairEnum};
+use duniter_documents::CurrencyName;
 use serde::de::DeserializeOwned;
 use serde::ser::{Serialize, Serializer};
 use std::collections::HashSet;
 use std::fmt::Debug;
 use std::sync::mpsc;
 
-#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash, Serialize)]
-/// Store Currency
-pub enum Currency {
-    /// Currency in string format
-    Str(String),
-    /// Currency in binary format
-    Bin([u8; 2]),
-}
-
-impl ToString for Currency {
-    fn to_string(&self) -> String {
-        match *self {
-            Currency::Str(ref currency_str) => currency_str.clone(),
-            Currency::Bin(_) => panic!("Currency binary format is not implemented !"),
-        }
-    }
-}
-
 #[derive(Clone, Deserialize, Debug, PartialEq, Eq, Hash, Serialize)]
 /// Store module identifier
 pub struct ModuleId(pub String);
@@ -101,9 +85,9 @@ pub trait DuniterConf: Clone + Debug + Default + PartialEq + Serialize + Deseria
     /// Get conf version profile
     fn version(&self) -> usize;
     /// Get currency
-    fn currency(&self) -> Currency;
+    fn currency(&self) -> CurrencyName;
     /// Set currency
-    fn set_currency(&mut self, new_currency: Currency);
+    fn set_currency(&mut self, new_currency: CurrencyName);
     /// Get node id
     fn my_node_id(&self) -> u32;
     /// Disable a module
diff --git a/network/Cargo.toml b/network/Cargo.toml
index 0a7a6e99dba9b53f3ab4cb6acb446b12ac4093be..f31e48bf03b8a9e25119fd3fcd8b90a29a7ad19a 100644
--- a/network/Cargo.toml
+++ b/network/Cargo.toml
@@ -9,6 +9,8 @@ license = "AGPL-3.0"
 path = "lib.rs"
 
 [dependencies]
+base58 = "0.1.*"
+byteorder = "1.2.3"
 duniter-crypto = { path = "../crypto" }
 duniter-documents = { path = "../documents" }
 duniter-module = { path = "../module" }
@@ -19,6 +21,10 @@ serde = "1.0.*"
 serde_derive = "1.0.*"
 serde_json = "1.0.*"
 
+[dev-dependencies]
+pretty_assertions = "0.5.1"
+bincode = "1.0.1"
+
 [features]
 # Treat warnings as a build error.
 strict = []
\ No newline at end of file
diff --git a/network/lib.rs b/network/lib.rs
index 41c066dae134aed4aaf310ad84f83844bce09b75..90eef25982cbe2bf9e28aa371235379316eec110 100644
--- a/network/lib.rs
+++ b/network/lib.rs
@@ -30,9 +30,14 @@
 
 #[macro_use]
 extern crate lazy_static;
+#[cfg(test)]
+#[macro_use]
+extern crate pretty_assertions;
 #[macro_use]
 extern crate serde_derive;
 
+extern crate base58;
+extern crate byteorder;
 extern crate crypto;
 extern crate duniter_crypto;
 extern crate duniter_documents;
@@ -42,26 +47,38 @@ extern crate serde_json;
 
 pub mod network_endpoint;
 pub mod network_head;
+pub mod network_head_v2;
+pub mod network_head_v3;
 pub mod network_peer;
 
 use crypto::digest::Digest;
 use crypto::sha2::Sha256;
+use duniter_crypto::hashs::*;
 use duniter_crypto::keys::*;
 use duniter_documents::blockchain::v10::documents::{
     BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument,
     TransactionDocument,
 };
 use duniter_documents::blockchain::Document;
-use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use duniter_documents::{BlockHash, BlockId, Blockstamp};
 use duniter_module::*;
+use network_endpoint::ApiFeatures;
 use network_head::NetworkHead;
-use network_peer::NetworkPeer;
+use network_peer::PeerCard;
 use std::fmt::{Debug, Display, Error, Formatter};
 use std::ops::Deref;
 use std::sync::mpsc;
 
+/// ApiModule
+pub trait ApiModule<DC: DuniterConf, M: ModuleMessage>: DuniterModule<DC, M> {
+    /// Parsing error
+    type ParseErr;
+    /// Parse raw api features
+    fn parse_raw_api_features(str_features: &str) -> Result<ApiFeatures, Self::ParseErr>;
+}
+
 /// NetworkModule
-pub trait NetworkModule<DC: DuniterConf, M: ModuleMessage>: DuniterModule<DC, M> {
+pub trait NetworkModule<DC: DuniterConf, M: ModuleMessage>: ApiModule<DC, M> {
     /// Launch synchronisation
     fn sync(
         soft_meta_datas: &SoftwareMetaDatas<DC>,
@@ -87,34 +104,34 @@ pub struct SyncEndpoint {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// Random identifier with which several Duniter nodes with the same network keypair can be differentiated
-pub struct NodeUUID(pub u32);
+pub struct NodeId(pub u32);
 
-impl Default for NodeUUID {
-    fn default() -> NodeUUID {
-        NodeUUID(0)
+impl Default for NodeId {
+    fn default() -> NodeId {
+        NodeId(0)
     }
 }
 
-impl Display for NodeUUID {
+impl Display for NodeId {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         write!(f, "{:x}", self.0)
     }
 }
 
-impl<'a> From<&'a str> for NodeUUID {
-    fn from(source: &'a str) -> NodeUUID {
-        NodeUUID(u32::from_str_radix(source, 16).expect("Fail to parse NodeUUID"))
+impl<'a> From<&'a str> for NodeId {
+    fn from(source: &'a str) -> NodeId {
+        NodeId(u32::from_str_radix(source, 16).expect("Fail to parse NodeId"))
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 /// Complete identifier of a duniter node.
-pub struct NodeFullId(pub NodeUUID, pub PubKey);
+pub struct NodeFullId(pub NodeId, pub PubKey);
 
 impl Default for NodeFullId {
     fn default() -> NodeFullId {
         NodeFullId(
-            NodeUUID::default(),
+            NodeId::default(),
             PubKey::Ed25519(
                 ed25519::PublicKey::from_base58("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
                     .unwrap(),
@@ -161,7 +178,7 @@ pub struct NetworkBlockV10 {
 #[derive(Debug, Clone)]
 /// Block in network format (Some events require a blockchain access to reconstitute the corresponding document)
 pub enum NetworkBlock {
-    /// Block V10
+    /// Block V1
     V10(Box<NetworkBlockV10>),
     /// Block V11
     V11(),
@@ -323,28 +340,35 @@ pub enum NetworkEvent {
     /// Receiving Pending Documents
     ReceiveDocuments(Vec<NetworkDocument>),
     /// Receipt of peer cards
-    ReceivePeers(Vec<NetworkPeer>),
+    ReceivePeers(Vec<PeerCard>),
     /// Receiving heads
     ReceiveHeads(Vec<NetworkHead>),
 }
 
 #[cfg(test)]
 mod tests {
-
+    pub extern crate bincode;
     use super::network_endpoint::*;
     use super::*;
 
+    pub fn keypair1() -> ed25519::KeyPair {
+        ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters().generate(
+            "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV".as_bytes(),
+            "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV_".as_bytes(),
+        )
+    }
+
     #[test]
     fn parse_endpoint() {
         let issuer = PubKey::Ed25519(
             ed25519::PublicKey::from_base58("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx")
                 .unwrap(),
         );
-        let node_id = NodeUUID(u32::from_str_radix("c1c39a0a", 16).unwrap());
+        let node_id = NodeId(u32::from_str_radix("c1c39a0a", 16).unwrap());
         let full_id = NodeFullId(node_id, issuer);
         assert_eq!(
-            NetworkEndpoint::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0),
-            Some(NetworkEndpoint::V1(NetworkEndpointV1 {
+            EndpointEnum::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0, 1),
+            Ok(EndpointEnum::V1(EndpointEnumV1 {
                 version: 1,
                 issuer,
                 api: NetworkEndpointApi(String::from("WS2P")),
@@ -366,11 +390,11 @@ mod tests {
             ed25519::PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv")
                 .unwrap(),
         );
-        let node_id = NodeUUID(u32::from_str_radix("cb06a19b", 16).unwrap());
+        let node_id = NodeId(u32::from_str_radix("cb06a19b", 16).unwrap());
         let full_id = NodeFullId(node_id, issuer);
         assert_eq!(
-            NetworkEndpoint::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0),
-            Some(NetworkEndpoint::V1(NetworkEndpointV1 {
+            EndpointEnum::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0, 1),
+            Ok(EndpointEnum::V1(EndpointEnumV1 {
                 version: 1,
                 issuer,
                 api: NetworkEndpointApi(String::from("WS2P")),
diff --git a/network/network_endpoint.rs b/network/network_endpoint.rs
index 6e5ccc23701123ba3ff570ba11a4dfafc2811d00..e33a864b42789ba07c7eee8e74a460f04db8a042 100644
--- a/network/network_endpoint.rs
+++ b/network/network_endpoint.rs
@@ -23,9 +23,53 @@ extern crate regex;
 extern crate serde;
 
 use self::regex::Regex;
-use super::{NodeFullId, NodeUUID};
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::PubKey;
-use duniter_documents::Hash;
+use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr};
+use std::num::ParseIntError;
+use std::str::FromStr;
+use {NodeFullId, NodeId};
+
+/// Total size of all fixed size fields of an EndpointV2
+pub static ENDPOINTV2_FIXED_SIZE: &'static usize = &9;
+/// Maximum number of network features
+pub static MAX_NETWORK_FEATURES_COUNT: &'static usize = &2040;
+/// Maximum number of api features
+pub static MAX_API_FEATURES_COUNT: &'static usize = &2040;
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+/// ApiFeatures
+pub struct ApiFeatures(pub Vec<u8>);
+
+impl ApiFeatures {
+    fn to_string_for_api(&self, api: &NetworkEndpointApi) -> String {
+        match api.0.as_str() {
+            "WS2P" => {
+                let mut af_count = 0;
+                let def = if self.0[0] | 0b_1111_1110 == 255u8 {
+                    af_count += 1;
+                    " DEF"
+                } else {
+                    ""
+                };
+                let low = if self.0[0] | 0b_1111_1101 == 255u8 {
+                    af_count += 1;
+                    " LOW"
+                } else {
+                    ""
+                };
+                let abf = if self.0[0] | 0b_1111_1011 == 255u8 {
+                    af_count += 1;
+                    " ABF"
+                } else {
+                    ""
+                };
+                format!("{}{}{}{}", af_count, def, low, abf)
+            }
+            _ => String::from(""),
+        }
+    }
+}
 
 lazy_static! {
     #[derive(Debug)]
@@ -35,19 +79,87 @@ lazy_static! {
     ).unwrap();
 }
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+/// ParseEndpointError
+pub enum ParseEndpointError {
+    /// VersionNotSupported
+    VersionNotSupported(),
+    /// WrongV1Format
+    WrongV1Format(),
+    /// WrongV2Format (human-readable explanation)
+    WrongV2Format(&'static str),
+    /// ApiNameTooLong
+    ApiNameTooLong(),
+    /// ParseIntError
+    ParseIntError(ParseIntError),
+    /// UnknowNetworkFeature (feature name)
+    UnknowNetworkFeature(String),
+    /// Maximum number of network features exceeded
+    MaxNetworkFeatures(),
+    /// Maximum number of api features exceeded
+    MaxApiFeatures(),
+    /// UnknowApiFeature (feature name)
+    UnknowApiFeature(String),
+    /// TooHighApiFeature
+    TooHighApiFeature(),
+    /// IP Parse error
+    AddrParseError(AddrParseError),
+}
+
+impl From<ParseIntError> for ParseEndpointError {
+    fn from(e: ParseIntError) -> Self {
+        ParseEndpointError::ParseIntError(e)
+    }
+}
+
+impl From<AddrParseError> for ParseEndpointError {
+    fn from(e: AddrParseError) -> Self {
+        ParseEndpointError::AddrParseError(e)
+    }
+}
+
+#[derive(Debug)]
+/// Error when converting a byte vector to Endpoint
+pub enum EndpointReadBytesError {
+    /// Bytes vector is too short
+    TooShort(),
+    /// Bytes vector is too long
+    TooLong(),
+    /// Wrong api datas Length
+    WrongApiDatasLen(),
+    /// Unknow api name
+    UnknowApiName(),
+    /// IoError
+    IoError(::std::io::Error),
+    /// FromUtf8Error
+    FromUtf8Error(::std::string::FromUtf8Error),
+}
+
+impl From<::std::io::Error> for EndpointReadBytesError {
+    fn from(e: ::std::io::Error) -> Self {
+        EndpointReadBytesError::IoError(e)
+    }
+}
+
+impl From<::std::string::FromUtf8Error> for EndpointReadBytesError {
+    fn from(e: ::std::string::FromUtf8Error) -> Self {
+        EndpointReadBytesError::FromUtf8Error(e)
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// Identifies the API of an endpoint
 pub struct NetworkEndpointApi(pub String);
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// Endpoint v1
-pub struct NetworkEndpointV1 {
+pub struct EndpointEnumV1 {
     /// API version
     pub version: usize,
     /// API Name
     pub api: NetworkEndpointApi,
     /// Node unique identifier
-    pub node_id: Option<NodeUUID>,
+    pub node_id: Option<NodeId>,
     /// Public key of the node declaring this endpoint
     pub issuer: PubKey,
     /// NodeFullID hash
@@ -66,44 +178,482 @@ pub struct NetworkEndpointV1 {
     pub last_check: u64,
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Network features
+pub struct EndpointV2NetworkFeatures(pub Vec<u8>);
+
+impl ToString for EndpointV2NetworkFeatures {
+    fn to_string(&self) -> String {
+        if self.tls() {
+            String::from("1 TLS")
+        } else if self.tor() {
+            String::from("1 TOR")
+        } else {
+            String::from("")
+        }
+    }
+}
+
+impl EndpointV2NetworkFeatures {
+    /// Parse network features from utf8 string's array
+    pub fn from_str_array(
+        str_array: &[&str],
+    ) -> Result<EndpointV2NetworkFeatures, ParseEndpointError> {
+        let mut network_features = 0u8;
+        for nf_str in str_array {
+            match *nf_str {
+                "IP4" => network_features += 1u8,
+                "IP6" => network_features += 2u8,
+                "TLS" => network_features += 4u8,
+                "TOR" => network_features += 8u8,
+                &_ => {
+                    return Err(ParseEndpointError::UnknowNetworkFeature(String::from(
+                        *nf_str,
+                    )))
+                }
+            }
+        }
+        Ok(EndpointV2NetworkFeatures(vec![network_features]))
+    }
+    /// Network features size
+    pub fn size(&self) -> u8 {
+        self.0.len() as u8
+    }
+    /// Convert Self into bytes
+    pub fn to_bytes_slice(&self) -> &[u8] {
+        &self.0
+    }
+    /// network feature ip_v4 is enable ?
+    pub fn ip_v4(&self) -> bool {
+        self.0[0] & 0b0000_0001 == 1u8
+    }
+    /// network feature ip_v6 is enable ?
+    pub fn ip_v6(&self) -> bool {
+        self.0[0] & 0b0000_0010 == 2u8
+    }
+    /// TLS feature is enable ?
+    pub fn tls(&self) -> bool {
+        self.0[0] & 0b0000_0100 == 4u8
+    }
+    /// TOR feature is enable ?
+    pub fn tor(&self) -> bool {
+        self.0[0] & 0b0000_1000 == 8u8
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Endpoint v2
+pub struct Endpoint {
+    /// Endpoint content
+    pub content: EndpointEnum,
+    /// Accessibility status of this endpoint  (updated regularly)
+    pub status: u32,
+    /// Timestamp of the last connection attempt to this endpoint
+    pub last_check: u64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Endpoint v2
+pub struct EndpointV2 {
+    /// API Name
+    pub api: NetworkEndpointApi,
+    /// API version
+    pub api_version: u16,
+    /// Network features
+    pub network_features: EndpointV2NetworkFeatures,
+    /// API features
+    pub api_features: ApiFeatures,
+    /// IPv4
+    pub ip_v4: Option<Ipv4Addr>,
+    /// IPv6
+    pub ip_v6: Option<Ipv6Addr>,
+    /// hostname
+    pub host: Option<String>,
+    /// port number
+    pub port: u16,
+    /// Optional path
+    pub path: Option<String>,
+}
+
+impl ToString for EndpointV2 {
+    fn to_string(&self) -> String {
+        let host: String = if let Some(ref host) = self.host {
+            format!("{} ", host)
+        } else {
+            String::from("")
+        };
+        let ip4: String = if let Some(ip4) = self.ip_v4 {
+            format!("{} ", ip4.to_string())
+        } else {
+            String::from("")
+        };
+        let ip6: String = if let Some(ip6) = self.ip_v6 {
+            format!("[{}] ", ip6.to_string())
+        } else {
+            String::from("")
+        };
+        let path: &str = if let Some(ref path) = self.path {
+            path
+        } else {
+            ""
+        };
+        format!(
+            "{api} {version} {nf} {af} {port} {host}{ip4}{ip6}{path}",
+            api = self.api.0,
+            version = self.api_version,
+            nf = self.network_features.to_string(),
+            af = self.api_features.to_string_for_api(&self.api),
+            port = self.port,
+            host = host,
+            ip4 = ip4,
+            ip6 = ip6,
+            path = path,
+        )
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Size informations of Endpoint v2
+pub struct EndpointV2Size {
+    /// Api nalme size
+    pub api_size: u8,
+    /// Hostname size
+    pub host_size: u8,
+    /// Optional path size
+    pub path_size: u8,
+    /// Network features size
+    pub nf_size: u8,
+    /// Network feature ip_v4
+    pub ip_v4: bool,
+    /// Network feature ip_v6
+    pub ip_v6: bool,
+    /// Api features size
+    pub af_size: u8,
+}
+
+impl EndpointV2Size {
+    /// Compute total size of endpoint in binary format
+    pub fn total_size(self) -> usize {
+        let mut total_size = self.api_size as usize
+            + self.host_size as usize
+            + self.path_size as usize
+            + self.nf_size as usize
+            + self.af_size as usize
+            + ENDPOINTV2_FIXED_SIZE;
+        if self.api_size == 0u8 {
+            total_size += 1;
+        }
+        if self.ip_v4 {
+            total_size += 4;
+        }
+        if self.ip_v6 {
+            total_size += 16;
+        }
+        total_size
+    }
+}
+
+impl EndpointV2 {
+    /// Generate endpoint url
+    pub fn get_url(&self, get_protocol: bool, supported_ip_v6: bool) -> Option<String> {
+        let protocol = self.api.0.clone();
+        let tls = match self.port {
+            443 => "s",
+            _ => "",
+        };
+        let host = if let Some(ref host) = self.host {
+            host.clone()
+        } else if supported_ip_v6 && self.ip_v6.is_some() {
+            let ip_v6 = self.ip_v6.unwrap();
+            format!("{}", ip_v6)
+        } else if self.ip_v4.is_some() {
+            let ip_v4 = self.ip_v4.unwrap();
+            format!("{}", ip_v4)
+        } else {
+            // Unreacheable endpoint
+            return None;
+        };
+        let path = match self.path {
+            Some(ref path_string) => path_string.clone(),
+            None => String::new(),
+        };
+        if get_protocol {
+            Some(format!(
+                "{}{}://{}:{}/{}",
+                protocol, tls, host, self.port, path
+            ))
+        } else {
+            Some(format!("{}:{}/{}", host, self.port, path))
+        }
+    }
+    /// parse from ut8 format
+    pub fn parse_from_raw(
+        raw_endpoint: &str,
+        _status: u32,
+        _last_check: u64,
+    ) -> Result<EndpointEnum, ParseEndpointError> {
+        let raw_ep_elements: Vec<&str> = raw_endpoint.split(' ').collect();
+        if raw_ep_elements.len() >= 6 {
+            let api = NetworkEndpointApi(String::from(raw_ep_elements[0]));
+            let api_version: u16 = raw_ep_elements[1].parse()?;
+            let network_features_count: usize = raw_ep_elements[2].parse()?;
+            if network_features_count > *MAX_NETWORK_FEATURES_COUNT {
+                Err(ParseEndpointError::MaxNetworkFeatures())
+            } else if raw_ep_elements.len() >= 6 + network_features_count {
+                let network_features = EndpointV2NetworkFeatures::from_str_array(
+                    &raw_ep_elements[3..(3 + network_features_count)],
+                )?;
+                let api_features_count: usize =
+                    raw_ep_elements[3 + network_features_count].parse()?;
+                if network_features_count > *MAX_API_FEATURES_COUNT {
+                    Err(ParseEndpointError::MaxApiFeatures())
+                } else {
+                    let mut af_bytes_count = network_features_count / 8;
+                    if network_features_count % 8 != 0 {
+                        af_bytes_count += 1;
+                    }
+                    let mut api_features = vec![0u8; af_bytes_count];
+                    if raw_ep_elements.len() < 4 + network_features_count + api_features_count {
+                        return Err(ParseEndpointError::WrongV2Format(
+                            "All api features must be declared !",
+                        ));
+                    }
+                    for str_feature in raw_ep_elements
+                        .iter()
+                        .take(4 + network_features_count + api_features_count)
+                        .skip(4 + network_features_count)
+                    {
+                        if let Ok(feature) = str_feature.parse::<usize>() {
+                            if feature > *MAX_API_FEATURES_COUNT {
+                                return Err(ParseEndpointError::TooHighApiFeature());
+                            }
+                            let byte_index = feature / 8;
+                            let feature = (feature % 8) as u8;
+                            api_features[byte_index] += feature.pow(2);
+                        } else if &api.0 == "WS2P" {
+                            match *str_feature {
+                                "DEF" => api_features[0] += 1u8,
+                                "LOW" => api_features[0] += 2u8,
+                                "ABF" => api_features[0] += 4u8,
+                                _ => {
+                                    return Err(ParseEndpointError::UnknowApiFeature(String::from(
+                                        *str_feature,
+                                    )))
+                                }
+                            }
+                        } else {
+                            return Err(ParseEndpointError::UnknowApiFeature(String::from(
+                                *str_feature,
+                            )));
+                        }
+                    }
+                    let mut index = 4 + network_features_count + api_features_count;
+                    let port = if let Ok(port) = raw_ep_elements[index].parse::<u16>() {
+                        index += 1;
+                        port
+                    } else {
+                        return Err(ParseEndpointError::WrongV2Format(
+                            "Missing port or is not integer !",
+                        ));
+                    };
+                    // HOST IP4 [IP6] PATH
+                    let (host, ip_v4, ip_v6, path) = if raw_ep_elements.len() == index + 4 {
+                        // HOST IP4 [IP6] PATH
+                        let len2 = raw_ep_elements[index + 2].len();
+                        (
+                            Some(String::from(raw_ep_elements[index])),
+                            Some(Ipv4Addr::from_str(raw_ep_elements[index + 1])?),
+                            Some(Ipv6Addr::from_str(
+                                &raw_ep_elements[index + 2][1..len2 - 1],
+                            )?),
+                            Some(String::from(raw_ep_elements[index + 3])),
+                        )
+                    } else if raw_ep_elements.len() == index + 3 {
+                        // IP4 [IP6] PATH
+                        if let Ok(ip_v4) = Ipv4Addr::from_str(raw_ep_elements[index]) {
+                            let len1 = raw_ep_elements[index + 1].len();
+                            (
+                                None,
+                                Some(ip_v4),
+                                Some(Ipv6Addr::from_str(
+                                    &raw_ep_elements[index + 1][1..len1 - 1],
+                                )?),
+                                Some(String::from(raw_ep_elements[index + 2])),
+                            )
+                        } else {
+                            let len1 = raw_ep_elements[index + 1].len();
+                            let len2 = raw_ep_elements[index + 2].len();
+                            if let Some('[') = raw_ep_elements[index + 1].chars().next() {
+                                // HOST [IP6] PATH
+                                (
+                                    Some(String::from(raw_ep_elements[index])),
+                                    None,
+                                    Some(Ipv6Addr::from_str(
+                                        &raw_ep_elements[index + 1][1..len1 - 1],
+                                    )?),
+                                    Some(String::from(raw_ep_elements[index + 2])),
+                                )
+                            } else if let Some('[') = raw_ep_elements[index + 2].chars().next() {
+                                // HOST IP4 [IP6]
+                                (
+                                    Some(String::from(raw_ep_elements[index])),
+                                    Some(Ipv4Addr::from_str(raw_ep_elements[index + 1])?),
+                                    Some(Ipv6Addr::from_str(
+                                        &raw_ep_elements[index + 2][1..len2 - 1],
+                                    )?),
+                                    None,
+                                )
+                            } else {
+                                // HOST IP4 PATH
+                                (
+                                    Some(String::from(raw_ep_elements[index])),
+                                    Some(Ipv4Addr::from_str(raw_ep_elements[index + 1])?),
+                                    None,
+                                    Some(String::from(raw_ep_elements[index + 2])),
+                                )
+                            }
+                        }
+                    } else if raw_ep_elements.len() == index + 2 {
+                        let len0 = raw_ep_elements[index].len();
+                        let len1 = raw_ep_elements[index + 1].len();
+                        if let Ok(ip_v4) = Ipv4Addr::from_str(raw_ep_elements[index]) {
+                            if let Some('[') = raw_ep_elements[index + 1].chars().next() {
+                                // IP4 [IP6]
+                                (
+                                    None,
+                                    Some(ip_v4),
+                                    Some(Ipv6Addr::from_str(
+                                        &raw_ep_elements[index + 1][1..len1 - 1],
+                                    )?),
+                                    None,
+                                )
+                            } else {
+                                // IP4 PATH
+                                (
+                                    None,
+                                    Some(ip_v4),
+                                    None,
+                                    Some(String::from(raw_ep_elements[index + 1])),
+                                )
+                            }
+                        } else if let Some('[') = raw_ep_elements[index].chars().next() {
+                            // [IP6] PATH
+                            (
+                                None,
+                                None,
+                                Some(Ipv6Addr::from_str(&raw_ep_elements[index][1..len0 - 1])?),
+                                Some(String::from(raw_ep_elements[index + 1])),
+                            )
+                        } else if let Ok(ip_v4) = Ipv4Addr::from_str(raw_ep_elements[index + 1]) {
+                            // HOST IP4
+                            (
+                                Some(String::from(raw_ep_elements[index])),
+                                Some(ip_v4),
+                                None,
+                                None,
+                            )
+                        } else if let Some('[') = raw_ep_elements[index + 1].chars().next() {
+                            // HOST [IP6]
+                            (
+                                Some(String::from(raw_ep_elements[index])),
+                                None,
+                                Some(Ipv6Addr::from_str(
+                                    &raw_ep_elements[index + 1][1..len1 - 1],
+                                )?),
+                                None,
+                            )
+                        } else {
+                            // HOST PATH
+                            (
+                                Some(String::from(raw_ep_elements[index])),
+                                None,
+                                None,
+                                Some(String::from(raw_ep_elements[index + 1])),
+                            )
+                        }
+                    } else if raw_ep_elements.len() == index + 1 {
+                        let len0 = raw_ep_elements[index].len();
+                        if let Some('[') = raw_ep_elements[index].chars().next() {
+                            // IP6
+                            (
+                                None,
+                                None,
+                                Some(Ipv6Addr::from_str(&raw_ep_elements[index][1..len0])?),
+                                None,
+                            )
+                        } else if let Ok(ip_v4) = Ipv4Addr::from_str(raw_ep_elements[index]) {
+                            // IP4
+                            (None, Some(ip_v4), None, None)
+                        } else {
+                            // HOST
+                            (Some(String::from(raw_ep_elements[index])), None, None, None)
+                        }
+                    } else {
+                        return Err(ParseEndpointError::WrongV2Format("Invalid fields count !"));
+                    };
+                    Ok(EndpointEnum::V2(EndpointV2 {
+                        api,
+                        api_version,
+                        network_features,
+                        api_features: ApiFeatures(api_features.to_vec()),
+                        ip_v4,
+                        ip_v6,
+                        host,
+                        port,
+                        path,
+                    }))
+                }
+            } else {
+                Err(ParseEndpointError::WrongV2Format(
+                    "All network features must be declared !",
+                ))
+            }
+        } else {
+            Err(ParseEndpointError::WrongV2Format(
+                "An endpoint must contain at least 6 elements",
+            ))
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// Endpoint
-pub enum NetworkEndpoint {
+pub enum EndpointEnum {
     /// Endpoint v1
-    V1(NetworkEndpointV1),
+    V1(EndpointEnumV1),
     /// Endpoint v2
-    V2(),
+    V2(EndpointV2),
 }
 
-impl ToString for NetworkEndpoint {
+impl ToString for EndpointEnum {
     fn to_string(&self) -> String {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(),
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.raw_endpoint.clone(),
+            EndpointEnum::V2(ref ep) => ep.to_string(),
         }
     }
 }
 
-impl NetworkEndpoint {
+impl EndpointEnum {
     /// Accessors providing API name
     pub fn api(&self) -> NetworkEndpointApi {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.api.clone(),
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.api.clone(),
+            EndpointEnum::V2(ref ep) => ep.api.clone(),
         }
     }
     /// Accessors providing node unique identifier
-    pub fn node_uuid(&self) -> Option<NodeUUID> {
+    pub fn node_uuid(&self) -> Option<NodeId> {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.node_id,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.node_id,
+            EndpointEnum::V2(ref _ep) => unreachable!(),
         }
     }
     /// Accessors providing node public key
     pub fn pubkey(&self) -> PubKey {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.issuer,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.issuer,
+            EndpointEnum::V2(ref _ep) => unreachable!(),
         }
     }
     /// Accessors providing node full identifier
@@ -116,42 +666,42 @@ impl NetworkEndpoint {
     /// Accessors providing port number
     pub fn port(&self) -> usize {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.port,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.port,
+            EndpointEnum::V2(ref ep) => ep.port as usize,
         }
     }
     /// Accessors providing raw format
     pub fn raw(&self) -> String {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(),
+            EndpointEnum::V1(ref ep) => ep.raw_endpoint.clone(),
             _ => panic!("Endpoint version is not supported !"),
         }
     }
     /// Accessors providing endpoint accessibility status
     pub fn status(&self) -> u32 {
         match *self {
-            NetworkEndpoint::V1(ref ep) => ep.status,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref ep) => ep.status,
+            EndpointEnum::V2(ref _ep) => unreachable!(),
         }
     }
     /// Set status
     pub fn set_status(&mut self, new_status: u32) {
         match *self {
-            NetworkEndpoint::V1(ref mut ep) => ep.status = new_status,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref mut ep) => ep.status = new_status,
+            EndpointEnum::V2(ref _ep) => unreachable!(),
         }
     }
     /// Set last_check
     pub fn set_last_check(&mut self, new_last_check: u64) {
         match *self {
-            NetworkEndpoint::V1(ref mut ep) => ep.last_check = new_last_check,
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V1(ref mut ep) => ep.last_check = new_last_check,
+            EndpointEnum::V2(ref _ep) => unreachable!(),
         }
     }
     /// Generate endpoint url
-    pub fn get_url(&self, get_protocol: bool) -> String {
+    pub fn get_url(&self, get_protocol: bool, supported_ip_v6: bool) -> Option<String> {
         match *self {
-            NetworkEndpoint::V1(ref ep) => {
+            EndpointEnum::V1(ref ep) => {
                 let protocol = match &ep.api.0[..] {
                     "WS2P" | "WS2PTOR" => "ws",
                     _ => "http",
@@ -165,12 +715,15 @@ impl NetworkEndpoint {
                     None => String::new(),
                 };
                 if get_protocol {
-                    format!("{}{}://{}:{}/{}", protocol, tls, ep.host, ep.port, path)
+                    Some(format!(
+                        "{}{}://{}:{}/{}",
+                        protocol, tls, ep.host, ep.port, path
+                    ))
                 } else {
-                    format!("{}:{}/{}", ep.host, ep.port, path)
+                    Some(format!("{}:{}/{}", ep.host, ep.port, path))
                 }
             }
-            _ => panic!("Endpoint version is not supported !"),
+            EndpointEnum::V2(ref ep_v2) => ep_v2.get_url(get_protocol, supported_ip_v6),
         }
     }
     /// Parse Endpoint from raw format
@@ -179,38 +732,132 @@ impl NetworkEndpoint {
         issuer: PubKey,
         status: u32,
         last_check: u64,
-    ) -> Option<NetworkEndpoint> {
-        match ENDPOINT_V1_REGEX.captures(raw_endpoint) {
-            Some(caps) => {
-                let node_id = match caps.name("uuid") {
-                    Some(caps_node_id) => match u32::from_str_radix(caps_node_id.as_str(), 16) {
-                        Ok(node_id) => Some(NodeUUID(node_id)),
-                        Err(_) => None,
-                    },
-                    None => None,
-                };
-                let hash_full_id = match node_id {
-                    Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()),
-                    None => None,
-                };
-                Some(NetworkEndpoint::V1(NetworkEndpointV1 {
-                    version: 1,
-                    issuer,
-                    api: NetworkEndpointApi(String::from(&caps["api"])),
-                    node_id,
-                    hash_full_id,
-                    host: String::from(&caps["host"]),
-                    port: caps["port"].parse().unwrap_or(80),
-                    path: match caps.name("path") {
-                        Some(m) => Some(m.as_str().to_string()),
+        endpoint_version: u16,
+    ) -> Result<EndpointEnum, ParseEndpointError> {
+        match endpoint_version {
+            1 => match ENDPOINT_V1_REGEX.captures(raw_endpoint) {
+                Some(caps) => {
+                    let node_id = match caps.name("uuid") {
+                        Some(caps_node_id) => {
+                            match u32::from_str_radix(caps_node_id.as_str(), 16) {
+                                Ok(node_id) => Some(NodeId(node_id)),
+                                Err(_) => None,
+                            }
+                        }
                         None => None,
-                    },
-                    raw_endpoint: String::from(raw_endpoint),
-                    status,
-                    last_check,
-                }))
-            }
-            None => None,
+                    };
+                    let hash_full_id = match node_id {
+                        Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()),
+                        None => None,
+                    };
+                    Ok(EndpointEnum::V1(EndpointEnumV1 {
+                        version: 1,
+                        issuer,
+                        api: NetworkEndpointApi(String::from(&caps["api"])),
+                        node_id,
+                        hash_full_id,
+                        host: String::from(&caps["host"]),
+                        port: caps["port"].parse().unwrap_or(80),
+                        path: match caps.name("path") {
+                            Some(m) => Some(m.as_str().to_string()),
+                            None => None,
+                        },
+                        raw_endpoint: String::from(raw_endpoint),
+                        status,
+                        last_check,
+                    }))
+                }
+                None => Err(ParseEndpointError::WrongV1Format()),
+            },
+            2 => EndpointV2::parse_from_raw(raw_endpoint, status, last_check),
+            _ => Err(ParseEndpointError::VersionNotSupported()),
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use tests::bincode::{deserialize, serialize};
+
+    fn test_parse_and_read_endpoint(str_endpoint: &str, endpoint: EndpointV2) {
+        assert_eq!(
+            EndpointV2::parse_from_raw(str_endpoint, 0, 0),
+            Ok(EndpointEnum::V2(endpoint.clone())),
+        );
+        let binary_endpoint = serialize(&endpoint).expect("Fail to serialize endpoint !");
+        let endpoint2: EndpointV2 =
+            deserialize(&binary_endpoint).expect("Fail to deserialize endpoint !");
+        assert_eq!(endpoint, endpoint2,);
+        assert_eq!(str_endpoint, endpoint.to_string());
+    }
+
+    #[test]
+    fn test_parse_and_read_endpoint_with_host() {
+        let str_endpoint = "WS2P 2 1 TLS 3 DEF LOW ABF 443 g1.durs.ifee.fr ws2p";
+        let endpoint = EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: None,
+            ip_v6: None,
+            host: Some(String::from("g1.durs.ifee.fr")),
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        };
+        test_parse_and_read_endpoint(str_endpoint, endpoint);
+    }
+
+    #[test]
+    fn test_parse_and_read_endpoint_with_ipv4() {
+        let str_endpoint = "WS2P 2 1 TLS 3 DEF LOW ABF 443 84.16.72.210 ws2p";
+        let endpoint = EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: Some(Ipv4Addr::from_str("84.16.72.210").unwrap()),
+            ip_v6: None,
+            host: None,
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        };
+        test_parse_and_read_endpoint(str_endpoint, endpoint);
+    }
+
+    #[test]
+    fn test_parse_and_read_endpoint_with_ipv6() {
+        let str_endpoint = "WS2P 2 1 TLS 3 DEF LOW ABF 443 [2001:41d0:8:c5aa::1] ws2p";
+        let endpoint = EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: None,
+            ip_v6: Some(Ipv6Addr::from_str("2001:41d0:8:c5aa::1").unwrap()),
+            host: None,
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        };
+        test_parse_and_read_endpoint(str_endpoint, endpoint);
+    }
+
+    #[test]
+    fn test_parse_and_read_endpoint_with_ipv4_and_ip_v6() {
+        let str_endpoint =
+            "WS2P 2 1 TLS 3 DEF LOW ABF 443 5.135.188.170 [2001:41d0:8:c5aa::1] ws2p";
+        let endpoint = EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: Some(Ipv4Addr::from_str("5.135.188.170").unwrap()),
+            ip_v6: Some(Ipv6Addr::from_str("2001:41d0:8:c5aa::1").unwrap()),
+            host: None,
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        };
+        test_parse_and_read_endpoint(str_endpoint, endpoint);
+    }
+}
diff --git a/network/network_head.rs b/network/network_head.rs
index 91bf2843e11cbcffa64cd1232f6b00c40576271a..f9ea9db57a6f83de7e41186d607daef04c4798e9 100644
--- a/network/network_head.rs
+++ b/network/network_head.rs
@@ -1,4 +1,4 @@
-//  Copyright (C) 2017  The Duniter Project Developers.
+//  Copyright (C) 2017  The Durs Project Developers.
 //
 // This program is free software: you can redistribute it and/or modify
 // it under the terms of the GNU Affero General Public License as
@@ -15,299 +15,78 @@
 
 //! Module defining the format of network heads and how to handle them.
 
-extern crate duniter_crypto;
-extern crate duniter_documents;
-extern crate serde_json;
-
-use super::{NodeFullId, NodeUUID};
 use duniter_crypto::keys::*;
+use duniter_documents::BlockUIdParseError;
 use duniter_documents::Blockstamp;
-use std::cmp::Ordering;
+use network_head_v2::*;
+use network_head_v3::*;
+use serde_json;
 use std::collections::HashMap;
+use std::num::ParseIntError;
 use std::ops::Deref;
+use std::str::FromStr;
+use {NodeFullId, NodeId};
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-/// Head Message V2
-pub struct NetworkHeadMessageV2 {
-    /// API details
-    pub api: String,
-    /// Head version
-    pub version: usize,
-    /// Head pubkey
-    pub pubkey: PubKey,
-    /// Head blockstamp
-    pub blockstamp: Blockstamp,
-    /// Head node id
-    pub node_uuid: NodeUUID,
-    /// Issuer node software
-    pub software: String,
-    /// Issuer node soft version
-    pub soft_version: String,
-    /// Issuer node prefix
-    pub prefix: usize,
-    /// Issuer node free member room
-    pub free_member_room: Option<usize>,
-    /// Issuer node free mirror room
-    pub free_mirror_room: Option<usize>,
-}
-
-impl PartialOrd for NetworkHeadMessageV2 {
-    fn partial_cmp(&self, other: &NetworkHeadMessageV2) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for NetworkHeadMessageV2 {
-    fn cmp(&self, other: &NetworkHeadMessageV2) -> Ordering {
-        self.blockstamp.cmp(&other.blockstamp)
-    }
-}
-
-impl NetworkHeadMessageV2 {
-    /// To human readable string
-    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
-        let short_api = &self.api[4..];
-
-        if max_len > 85 && uid.is_some() {
-            format!(
-                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} {pre:3} [{api:5}]  {mer:02}:{mir:02} {uid}",
-                node_id = self.node_uuid.to_string(),
-                pubkey = self.pubkey.to_string(),
-                blockstamp = self.blockstamp.to_string(),
-                soft = self.software,
-                ver = self.soft_version,
-                pre = self.prefix,
-                api = short_api,
-                mer = self.free_member_room.unwrap_or(0),
-                mir = self.free_mirror_room.unwrap_or(0),
-                uid = uid.unwrap(),
-            )
-        } else if max_len > 75 {
-            format!(
-                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} {pre:3} [{api:5}]  {mer:02}:{mir:02}",
-                node_id = self.node_uuid.to_string(),
-                pubkey = self.pubkey.to_string(),
-                blockstamp = self.blockstamp.to_string(),
-                soft = self.software,
-                ver = self.soft_version,
-                pre = self.prefix,
-                api = short_api,
-                mer = self.free_member_room.unwrap_or(0),
-                mir = self.free_mirror_room.unwrap_or(0),
-            )
-        } else if max_len > 70 {
-            format!(
-                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} [{api:5}]  {mer:02}:{mir:02}",
-                node_id = self.node_uuid.to_string(),
-                pubkey = self.pubkey.to_string(),
-                blockstamp = self.blockstamp.to_string(),
-                soft = self.software,
-                ver = self.soft_version,
-                api = short_api,
-                mer = self.free_member_room.unwrap_or(0),
-                mir = self.free_mirror_room.unwrap_or(0),
-            )
-        } else if max_len > 47 {
-            format!(
-                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]  {mer:02}:{mir:02}",
-                node_id = self.node_uuid.to_string(),
-                pubkey = self.pubkey.to_string(),
-                blockstamp = self.blockstamp.to_string(),
-                api = short_api,
-                mer = self.free_member_room.unwrap_or(0),
-                mir = self.free_mirror_room.unwrap_or(0),
-            )
-        } else if max_len > 41 {
-            format!(
-                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]",
-                node_id = self.node_uuid.to_string(),
-                pubkey = self.pubkey.to_string(),
-                blockstamp = self.blockstamp.to_string(),
-                api = short_api,
-            )
-        } else {
-            String::from("Term width insufficient")
-        }
-    }
-}
-
-#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
-/// Head Message
-pub enum NetworkHeadMessage {
-    /// Head Message V2
-    V2(NetworkHeadMessageV2),
-    /// Head Message V3
-    V3(),
-}
-
-impl NetworkHeadMessage {
-    /// To human readable string
-    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
-        match *self {
-            NetworkHeadMessage::V2(ref mess_v2) => mess_v2.deref().to_human_string(max_len, uid),
-            _ => panic!("NetworkHead version not supported !"),
-        }
-    }
-    /// Parse head from string
-    fn from_str(source: &str) -> Option<NetworkHeadMessage> {
-        let source_array: Vec<&str> = source.split(':').collect();
-        if let Ok(pubkey) = ed25519::PublicKey::from_base58(&source_array[3].to_string()) {
-            Some(NetworkHeadMessage::V2(NetworkHeadMessageV2 {
-                api: source_array[0].to_string(),
-                version: source_array[2].parse().unwrap(),
-                pubkey: PubKey::Ed25519(pubkey),
-                blockstamp: Blockstamp::from_string(source_array[4]).unwrap(),
-                node_uuid: NodeUUID(u32::from_str_radix(source_array[5], 16).unwrap()),
-                software: source_array[6].to_string(),
-                soft_version: source_array[7].to_string(),
-                prefix: source_array[8].parse().unwrap(),
-                free_member_room: if let Some(field) = source_array.get(9) {
-                    Some(field.parse().unwrap())
-                } else {
-                    None
-                },
-                free_mirror_room: if let Some(field) = source_array.get(10) {
-                    Some(field.parse().unwrap())
-                } else {
-                    None
-                },
-            }))
-        } else {
-            None
-        }
-    }
-    /// Get head blockcstamp
-    fn blockstamp(&self) -> Blockstamp {
-        match *self {
-            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.blockstamp,
-            _ => panic!("This HEAD version is not supported !"),
-        }
-    }
-    /// Get head node id
-    fn node_uuid(&self) -> NodeUUID {
-        match *self {
-            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.node_uuid,
-            _ => panic!("This HEAD version is not supported !"),
-        }
-    }
-    /// Get head issuer public key
-    fn _pubkey(&self) -> PubKey {
-        match *self {
-            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey,
-            _ => panic!("This HEAD version is not supported !"),
-        }
-    }
-}
-
-impl ToString for NetworkHeadMessageV2 {
-    fn to_string(&self) -> String {
-        match self.version {
-            1 => format!(
-                "{}:HEAD:1:{}:{}:{}:{}:{}:{}",
-                self.api,
-                self.pubkey,
-                self.blockstamp,
-                self.node_uuid,
-                self.software,
-                self.soft_version,
-                self.prefix
-            ),
-            2 => format!(
-                "{}:HEAD:2:{}:{}:{}:{}:{}:{}:{}:{}",
-                self.api,
-                self.pubkey,
-                self.blockstamp,
-                self.node_uuid,
-                self.software,
-                self.soft_version,
-                self.prefix,
-                self.free_member_room.unwrap(),
-                self.free_mirror_room.unwrap()
-            ),
-            _ => panic!("NetworkHeadMessage is wrongly parsed !"),
-        }
-    }
+#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Serialize, Deserialize)]
+/// Network Head : Set of information on the current state of a node, the central information being the blockstamp of its current block (= the head of its blockchain).
+pub enum NetworkHead {
+    /// Head V2
+    V2(Box<NetworkHeadV2>),
+    /// head V3
+    V3(Box<NetworkHeadV3Container>),
 }
 
-impl ToString for NetworkHeadMessage {
+impl ToString for NetworkHead {
     fn to_string(&self) -> String {
         match *self {
-            NetworkHeadMessage::V2(ref head_message) => head_message.to_string(),
-            _ => panic!("This HEADMessage version is not supported !"),
+            NetworkHead::V2(ref head_v2) => head_v2.deref().to_string(),
+            _ => panic!("NetworkHead version not supported !"),
         }
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
-/// Head V2
-pub struct NetworkHeadV2 {
-    /// Head V1 Message
-    pub message: NetworkHeadMessage,
-    /// signature of V1 Message
-    pub sig: Sig,
-    /// Head V2 Message
-    pub message_v2: NetworkHeadMessage,
-    /// signature of V2 Message
-    pub sig_v2: Sig,
-    /// Head step
-    pub step: u32,
-    /// Head issuer uid
-    pub uid: Option<String>,
+/// NetworkHeadParseErr parse error
+#[derive(Debug)]
+pub enum NetworkHeadParseErr {
+    /// BaseConvertionError
+    BaseConvertionError(BaseConvertionError),
+    /// ParseIntError
+    ParseIntError(ParseIntError),
+    /// BlockUIdParseError
+    BlockUIdParseError(BlockUIdParseError),
+    /// NetworkHeadMessageParseErr
+    NetworkHeadMessageParseErr(NetworkHeadMessageParseErr),
+    /// InvalidMessageVersion
+    InvalidMessageVersion(),
+    /// InvalidStep
+    InvalidStep(),
+    /// InvalidStr
+    InvalidStr(&'static str),
+    /// MissingField
+    MissingField(&'static str),
 }
 
-impl ToString for NetworkHeadV2 {
-    fn to_string(&self) -> String {
-        self.message_v2.to_string()
+impl From<NetworkHeadMessageParseErr> for NetworkHeadParseErr {
+    fn from(e: NetworkHeadMessageParseErr) -> Self {
+        NetworkHeadParseErr::NetworkHeadMessageParseErr(e)
     }
 }
 
-impl PartialOrd for NetworkHeadV2 {
-    fn partial_cmp(&self, other: &NetworkHeadV2) -> Option<Ordering> {
-        Some(self.cmp(other))
+impl From<BaseConvertionError> for NetworkHeadParseErr {
+    fn from(e: BaseConvertionError) -> Self {
+        NetworkHeadParseErr::BaseConvertionError(e)
     }
 }
 
-impl Ord for NetworkHeadV2 {
-    fn cmp(&self, other: &NetworkHeadV2) -> Ordering {
-        self.message.cmp(&other.message)
+impl From<BlockUIdParseError> for NetworkHeadParseErr {
+    fn from(e: BlockUIdParseError) -> Self {
+        NetworkHeadParseErr::BlockUIdParseError(e)
     }
 }
 
-impl NetworkHeadV2 {
-    /// To human readable string
-    pub fn to_human_string(&self, max_len: usize) -> String {
-        if max_len > 2 {
-            format!(
-                "{} {}",
-                self.step,
-                self.message_v2
-                    .to_human_string(max_len - 2, self.uid.clone())
-            )
-        } else {
-            String::from(".")
-        }
-    }
-    /// Get uid of head issuer
-    pub fn uid(&self) -> Option<String> {
-        self.uid.clone()
-    }
-}
-
-#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)]
-/// Network Head : Set of information on the current state of a node, the central information being the blockstamp of its current block (= the head of its blockchain).
-pub enum NetworkHead {
-    /// Head V2
-    V2(Box<NetworkHeadV2>),
-    /// head V3
-    V3(),
-}
-
-impl ToString for NetworkHead {
-    fn to_string(&self) -> String {
-        match *self {
-            NetworkHead::V2(ref head_v2) => head_v2.deref().to_string(),
-            _ => panic!("NetworkHead version not supported !"),
-        }
+impl From<ParseIntError> for NetworkHeadParseErr {
+    fn from(e: ParseIntError) -> Self {
+        NetworkHeadParseErr::ParseIntError(e)
     }
 }
 
@@ -371,7 +150,7 @@ impl NetworkHead {
         }
     }
     /// Returns issuer node id
-    pub fn node_uuid(&self) -> NodeUUID {
+    pub fn node_uuid(&self) -> NodeId {
         match *self {
             NetworkHead::V2(ref head_v2) => head_v2.message_v2.node_uuid(),
             _ => panic!("This HEAD version is not supported !"),
@@ -405,25 +184,64 @@ impl NetworkHead {
         }
     }
     /// Parse Json Head
-    pub fn from_json_value(source: &serde_json::Value) -> Option<NetworkHead> {
-        let message = NetworkHeadMessage::from_str(source.get("message")?.as_str().unwrap())?;
+    pub fn from_json_value(source: &serde_json::Value) -> Result<NetworkHead, NetworkHeadParseErr> {
+        let message = NetworkHeadMessage::from_str(if let Some(str_msg) = source.get("message") {
+            if let Some(str_msg) = str_msg.as_str() {
+                str_msg
+            } else {
+                return Err(NetworkHeadParseErr::InvalidStr("message"));
+            }
+        } else {
+            return Err(NetworkHeadParseErr::MissingField("message"));
+        })?;
         match message {
-            NetworkHeadMessage::V2(_) => Some(NetworkHead::V2(Box::new(NetworkHeadV2 {
+            NetworkHeadMessage::V2(_) => Ok(NetworkHead::V2(Box::new(NetworkHeadV2 {
                 message,
-                sig: Sig::Ed25519(
-                    ed25519::Signature::from_base64(source.get("sig")?.as_str().unwrap()).unwrap(),
-                ),
-                message_v2: NetworkHeadMessage::from_str(
-                    source.get("messageV2")?.as_str().unwrap(),
-                )?,
-                sig_v2: Sig::Ed25519(
-                    ed25519::Signature::from_base64(source.get("sigV2")?.as_str().unwrap())
-                        .unwrap(),
-                ),
-                step: source.get("step")?.as_u64().unwrap() as u32,
+                sig: Sig::Ed25519(ed25519::Signature::from_base64(if let Some(str_sig) =
+                    source.get("sig")
+                {
+                    if let Some(str_sig) = str_sig.as_str() {
+                        str_sig
+                    } else {
+                        return Err(NetworkHeadParseErr::InvalidStr("sig"));
+                    }
+                } else {
+                    return Err(NetworkHeadParseErr::MissingField("sigV2"));
+                })?),
+                message_v2: NetworkHeadMessage::from_str(if let Some(str_msg) =
+                    source.get("messageV2")
+                {
+                    if let Some(str_msg) = str_msg.as_str() {
+                        str_msg
+                    } else {
+                        return Err(NetworkHeadParseErr::InvalidStr("messageV2"));
+                    }
+                } else {
+                    return Err(NetworkHeadParseErr::MissingField("messageV2"));
+                })?,
+                sig_v2: Sig::Ed25519(ed25519::Signature::from_base64(if let Some(str_sig) =
+                    source.get("sigV2")
+                {
+                    if let Some(str_sig) = str_sig.as_str() {
+                        str_sig
+                    } else {
+                        return Err(NetworkHeadParseErr::InvalidStr("sigV2"));
+                    }
+                } else {
+                    return Err(NetworkHeadParseErr::MissingField("sigV2"));
+                })?),
+                step: if let Some(step) = source.get("step") {
+                    if let Some(step) = step.as_u64() {
+                        step as u32
+                    } else {
+                        return Err(NetworkHeadParseErr::InvalidStep());
+                    }
+                } else {
+                    return Err(NetworkHeadParseErr::MissingField("step"));
+                },
                 uid: None,
             }))),
-            _ => None,
+            _ => Err(NetworkHeadParseErr::InvalidMessageVersion()),
         }
     }
     /// To human readable string
diff --git a/network/network_head_v2.rs b/network/network_head_v2.rs
new file mode 100644
index 0000000000000000000000000000000000000000..25fa81c11786db8b4c0bd9e7e6abd9c89d3589c4
--- /dev/null
+++ b/network/network_head_v2.rs
@@ -0,0 +1,322 @@
+//  Copyright (C) 2017  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Module defining the format of network heads v2 and how to handle them.
+
+use duniter_crypto::keys::*;
+use duniter_documents::BlockUIdParseError;
+use duniter_documents::Blockstamp;
+use std::cmp::Ordering;
+use std::num::ParseIntError;
+use std::ops::Deref;
+use std::str::FromStr;
+use NodeId;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Head Message V2
+pub struct NetworkHeadMessageV2 {
+    /// API details
+    pub api: String,
+    /// Head version
+    pub version: usize,
+    /// Head pubkey
+    pub pubkey: PubKey,
+    /// Head blockstamp
+    pub blockstamp: Blockstamp,
+    /// Head node id
+    pub node_uuid: NodeId,
+    /// Issuer node software
+    pub software: String,
+    /// Issuer node soft version
+    pub soft_version: String,
+    /// Issuer node prefix
+    pub prefix: usize,
+    /// Issuer node free member room
+    pub free_member_room: Option<usize>,
+    /// Issuer node free mirror room
+    pub free_mirror_room: Option<usize>,
+}
+
+impl PartialOrd for NetworkHeadMessageV2 {
+    fn partial_cmp(&self, other: &NetworkHeadMessageV2) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadMessageV2 {
+    fn cmp(&self, other: &NetworkHeadMessageV2) -> Ordering {
+        self.blockstamp.cmp(&other.blockstamp)
+    }
+}
+
+impl NetworkHeadMessageV2 {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
+        let short_api = &self.api[4..];
+
+        if max_len > 85 && uid.is_some() {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} {pre:3} [{api:5}]  {mer:02}:{mir:02} {uid}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                pre = self.prefix,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+                uid = uid.unwrap(),
+            )
+        } else if max_len > 75 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} {pre:3} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                pre = self.prefix,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 70 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:14} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 47 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 41 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                api = short_api,
+            )
+        } else {
+            String::from("Term width insufficient")
+        }
+    }
+}
+
+#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
+/// Head Message
+pub enum NetworkHeadMessage {
+    /// Head Message V2
+    V2(NetworkHeadMessageV2),
+    /// Do not use
+    Other(),
+}
+
+/// NetworkHeadMessage parse error
+#[derive(Debug)]
+pub enum NetworkHeadMessageParseErr {
+    /// BaseConvertionError
+    BaseConvertionError(BaseConvertionError),
+    /// ParseIntError
+    ParseIntError(ParseIntError),
+    /// BlockUIdParseError
+    BlockUIdParseError(BlockUIdParseError),
+}
+
+impl From<BaseConvertionError> for NetworkHeadMessageParseErr {
+    fn from(e: BaseConvertionError) -> Self {
+        NetworkHeadMessageParseErr::BaseConvertionError(e)
+    }
+}
+
+impl From<BlockUIdParseError> for NetworkHeadMessageParseErr {
+    fn from(e: BlockUIdParseError) -> Self {
+        NetworkHeadMessageParseErr::BlockUIdParseError(e)
+    }
+}
+
+impl From<ParseIntError> for NetworkHeadMessageParseErr {
+    fn from(e: ParseIntError) -> Self {
+        NetworkHeadMessageParseErr::ParseIntError(e)
+    }
+}
+
+impl FromStr for NetworkHeadMessage {
+    type Err = NetworkHeadMessageParseErr;
+    fn from_str(source: &str) -> Result<Self, Self::Err> {
+        let source_array: Vec<&str> = source.split(':').collect();
+        Ok(NetworkHeadMessage::V2(NetworkHeadMessageV2 {
+            api: source_array[0].to_string(),
+            version: source_array[2].parse()?,
+            pubkey: PubKey::Ed25519(ed25519::PublicKey::from_base58(
+                &source_array[3].to_string(),
+            )?),
+            blockstamp: Blockstamp::from_string(source_array[4])?,
+            node_uuid: NodeId(u32::from_str_radix(source_array[5], 16)?),
+            software: source_array[6].to_string(),
+            soft_version: source_array[7].to_string(),
+            prefix: source_array[8].parse()?,
+            free_member_room: if let Some(field) = source_array.get(9) {
+                Some(field.parse()?)
+            } else {
+                None
+            },
+            free_mirror_room: if let Some(field) = source_array.get(10) {
+                Some(field.parse()?)
+            } else {
+                None
+            },
+        }))
+    }
+}
+
+impl NetworkHeadMessage {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
+        match *self {
+            NetworkHeadMessage::V2(ref mess_v2) => mess_v2.deref().to_human_string(max_len, uid),
+            _ => panic!("NetworkHead version not supported !"),
+        }
+    }
+    /// Get head blockcstamp
+    pub fn blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.blockstamp,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get head node id
+    pub fn node_uuid(&self) -> NodeId {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.node_uuid,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get head issuer public key
+    fn _pubkey(&self) -> PubKey {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+}
+
+impl ToString for NetworkHeadMessageV2 {
+    fn to_string(&self) -> String {
+        match self.version {
+            1 => format!(
+                "{}:HEAD:1:{}:{}:{}:{}:{}:{}",
+                self.api,
+                self.pubkey,
+                self.blockstamp,
+                self.node_uuid,
+                self.software,
+                self.soft_version,
+                self.prefix
+            ),
+            2 => format!(
+                "{}:HEAD:2:{}:{}:{}:{}:{}:{}:{}:{}",
+                self.api,
+                self.pubkey,
+                self.blockstamp,
+                self.node_uuid,
+                self.software,
+                self.soft_version,
+                self.prefix,
+                self.free_member_room.unwrap(),
+                self.free_mirror_room.unwrap()
+            ),
+            _ => panic!("NetworkHeadMessage is wrongly parsed !"),
+        }
+    }
+}
+
+impl ToString for NetworkHeadMessage {
+    fn to_string(&self) -> String {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message) => head_message.to_string(),
+            _ => panic!("This HEADMessage version is not supported !"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+/// Head V2
+pub struct NetworkHeadV2 {
+    /// Head V1 Message
+    pub message: NetworkHeadMessage,
+    /// signature of V1 Message
+    pub sig: Sig,
+    /// Head V2 Message
+    pub message_v2: NetworkHeadMessage,
+    /// signature of V2 Message
+    pub sig_v2: Sig,
+    /// Head step
+    pub step: u32,
+    /// Head issuer uid
+    pub uid: Option<String>,
+}
+
+impl ToString for NetworkHeadV2 {
+    fn to_string(&self) -> String {
+        self.message_v2.to_string()
+    }
+}
+
+impl PartialOrd for NetworkHeadV2 {
+    fn partial_cmp(&self, other: &NetworkHeadV2) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadV2 {
+    fn cmp(&self, other: &NetworkHeadV2) -> Ordering {
+        self.message.cmp(&other.message)
+    }
+}
+
+impl NetworkHeadV2 {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize) -> String {
+        if max_len > 2 {
+            format!(
+                "{} {}",
+                self.step,
+                self.message_v2
+                    .to_human_string(max_len - 2, self.uid.clone())
+            )
+        } else {
+            String::from(".")
+        }
+    }
+    /// Get uid of head issuer
+    pub fn uid(&self) -> Option<String> {
+        self.uid.clone()
+    }
+}
diff --git a/network/network_head_v3.rs b/network/network_head_v3.rs
new file mode 100644
index 0000000000000000000000000000000000000000..99ac157f48bfa8165809f07a4b5aec9d041977e2
--- /dev/null
+++ b/network/network_head_v3.rs
@@ -0,0 +1,192 @@
+//  Copyright (C) 2017  The Durs Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Module defining the format of network heads v3 and how to handle them.
+
+use base58::ToBase58;
+use duniter_crypto::keys::bin_signable::BinSignable;
+use duniter_crypto::keys::*;
+use duniter_documents::Blockstamp;
+use serde_json;
+use std::cmp::Ordering;
+use NodeId;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Head V3
+pub struct NetworkHeadV3Container {
+    /// Head step
+    pub step: u8,
+    /// head body
+    pub body: NetworkHeadV3,
+}
+
+impl PartialOrd for NetworkHeadV3Container {
+    fn partial_cmp(&self, other: &NetworkHeadV3Container) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadV3Container {
+    fn cmp(&self, other: &NetworkHeadV3Container) -> Ordering {
+        self.body.cmp(&other.body)
+    }
+}
+
+impl NetworkHeadV3Container {
+    /// Convert to JSON String
+    pub fn to_json_head(&self) -> Result<String, serde_json::Error> {
+        Ok(serde_json::to_string_pretty(&JsonHeadV3 {
+            api_outgoing_conf: self.body.api_outgoing_conf,
+            api_incoming_conf: self.body.api_incoming_conf,
+            free_mirror_rooms: self.body.free_mirror_rooms,
+            low_priority_rooms: self.body.low_priority_rooms,
+            node_id: self.body.node_id,
+            algorithm: self.body.pubkey.algo(),
+            pubkey: self.body.pubkey.to_base58(),
+            blockstamp: self.body.blockstamp.to_string(),
+            software: &self.body.software,
+            soft_version: &self.body.soft_version,
+            signature: if let Some(sig) = self.body.signature {
+                Some(sig.to_base64())
+            } else {
+                None
+            },
+            step: self.step,
+        })?)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Head V3
+pub struct NetworkHeadV3 {
+    /// WS2P Private configuration
+    pub api_outgoing_conf: u8,
+    /// WS2P Public configuration
+    pub api_incoming_conf: u8,
+    /// Issuer node free mirror rooms
+    pub free_mirror_rooms: u8,
+    /// Issuer node free "low priority" rooms
+    pub low_priority_rooms: u8,
+    /// Issuer node id
+    pub node_id: NodeId,
+    /// Issuer pubkey
+    pub pubkey: PubKey,
+    /// Head blockstamp
+    pub blockstamp: Blockstamp,
+    /// Issuer node software
+    pub software: String,
+    /// Issuer node soft version
+    pub soft_version: String,
+    /// Issuer signature
+    pub signature: Option<Sig>,
+}
+
+impl PartialOrd for NetworkHeadV3 {
+    fn partial_cmp(&self, other: &NetworkHeadV3) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadV3 {
+    fn cmp(&self, other: &NetworkHeadV3) -> Ordering {
+        self.blockstamp.cmp(&other.blockstamp)
+    }
+}
+
+impl<'de> BinSignable<'de> for NetworkHeadV3 {
+    fn issuer_pubkey(&self) -> PubKey {
+        self.pubkey
+    }
+    fn signature(&self) -> Option<Sig> {
+        self.signature
+    }
+    fn set_signature(&mut self, signature: Sig) {
+        self.signature = Some(signature);
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Head V3 for json serializer
+pub struct JsonHeadV3<'a> {
+    /// WS2P Private configuration
+    pub api_outgoing_conf: u8,
+    /// WS2P Public configuration
+    pub api_incoming_conf: u8,
+    /// Issuer node free mirror rooms
+    pub free_mirror_rooms: u8,
+    /// Issuer node free "low priority" rooms
+    pub low_priority_rooms: u8,
+    /// Issuer node id
+    pub node_id: NodeId,
+    /// Issuer key algorithm
+    pub algorithm: KeysAlgo,
+    /// Issuer pubkey
+    pub pubkey: String,
+    /// Head blockstamp
+    pub blockstamp: String,
+    /// Issuer node software
+    pub software: &'a str,
+    /// Issuer node soft version
+    pub soft_version: &'a str,
+    /// Issuer signature
+    pub signature: Option<String>,
+    /// Head step
+    pub step: u8,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_crypto::keys::bin_signable::BinSignable;
+    use tests::bincode::deserialize;
+    use tests::keypair1;
+
+    #[test]
+    fn head_v3_sign_and_verify() {
+        let mut head_v3 = NetworkHeadV3Container {
+            step: 0,
+            body: NetworkHeadV3 {
+                api_outgoing_conf: 0u8,
+                api_incoming_conf: 0u8,
+                free_mirror_rooms: 0u8,
+                low_priority_rooms: 0u8,
+                node_id: NodeId(0),
+                pubkey: PubKey::Ed25519(keypair1().public_key()),
+                blockstamp: Blockstamp::from_string(
+                    "50-000005B1CEB4EC5245EF7E33101A330A1C9A358EC45A25FC13F78BB58C9E7370",
+                ).unwrap(),
+                software: String::from("durs"),
+                soft_version: String::from("0.1.0-a0.1"),
+                signature: None,
+            },
+        };
+        // Sign
+        let sign_result = head_v3
+            .body
+            .sign(PrivKey::Ed25519(keypair1().private_key()));
+        if let Ok(head_v3_body_bytes) = sign_result {
+            let deser_head_v3_body: NetworkHeadV3 =
+                deserialize(&head_v3_body_bytes).expect("Fail to deserialize PeerCardV11 !");
+            assert_eq!(head_v3.body, deser_head_v3_body,)
+        } else {
+            panic!("failt to sign head v3 : {:?}", sign_result.err().unwrap())
+        }
+        // Verify signature
+        head_v3.body.verify().expect("HEADv3 : Invalid signature !");
+        //let json_head_v3 = head_v3.to_json_head().expect("Fail to serialize HEAD v3 !");
+        //println!("{}", json_head_v3);
+        //panic!();
+    }
+}
diff --git a/network/network_peer.rs b/network/network_peer.rs
index b539af01dcaaff77a0b3f9e4557f0065aa7b5649..8a62a434ed2aa3730b27c2f5b8fb91714a7cdbc1 100644
--- a/network/network_peer.rs
+++ b/network/network_peer.rs
@@ -21,49 +21,122 @@ extern crate duniter_documents;
 extern crate duniter_module;
 extern crate serde;
 
-use super::network_endpoint::NetworkEndpoint;
+use base58::ToBase58;
+use duniter_crypto::keys::bin_signable::BinSignable;
 use duniter_crypto::keys::*;
-use duniter_documents::Blockstamp;
+use duniter_documents::{Blockstamp, CurrencyName};
+use network_endpoint::*;
+use *;
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 /// Peer card V10
-pub struct NetworkPeerV10 {
+pub struct PeerCardV10 {
     /// Peer card Blockstamp
     pub blockstamp: Blockstamp,
     /// Peer card issuer
     pub issuer: PubKey,
     /// Peer card endpoints list
-    pub endpoints: Vec<NetworkEndpoint>,
+    pub endpoints: Vec<EndpointEnum>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Peer card V11
+pub struct PeerCardV11 {
+    /// Currency name
+    pub currency_name: CurrencyName,
+    /// Peer card issuer
+    pub issuer: PubKey,
+    /// Issuer node id
+    pub node_id: NodeId,
+    /// Peer card Blockstamp
+    pub blockstamp: Blockstamp,
+    /// Peer card endpoints list
+    pub endpoints: Vec<EndpointEnum>,
+    /// Signature
+    pub sig: Option<Sig>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Peer card V11 for JSON Serializer
+pub struct JsonPeerCardV11<'a> {
+    /// PeerCard version
+    pub version: usize,
+    /// Currency Name
+    pub currency_name: &'a str,
+    /// Issuer node id
+    pub node_id: NodeId,
+    /// Issuer pubkey alogirithm
+    pub algorithm: KeysAlgo,
+    /// Issuer pubkey
+    pub pubkey: String,
+    /// Peer card creation blockstamp
+    pub blockstamp: String,
+    /// Endpoints
+    pub endpoints: Vec<String>,
+    /// Signature
+    pub signature: Option<String>,
+}
+
+impl PeerCardV11 {
+    /// Convert to JSON String
+    pub fn to_json_peer(&self) -> Result<String, serde_json::Error> {
+        Ok(serde_json::to_string_pretty(&JsonPeerCardV11 {
+            version: 11,
+            currency_name: &self.currency_name.0,
+            node_id: self.node_id,
+            algorithm: self.issuer.algo(),
+            pubkey: self.issuer.to_base58(),
+            blockstamp: self.blockstamp.to_string(),
+            endpoints: self.endpoints.iter().map(|ep| ep.to_string()).collect(),
+            signature: if let Some(sig) = self.sig {
+                Some(sig.to_base64())
+            } else {
+                None
+            },
+        })?)
+    }
+}
+
+impl<'de> BinSignable<'de> for PeerCardV11 {
+    fn issuer_pubkey(&self) -> PubKey {
+        self.issuer
+    }
+    fn signature(&self) -> Option<Sig> {
+        self.sig
+    }
+    fn set_signature(&mut self, signature: Sig) {
+        self.sig = Some(signature)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 /// Peer card
-pub enum NetworkPeer {
+pub enum PeerCard {
     /// Peer card V10
-    V10(NetworkPeerV10),
+    V10(PeerCardV10),
     /// Peer card V11
-    V11(),
+    V11(PeerCardV11),
 }
 
-impl NetworkPeer {
+impl PeerCard {
     /// Get peer card version
     pub fn version(&self) -> u32 {
         match *self {
-            NetworkPeer::V10(ref _peer_v10) => 10,
-            _ => panic!("Peer version is not supported !"),
+            PeerCard::V10(ref _peer_v10) => 10,
+            PeerCard::V11(ref _peer_v11) => 11,
         }
     }
     /// Get peer card blockstamp
     pub fn blockstamp(&self) -> Blockstamp {
         match *self {
-            NetworkPeer::V10(ref peer_v10) => peer_v10.blockstamp,
+            PeerCard::V10(ref peer_v10) => peer_v10.blockstamp,
             _ => panic!("Peer version is not supported !"),
         }
     }
     /// Get peer card issuer
     pub fn issuer(&self) -> PubKey {
         match *self {
-            NetworkPeer::V10(ref peer_v10) => peer_v10.issuer,
+            PeerCard::V10(ref peer_v10) => peer_v10.issuer,
             _ => panic!("Peer version is not supported !"),
         }
     }
@@ -72,7 +145,74 @@ impl NetworkPeer {
         false
     }
     /// Get peer card endpoint
-    pub fn get_endpoints(&self) -> Vec<NetworkEndpoint> {
+    pub fn get_endpoints(&self) -> Vec<EndpointEnum> {
         Vec::with_capacity(0)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::net::Ipv4Addr;
+    use std::str::FromStr;
+    use tests::bincode::deserialize;
+    use tests::keypair1;
+
+    fn create_endpoint_v2() -> EndpointV2 {
+        EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: None,
+            ip_v6: None,
+            host: Some(String::from("g1.durs.ifee.fr")),
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        }
+    }
+    fn create_second_endpoint_v2() -> EndpointV2 {
+        EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![5u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: Some(Ipv4Addr::from_str("84.16.72.210").unwrap()),
+            ip_v6: None,
+            host: None,
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        }
+    }
+
+    #[test]
+    fn peer_card_v11_sign_and_verify() {
+        let keypair1 = keypair1();
+        let mut peer_card_v11 = PeerCardV11 {
+            currency_name: CurrencyName(String::from("g1")),
+            issuer: PubKey::Ed25519(keypair1.public_key()),
+            node_id: NodeId(0),
+            blockstamp: Blockstamp::from_string(
+                "50-000005B1CEB4EC5245EF7E33101A330A1C9A358EC45A25FC13F78BB58C9E7370",
+            ).unwrap(),
+            endpoints: vec![
+                EndpointEnum::V2(create_endpoint_v2()),
+                EndpointEnum::V2(create_second_endpoint_v2()),
+            ],
+            sig: None,
+        };
+        // Sign
+        let sign_result = peer_card_v11.sign(PrivKey::Ed25519(keypair1.private_key()));
+        if let Ok(peer_card_v11_bytes) = sign_result {
+            let deser_peer_card_v11: PeerCardV11 =
+                deserialize(&peer_card_v11_bytes).expect("Fail to deserialize PeerCardV11 !");
+            assert_eq!(peer_card_v11, deser_peer_card_v11,)
+        } else {
+            panic!("failt to sign peer card : {:?}", sign_result.err().unwrap())
+        }
+        // Verify signature
+        peer_card_v11
+            .verify()
+            .expect("Fail to verify PeerCardV11 !");
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index f275ed88608829d3a46d2b772e71e0df34f5ba37..b4e8bb10093c4f7d188cacdf4991f96a19b3aefd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,13 +31,15 @@
 extern crate duniter_core;
 #[cfg(feature = "tui")]
 extern crate duniter_tui;
-extern crate duniter_ws2p;
+extern crate durs_ws2p_v1_legacy;
+//extern crate durs_ws2p;
 
 pub use duniter_core::DuRsConf;
 pub use duniter_core::DuniterCore;
 #[cfg(feature = "tui")]
 pub use duniter_tui::TuiModule;
-pub use duniter_ws2p::WS2PModule;
+pub use durs_ws2p_v1_legacy::WS2PModule;
+//pub use durs_ws2p::WS2Pv2Module;
 
 /// Main function
 fn main() {
@@ -54,6 +56,7 @@ fn main() {
         //duniter_core.plug::<PowModule>();
         plug_tui_module(&mut duniter_core);
         duniter_core.plug_network::<WS2PModule>();
+        //duniter_core.plug_network::<WS2Pv2Module>();
         duniter_core.start_blockchain();
     };
 }
diff --git a/wotb/operations/path.rs b/wotb/operations/path.rs
index 48690a0a3bfb9a0623d7cf9383e773c0f3e0dba2..5e3d24252875aedbbdba888ac98504487c601d61 100644
--- a/wotb/operations/path.rs
+++ b/wotb/operations/path.rs
@@ -40,17 +40,15 @@ impl<T: WebOfTrust> PathFinder<T> for RustyPathFinder {
 
         // Stores for each node its distance to `to` node and its backward links.
         // By default all nodes are out of range (`k_max + 1`) and links are known.
-        let mut graph: Vec<(u32, Vec<NodeId>)> = (0..wot.size())
-            .into_iter()
-            .map(|_| (k_max + 1, vec![]))
-            .collect();
+        let mut graph: Vec<(u32, Vec<NodeId>)> =
+            (0..wot.size()).map(|_| (k_max + 1, vec![])).collect();
         // `to` node is at distance 0, and have no backward links.
         graph[to.0] = (0, vec![]);
         // Explored zone border.
         let mut border = HashSet::new();
         border.insert(to);
 
-        for distance in 1..(k_max + 1) {
+        for distance in 1..=k_max {
             let mut next_border = HashSet::new();
 
             for node in border {
@@ -74,7 +72,7 @@ impl<T: WebOfTrust> PathFinder<T> for RustyPathFinder {
         //    For each path, we look at the last element sources and build new paths with them.
         let mut paths = vec![vec![from]];
 
-        for _ in 1..(k_max + 1) {
+        for _ in 1..=k_max {
             let mut new_paths = vec![];
 
             for path in &paths {
diff --git a/ws2p-messages/Cargo.toml b/ws2p-messages/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..aa6532d06cbcf4373f87bcf91b7665f5225a6bbf
--- /dev/null
+++ b/ws2p-messages/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "durs-ws2p-messages"
+version = "0.1.0-a0.1"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Handles WebSocketToPeer API Messages."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+bincode = "1.0.*"
+byteorder = "1.2.3"
+duniter-crypto = { path = "../crypto" }
+duniter-documents = { path = "../documents" }
+duniter-network = { path = "../network" }
+log = "0.4.*"
+regex = "0.2.*"
+rust-crypto = "0.2.*"
+serde = "1.0.*"
+serde_derive = "1.0.*"
+serde_json = "1.0.*"
+
+[dev-dependencies]
+pretty_assertions = "0.5.1"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/ws2p-messages/lib.rs b/ws2p-messages/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e7e0e9c602a46089e16d8c4df5875a3b8a73bcbd
--- /dev/null
+++ b/ws2p-messages/lib.rs
@@ -0,0 +1,183 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Handles WebSocketToPeer API Messages.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs,
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    unsafe_code,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications
+)]
+
+/*#[cfg(test)]
+#[macro_use]
+extern crate pretty_assertions;*/
+
+#[macro_use]
+extern crate serde_derive;
+
+extern crate bincode;
+extern crate byteorder;
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_network;
+
+/// WS2Pv2 Messages
+pub mod v2;
+
+use v2::WS2Pv0Message;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+/// WS2Pv0Message
+pub enum WS2PMessage {
+    /// Version 2
+    V0(WS2Pv0Message),
+}
+
+#[cfg(test)]
+mod tests {
+    use bincode;
+    use bincode::{deserialize, serialize};
+    use duniter_crypto::keys::bin_signable::BinSignable;
+    use duniter_crypto::keys::*;
+    use duniter_documents::blockchain::v10::documents::certification::*;
+    use duniter_documents::{Blockstamp, CurrencyName};
+    use duniter_network::network_endpoint::*;
+    use duniter_network::network_peer::*;
+    use duniter_network::*;
+    use std::net::Ipv4Addr;
+    use std::str::FromStr;
+    use v2::payload_container::WS2Pv0MessagePayload;
+    use v2::WS2Pv0Message;
+
+    pub fn keypair1() -> ed25519::KeyPair {
+        ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters().generate(
+            "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV".as_bytes(),
+            "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV_".as_bytes(),
+        )
+    }
+
+    pub fn create_endpoint_v11() -> EndpointEnum {
+        EndpointEnum::V2(EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![4u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: None,
+            ip_v6: None,
+            host: Some(String::from("g1.durs.ifee.fr")),
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        })
+    }
+    pub fn create_second_endpoint_v11() -> EndpointEnum {
+        EndpointEnum::V2(EndpointV2 {
+            api: NetworkEndpointApi(String::from("WS2P")),
+            api_version: 2,
+            network_features: EndpointV2NetworkFeatures(vec![5u8]),
+            api_features: ApiFeatures(vec![7u8]),
+            ip_v4: Some(Ipv4Addr::from_str("84.16.72.210").unwrap()),
+            ip_v6: None,
+            host: None,
+            port: 443u16,
+            path: Some(String::from("ws2p")),
+        })
+    }
+
+    pub fn create_peer_card_v11() -> PeerCardV11 {
+        PeerCardV11 {
+            currency_name: CurrencyName(String::from("g1")),
+            issuer: PubKey::Ed25519(keypair1().pubkey),
+            node_id: NodeId(0),
+            blockstamp: Blockstamp::from_string(
+                "50-000005B1CEB4EC5245EF7E33101A330A1C9A358EC45A25FC13F78BB58C9E7370",
+            ).unwrap(),
+            endpoints: vec![create_endpoint_v11(), create_second_endpoint_v11()],
+            sig: None,
+        }
+    }
+
+    pub fn test_ws2p_message(payload: WS2Pv0MessagePayload) {
+        let keypair1 = keypair1();
+        let mut ws2p_message = WS2Pv0Message {
+            currency_code: CurrencyName(String::from("g1")),
+            issuer_node_id: NodeId(0),
+            issuer_pubkey: PubKey::Ed25519(keypair1.public_key()),
+            payload,
+            message_hash: None,
+            signature: None,
+        };
+
+        let sign_result = ws2p_message.sign(PrivKey::Ed25519(keypair1.private_key()));
+        if let Ok(bin_msg) = sign_result {
+            // Test binarization
+            assert_eq!(
+                serialize(&ws2p_message).expect("Fail to serialize WS2Pv0Message !"),
+                bin_msg
+            );
+            // Test sign
+            ws2p_message
+                .verify()
+                .expect("WS2Pv0Message : Invalid signature !");
+            // Test debinarization
+            let debinarization_result: Result<WS2Pv0Message, bincode::Error> =
+                deserialize(&bin_msg);
+            if let Ok(ws2p_message2) = debinarization_result {
+                assert_eq!(ws2p_message, ws2p_message2);
+            } else {
+                panic!(
+                    "Fail to debinarize ws2p_message : {:?}",
+                    debinarization_result.err().unwrap()
+                );
+            }
+        } else {
+            panic!(
+                "Fail to sign ws2p_message : {:?}",
+                sign_result.err().unwrap()
+            );
+        }
+    }
+
+    pub fn create_cert_doc() -> CompactCertificationDocument {
+        let sig = Sig::Ed25519(ed25519::Signature::from_base64(
+            "qfR6zqT1oJbqIsppOi64gC9yTtxb6g6XA9RYpulkq9ehMvqg2VYVigCbR0yVpqKFsnYiQTrnjgFuFRSJCJDfCw==",
+        ).unwrap());
+
+        let target = PubKey::Ed25519(
+            ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV")
+                .unwrap(),
+        );
+
+        let blockstamp = Blockstamp::from_string(
+            "36-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B865",
+        ).unwrap();
+
+        CompactCertificationDocument {
+            issuer: PubKey::Ed25519(
+                ed25519::PublicKey::from_base58("4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR")
+                    .unwrap(),
+            ),
+            target: target,
+            block_number: blockstamp.id,
+            signature: sig,
+        }
+    }
+}
diff --git a/ws2p-messages/v2/api_features.rs b/ws2p-messages/v2/api_features.rs
new file mode 100644
index 0000000000000000000000000000000000000000..28083f4b4eae5df3fc6259b8bfb268683abc9b1b
--- /dev/null
+++ b/ws2p-messages/v2/api_features.rs
@@ -0,0 +1,42 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2PFeatures
+pub struct WS2PFeatures(pub Vec<u8>);
+
+impl WS2PFeatures {
+    /// Return true if all flags are disabled (or if it's really empty).
+    pub fn is_empty(&self) -> bool {
+        for byte in &self.0 {
+            if *byte > 0u8 {
+                return false;
+            }
+        }
+        true
+    }
+    /// Check flag DEF
+    pub fn _def(&self) -> bool {
+        self.0[0] | 0b1111_1110 == 255u8
+    }
+    /// Check flag LOW
+    pub fn _low(&self) -> bool {
+        self.0[0] | 0b1111_1101 == 255u8
+    }
+    /// Check flag ABF
+    pub fn _abf(&self) -> bool {
+        self.0[0] | 0b1111_1011 == 255u8
+    }
+}
diff --git a/ws2p-messages/v2/connect.rs b/ws2p-messages/v2/connect.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3ed23827ad012a8215e1887c8ccb28e2778cb215
--- /dev/null
+++ b/ws2p-messages/v2/connect.rs
@@ -0,0 +1,113 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use super::api_features::WS2PFeatures;
+use duniter_crypto::hashs::Hash;
+use duniter_documents::Blockstamp;
+use duniter_network::network_peer::PeerCardV11;
+
+/// WS2P v2 connect message min size
+pub static CONNECT_MSG_MIN_SIZE: &'static usize = &36;
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2PConnectFlags
+pub struct WS2PConnectFlags(Vec<u8>);
+
+impl WS2PConnectFlags {
+    /// Return true if all flags are disabled (or if it's really empty).
+    pub fn is_empty(&self) -> bool {
+        for byte in &self.0 {
+            if *byte > 0u8 {
+                return false;
+            }
+        }
+        true
+    }
+    /// Check flag SYNC
+    pub fn _sync(&self) -> bool {
+        self.0[0] & 0b0000_0001 == 1u8
+    }
+    /// Check flag ASK_SYNC_CHUNK
+    pub fn _ask_sync_chunk(&self) -> bool {
+        self.0[0] & 0b0000_0010 == 2u8
+    }
+    /// Check flag RES_SYNC_CHUNK
+    pub fn _res_sync_chunk(&self) -> bool {
+        self.0[0] & 0b0000_0100 == 4u8
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2Pv2OkWS2Pv2ConnectMsgMsg
+pub struct WS2Pv2ConnectMsg {
+    /// random hash generated by the sending node of the CONNECT message,
+    /// the receiving node will then have to sign this challenge and then
+    /// send this signature in its ACK message to prove that it has the corresponding private key to the public key it indicates.
+    pub challenge: Hash,
+    /// This is exactly the same type as the field of the same name in the endpoints. But private WS2P nodes do not declare endpoints,
+    /// so they must be able to indicate in the CONNECT message which features they support. Public WS2P nodes also fill this field,
+    /// so any changes in the configuration of a public node will be applied on the 1st new connection. (If this was not the case,
+    /// we would have to wait for the update of the peer record).
+    pub api_features: WS2PFeatures,
+    /// WS2PConnectFlags
+    pub flags_queries: WS2PConnectFlags,
+    /// Issuer PeerCard
+    pub peer_card: Option<PeerCardV11>,
+    /// Blockstamp of the last block of the chunk
+    pub chunkstamp: Option<Blockstamp>,
+}
+
+impl Default for WS2Pv2ConnectMsg {
+    fn default() -> Self {
+        WS2Pv2ConnectMsg {
+            challenge: Hash::random(),
+            api_features: WS2PFeatures(vec![]),
+            flags_queries: WS2PConnectFlags(vec![]),
+            peer_card: None,
+            chunkstamp: None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use duniter_documents::Blockstamp;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_connect() {
+        let keypair1 = keypair1();
+        let mut peer = create_peer_card_v11();
+        peer.sign(PrivKey::Ed25519(keypair1.private_key()))
+            .expect("Fail to sign peer card !");
+        let connect_msg = WS2Pv2ConnectMsg {
+            challenge: Hash::from_hex(
+                "000007722B243094269E548F600BD34D73449F7578C05BD370A6D301D20B5F10",
+            ).unwrap(),
+            api_features: WS2PFeatures(vec![7u8]),
+            flags_queries: WS2PConnectFlags(vec![]),
+            peer_card: Some(peer),
+            chunkstamp: Some(
+                Blockstamp::from_string(
+                    "499-000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F",
+                ).unwrap(),
+            ),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::Connect(Box::new(connect_msg)));
+    }
+}
diff --git a/ws2p-messages/v2/mod.rs b/ws2p-messages/v2/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8562ef37c5896781f3458f90dd3a716de1315408
--- /dev/null
+++ b/ws2p-messages/v2/mod.rs
@@ -0,0 +1,102 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+/// WS2P Features
+pub mod api_features;
+/// WS2P v2 CONNECT Message
+pub mod connect;
+/// WS2P v2 OK Message
+pub mod ok;
+/// Message Payload container
+pub mod payload_container;
+/// WS2Pv2 requests responses messages
+pub mod req_responses;
+/// WS2Pv2 requests messages
+pub mod requests;
+/// WS2P v2 SECRET_FLAGS Message
+pub mod secret_flags;
+
+use duniter_crypto::hashs::Hash;
+use duniter_crypto::keys::bin_signable::BinSignable;
+use duniter_crypto::keys::*;
+use duniter_documents::CurrencyName;
+use duniter_network::NodeId;
+use v2::payload_container::*;
+
+/// WS2P v2 message metadata size
+pub static WS2P_V2_MESSAGE_METADATA_SIZE: &'static usize = &144;
+
+/// WS2Pv0Message
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub struct WS2Pv0Message {
+    /// Currency name
+    pub currency_code: CurrencyName,
+    /// Issuer NodeId
+    pub issuer_node_id: NodeId,
+    /// Issuer plublic key
+    pub issuer_pubkey: PubKey,
+    /// Message payload
+    pub payload: WS2Pv0MessagePayload,
+    /// Message hash
+    pub message_hash: Option<Hash>,
+    /// Signature
+    pub signature: Option<Sig>,
+}
+
+impl WS2Pv0Message {
+    /// WS2P Version number
+    pub const WS2P_VERSION: u16 = 0;
+}
+
+impl<'de> BinSignable<'de> for WS2Pv0Message {
+    fn issuer_pubkey(&self) -> PubKey {
+        self.issuer_pubkey
+    }
+    fn store_hash(&self) -> bool {
+        true
+    }
+    fn hash(&self) -> Option<Hash> {
+        self.message_hash
+    }
+    fn set_hash(&mut self, hash: Hash) {
+        self.message_hash = Some(hash)
+    }
+    fn signature(&self) -> Option<Sig> {
+        self.signature
+    }
+    fn set_signature(&mut self, signature: Sig) {
+        self.signature = Some(signature)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_ack() {
+        test_ws2p_message(WS2Pv0MessagePayload::Ack(Hash::random()));
+    }
+
+    #[test]
+    fn test_ws2p_message_peers() {
+        let keypair1 = keypair1();
+        let mut peer = create_peer_card_v11();
+        peer.sign(PrivKey::Ed25519(keypair1.private_key()))
+            .expect("Fail to sign peer card !");
+        test_ws2p_message(WS2Pv0MessagePayload::Peers(vec![peer]));
+    }
+}
diff --git a/ws2p-messages/v2/ok.rs b/ws2p-messages/v2/ok.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e442e373aac0b413b0f4b81b8075692a0cbe8858
--- /dev/null
+++ b/ws2p-messages/v2/ok.rs
@@ -0,0 +1,75 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use duniter_crypto::hashs::Hash;
+use duniter_documents::Blockstamp;
+use std::num::NonZeroU16;
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2Pv2OkMsg
+pub struct WS2Pv2OkMsg {
+    /// If this field is zero, it means that the remote node does not want to reveal its prefix (the prefix being necessarily greater than or equal to 1).
+    pub prefix: Option<NonZeroU16>,
+    /// WS2Pv2SyncTarget
+    pub sync_target: Option<WS2Pv2SyncTarget>,
+}
+
+impl Default for WS2Pv2OkMsg {
+    fn default() -> Self {
+        WS2Pv2OkMsg {
+            prefix: None,
+            sync_target: None,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2Pv2SyncTarget
+pub struct WS2Pv2SyncTarget {
+    /// Indicates the current blockstamp of the message sender node. This blockstamp will be the target to reach for the node being synchronized.
+    pub target_blockstamp: Blockstamp,
+    /// Hash table of the last block of each chunk. We do not need the block numbers, we know them. Here the remote node sends the hashs of all these chunk, which correspond to the current hashs of all the blocks having a number in 250 module 249, in ascending order.
+    pub chunks_hash: Vec<Hash>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use duniter_documents::Blockstamp;
+    use std::num::NonZeroU16;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_ok() {
+        let ok_msg = WS2Pv2OkMsg {
+            prefix: NonZeroU16::new(1),
+            sync_target: Some(WS2Pv2SyncTarget {
+                target_blockstamp: Blockstamp::from_string(
+                    "500-000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F",
+                ).unwrap(),
+                chunks_hash: vec![
+                    Hash::from_hex(
+                        "000007722B243094269E548F600BD34D73449F7578C05BD370A6D301D20B5F10",
+                    ).unwrap(),
+                    Hash::from_hex(
+                        "0000095FD4C8EA96DE2844E3A4B62FD18761E9B4C13A74FAB716A4C81F438D91",
+                    ).unwrap(),
+                ],
+            }),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::Ok(ok_msg));
+    }
+}
diff --git a/ws2p-messages/v2/payload_container.rs b/ws2p-messages/v2/payload_container.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9ff1dec581943c9149415c5dcbe801b067b84cde
--- /dev/null
+++ b/ws2p-messages/v2/payload_container.rs
@@ -0,0 +1,68 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use super::connect::WS2Pv2ConnectMsg;
+use super::ok::WS2Pv2OkMsg;
+use super::req_responses::WS2Pv2ReqRes;
+use super::requests::WS2Pv2Request;
+use super::secret_flags::WS2Pv2SecretFlagsMsg;
+use duniter_crypto::hashs::Hash;
+use duniter_documents::blockchain::v10::documents::{
+    BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument,
+    TransactionDocument,
+};
+use duniter_network::network_head_v2::NetworkHeadV2;
+use duniter_network::network_head_v3::NetworkHeadV3Container;
+use duniter_network::network_peer::PeerCardV11;
+
+/// WS2P v2 message payload metadata size
+pub static WS2P_V2_MESSAGE_PAYLOAD_METADATA_SIZE: &'static usize = &8;
+
+/// WS2Pv0MessagePayload
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum WS2Pv0MessagePayload {
+    /// CONNECT message
+    Connect(Box<WS2Pv2ConnectMsg>),
+    /// ACK message
+    Ack(Hash),
+    /// SECRET_FLAGS Message
+    SecretFlags(WS2Pv2SecretFlagsMsg),
+    /// OK Message
+    Ok(WS2Pv2OkMsg),
+    /// KO Message
+    Ko(u16),
+    /// REQUEST Message
+    Request(WS2Pv2Request),
+    /// REQUEST_RESPONSE Message
+    ReqRes(WS2Pv2ReqRes),
+    /// PEERS Message
+    Peers(Vec<PeerCardV11>),
+    /// HEADS_V2 Message
+    Headsv2(Vec<NetworkHeadV2>),
+    /// HEADS_V3 Message
+    Heads3(Vec<NetworkHeadV3Container>),
+    /// BLOCKS Message
+    Blocks(Vec<BlockDocument>),
+    /// PENDING_IDENTITIES Message
+    PendingIdentities(Vec<IdentityDocument>),
+    /// PENDING_MEMBERSHIPS Message
+    PendingMemberships(Vec<MembershipDocument>),
+    /// PENDING_CERTS Message
+    PendingCerts(Vec<CertificationDocument>),
+    /// PENDING_REVOCATIONS Message
+    PendingRevocations(Vec<RevocationDocument>),
+    /// PENDING_TXS Message
+    PendingTxs(Vec<TransactionDocument>),
+}
diff --git a/ws2p-messages/v2/req_responses.rs b/ws2p-messages/v2/req_responses.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0a46a21d786298cc88cee8dc4de059d161b88238
--- /dev/null
+++ b/ws2p-messages/v2/req_responses.rs
@@ -0,0 +1,123 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use duniter_crypto::hashs::Hash;
+use duniter_documents::blockchain::v10::documents::certification::CompactCertificationDocument;
+use duniter_documents::blockchain::v10::documents::identity::CompactIdentityDocument;
+use duniter_documents::blockchain::v10::documents::membership::CompactPoolMembershipDoc;
+use duniter_documents::blockchain::v10::documents::BlockDocument;
+use duniter_documents::Blockstamp;
+use std::str;
+
+/// WS2Pv2 request response
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct WS2Pv2ReqRes {
+    /// request unique identifier
+    pub id: u32,
+    /// request body
+    pub body: WS2Pv2ReqResBody,
+}
+
+/// WS2Pv2 request response body
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum WS2Pv2ReqResBody {
+    /// Empty response
+    None,
+    /// BadRequest (reason)
+    BadRequest(String),
+    /// Current blockstamp
+    Current(Blockstamp),
+    /// Blocks hashs
+    BlocksHashs(Vec<Hash>),
+    /// Chunk of blocks.
+    Chunk(Vec<BlockDocument>),
+    /// Wot pool datas
+    WotPool(Vec<CompactCertificationDocument>, Vec<WotPoolFolder>),
+}
+
+///WotPoolFolder
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct WotPoolFolder {
+    /// Pending identity
+    pub idty: CompactIdentityDocument,
+    /// Pending first membership
+    pub membership: CompactPoolMembershipDoc,
+    /// Pending certs
+    pub certs: Vec<CompactCertificationDocument>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use duniter_documents::Blockstamp;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_req_res_none() {
+        let response = WS2Pv2ReqRes {
+            id: 27,
+            body: WS2Pv2ReqResBody::None,
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::ReqRes(response));
+    }
+
+    #[test]
+    fn test_ws2p_message_req_res_bad_request() {
+        let reason = String::from("bla bla bla");
+        let response = WS2Pv2ReqRes {
+            id: 28,
+            body: WS2Pv2ReqResBody::BadRequest(reason),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::ReqRes(response));
+    }
+
+    #[test]
+    fn test_ws2p_message_req_res_current() {
+        let blockstamp = Blockstamp::from_string(
+            "499-000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F",
+        ).unwrap();
+        let response = WS2Pv2ReqRes {
+            id: 28,
+            body: WS2Pv2ReqResBody::Current(blockstamp),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::ReqRes(response));
+    }
+
+    #[test]
+    fn test_ws2p_message_req_res_blocks_hashs() {
+        let hashs = vec![
+            Hash::from_hex("000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F")
+                .unwrap(),
+            Hash::from_hex("0000007F8D3CCAF77CB77C5C025C4AED8A82BA2DBD2156FD92C9634DAB59BD7E")
+                .unwrap(),
+        ];
+        let response = WS2Pv2ReqRes {
+            id: 29,
+            body: WS2Pv2ReqResBody::BlocksHashs(hashs),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::ReqRes(response));
+    }
+
+    #[test]
+    fn test_ws2p_message_req_res_wot_pool() {
+        let cert_doc = create_cert_doc();
+        let response = WS2Pv2ReqRes {
+            id: 29,
+            body: WS2Pv2ReqResBody::WotPool(vec![cert_doc.clone(), cert_doc.clone()], vec![]),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::ReqRes(response));
+    }
+}
diff --git a/ws2p-messages/v2/requests.rs b/ws2p-messages/v2/requests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3290ceaa4006473e64f16727e244e09aaad7292a
--- /dev/null
+++ b/ws2p-messages/v2/requests.rs
@@ -0,0 +1,91 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use duniter_documents::{BlockId, Blockstamp};
+
+/// WS2Pv2Request
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub struct WS2Pv2Request {
+    /// request unique identifier
+    pub id: u32,
+    /// request body
+    pub body: WS2Pv2RequestBody,
+}
+
+impl WS2Pv2Request {
+    /// Request size in binary format
+    pub fn size_in_bytes(&self) -> usize {
+        4 + self.body.size_in_bytes()
+    }
+}
+
+/// WS2Pv2RequestBody
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub enum WS2Pv2RequestBody {
+    /// Empty request
+    None,
+    /// Request current blockstamp
+    Current,
+    /// BLOCKS_HASHS : In case of fork, to quickly find the fork point, the node will request the hashes of the ForkWindowsSize of the local blockchains of the other nodes.
+    /// It would be counterproductive to ask directly for the entire blocks, when you will only need them if you actually decide to stack the corresponding branch.
+    /// param1: begin_block_id (u32)
+    /// param2: blocks_count (u16)
+    BlocksHashs(BlockId, u16),
+    /// CHUNK: Request chunk of blocks.
+    /// param1: begin_block_id (u32)
+    /// param2: blocks_count (u16)
+    Chunk(BlockId, u16),
+    /// CHUNK_BY_HASH : During synchronization, chunk is requested by Chunkstamp (= Blockstamp of the last block of the chunk).
+    ChunkByHash(Blockstamp),
+    /// WOT_POOL : For network performance reasons, a Durs* node never shares its entire wot pool at once.
+    /// It randomly selects folders_count folders among those having received at least min_cert certifications.
+    /// It's the requesting node that sets the values of min_cert and folders_count according to its connection rate,
+    /// its configuration and the rate of new folders it has obtained in these previous requests.
+    /// param1: folders_count (u16)
+    /// param2: min_cert (u8)
+    WotPool(u16, u8),
+}
+
+impl WS2Pv2RequestBody {
+    /// Request size in binary format
+    pub fn size_in_bytes(&self) -> usize {
+        match *self {
+            WS2Pv2RequestBody::None | WS2Pv2RequestBody::Current => 1,
+            WS2Pv2RequestBody::BlocksHashs(_, _) | WS2Pv2RequestBody::Chunk(_, _) => 7,
+            WS2Pv2RequestBody::ChunkByHash(_) => 37,
+            WS2Pv2RequestBody::WotPool(_, _) => 4,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use duniter_documents::Blockstamp;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_request() {
+        let chunkstamp = Blockstamp::from_string(
+            "499-000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F",
+        ).unwrap();
+        let request = WS2Pv2Request {
+            id: 27,
+            body: WS2Pv2RequestBody::ChunkByHash(chunkstamp),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::Request(request));
+    }
+}
diff --git a/ws2p-messages/v2/secret_flags.rs b/ws2p-messages/v2/secret_flags.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ac4b3400d2744c4f96030ec71ce2ba09cc4975ed
--- /dev/null
+++ b/ws2p-messages/v2/secret_flags.rs
@@ -0,0 +1,84 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use duniter_crypto::keys::*;
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2Pv2SecretFlags
+pub struct WS2Pv2SecretFlags(Vec<u8>);
+
+impl WS2Pv2SecretFlags {
+    /// Return true if all flags are disabled (or if it's really empty).
+    pub fn is_empty(&self) -> bool {
+        for byte in &self.0 {
+            if *byte > 0u8 {
+                return false;
+            }
+        }
+        true
+    }
+    /// Check flag LOW_FLOW_DEMAND
+    pub fn _low_flow_demand(&self) -> bool {
+        self.0[0] | 0b1111_1110 == 255u8
+    }
+    /// Check flag MEMBER_PUBKEY
+    pub fn member_pubkey(&self) -> bool {
+        self.0[0] | 0b1111_1101 == 255u8
+    }
+    /// Check flag MEMBER_PROOF
+    pub fn member_proof(&self) -> bool {
+        self.0[0] | 0b1111_1011 == 255u8
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+/// WS2Pv2SecretFlagsMsg
+pub struct WS2Pv2SecretFlagsMsg {
+    /// Secret flags
+    pub secret_flags: WS2Pv2SecretFlags,
+    ///
+    pub member_pubkey: Option<PubKey>,
+    /// Proof that the sender node is a member (Signature of the challenge send by other node in their CONNECT message.)
+    pub member_proof: Option<Sig>,
+}
+
+impl Default for WS2Pv2SecretFlagsMsg {
+    fn default() -> Self {
+        WS2Pv2SecretFlagsMsg {
+            secret_flags: WS2Pv2SecretFlags(vec![]),
+            member_pubkey: None,
+            member_proof: None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use tests::*;
+
+    #[test]
+    fn test_ws2p_message_secret_flags() {
+        let keypair1 = keypair1();
+        let challenge = Hash::random();
+        let msg = WS2Pv2SecretFlagsMsg {
+            secret_flags: WS2Pv2SecretFlags(vec![6u8]),
+            member_pubkey: Some(PubKey::Ed25519(keypair1.public_key())),
+            member_proof: Some(Sig::Ed25519(keypair1.private_key().sign(&challenge.0))),
+        };
+        test_ws2p_message(WS2Pv0MessagePayload::SecretFlags(msg));
+    }
+}
diff --git a/ws2p-v1-legacy/Cargo.toml b/ws2p-v1-legacy/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b8c414717834e5e1b7dff2cf2d3c9122fabe720a
--- /dev/null
+++ b/ws2p-v1-legacy/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "durs-ws2p-v1-legacy"
+version = "0.1.0-a0.1"
+authors = ["librelois <elois@ifee.fr>"]
+description = "WebSocketToPeer API for the Durs project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+byteorder = "1.2.3"
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-dal = { path = "../dal" }
+duniter-documents = { path = "../documents" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+duniter-wotb = { path = "../wotb" }
+lazy_static = "1.0.*"
+log = "0.4.*"
+rand = "0.4.*"
+regex = "0.2.*"
+rust-crypto = "0.2.*"
+sqlite = "0.23.*"
+serde = "1.0.*"
+serde_derive = "1.0.*"
+serde_json = "1.0.*"
+ws = { version = "0.7.*", features = ["permessage-deflate"] }
+
+[features]
+ssl = ["ws/ssl"]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/ws2p/ack_message.rs b/ws2p-v1-legacy/ack_message.rs
similarity index 100%
rename from ws2p/ack_message.rs
rename to ws2p-v1-legacy/ack_message.rs
diff --git a/ws2p/connect_message.rs b/ws2p-v1-legacy/connect_message.rs
similarity index 100%
rename from ws2p/connect_message.rs
rename to ws2p-v1-legacy/connect_message.rs
diff --git a/ws2p-v1-legacy/constants.rs b/ws2p-v1-legacy/constants.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bc8844462cb4e4e94db783f0ce47d604b17d3c69
--- /dev/null
+++ b/ws2p-v1-legacy/constants.rs
@@ -0,0 +1,22 @@
+extern crate regex;
+
+use self::regex::Regex;
+
+lazy_static! {
+    #[derive(Debug)]
+    pub static ref WS2P_V1_ENDPOINT_REGEX: Regex = Regex::new(
+        "^WS2P (?P<version>[1-9][0-9]* )?(?P<uuid>[a-f0-9]{6,8}) (?P<host>[a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) (?P<port>[0-9]+)(?: /?(?P<path>.+)?)? *$"
+    ).unwrap();
+}
+pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75;
+pub static WS2P_OUTCOMING_INTERVAL: &'static u64 = &300;
+pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10;
+pub static WS2P_NEGOTIATION_TIMEOUT: &'static u64 = &15;
+//pub static WS2P_REQUEST_TIMEOUT : &'static u64 = &30;
+pub static WS2P_EXPIRE_TIMEOUT: &'static u64 = &120;
+pub static WS2P_SPAM_INTERVAL_IN_MILLI_SECS: &'static u64 = &80;
+pub static WS2P_SPAM_LIMIT: &'static usize = &6;
+pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &'static u64 = &100;
+pub static DURATION_BEFORE_RECORDING_ENDPOINT: &'static u64 = &180;
+pub static BLOCKS_REQUEST_INTERVAL: &'static u64 = &60;
+pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &'static u64 = &40;
diff --git a/ws2p/datas.rs b/ws2p-v1-legacy/datas.rs
similarity index 98%
rename from ws2p/datas.rs
rename to ws2p-v1-legacy/datas.rs
index 5129f7b7c97697f501ac7ae4f9b6531527b66647..b163718542c8cc13d1df5f11654cf618eccac28f 100644
--- a/ws2p/datas.rs
+++ b/ws2p-v1-legacy/datas.rs
@@ -31,12 +31,12 @@ pub struct WS2PModuleDatas {
     pub currency: Option<String>,
     pub key_pair: Option<KeyPairEnum>,
     pub conf: WS2PConf,
-    pub node_id: NodeUUID,
+    pub node_id: NodeId,
     pub main_thread_channel: (
         mpsc::Sender<WS2PThreadSignal>,
         mpsc::Receiver<WS2PThreadSignal>,
     ),
-    pub ws2p_endpoints: HashMap<NodeFullId, (NetworkEndpoint, WS2PConnectionState)>,
+    pub ws2p_endpoints: HashMap<NodeFullId, (EndpointEnum, WS2PConnectionState)>,
     pub websockets: HashMap<NodeFullId, WsSender>,
     pub requests_awaiting_response: HashMap<ModuleReqId, (NetworkRequest, NodeFullId, SystemTime)>,
     pub heads_cache: HashMap<NodeFullId, NetworkHead>,
@@ -171,7 +171,7 @@ impl WS2PModuleDatas {
             }
         }
     }
-    pub fn connect_to(&mut self, endpoint: &NetworkEndpoint) -> () {
+    pub fn connect_to(&mut self, endpoint: &EndpointEnum) -> () {
         // Add endpoint to endpoints list (if there isn't already)
         match self.ws2p_endpoints.get(
             &endpoint
@@ -302,7 +302,7 @@ impl WS2PModuleDatas {
             WS2PConnectionMessagePayload::Heads(heads) => {
                 let mut applied_heads = Vec::with_capacity(heads.len());
                 for head in heads {
-                    if let Some(head) = NetworkHead::from_json_value(&head) {
+                    if let Ok(head) = NetworkHead::from_json_value(&head) {
                         if head.verify()
                             && (self.my_head.is_none() || head.node_full_id() != self
                                 .my_head
@@ -494,7 +494,7 @@ impl WS2PModuleDatas {
         Ok(())
     }
 
-    fn connect_to_without_checking_quotas(&mut self, endpoint: &NetworkEndpoint) {
+    fn connect_to_without_checking_quotas(&mut self, endpoint: &EndpointEnum) {
         let endpoint_copy = endpoint.clone();
         let conductor_sender_copy = self.main_thread_channel.0.clone();
         let currency_copy = self.currency.clone();
diff --git a/ws2p/heads.rs b/ws2p-v1-legacy/heads.rs
similarity index 97%
rename from ws2p/heads.rs
rename to ws2p-v1-legacy/heads.rs
index 5528e630d4b596cd2eb6f003cc0c853fb49db2bd..f57ad2c40441be2918c201ee7515f142368fb57e 100644
--- a/ws2p/heads.rs
+++ b/ws2p-v1-legacy/heads.rs
@@ -14,12 +14,12 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use duniter_documents::Blockstamp;
-use duniter_network::network_head::*;
+use duniter_network::network_head_v2::*;
 use *;
 
 pub fn generate_my_head(
     network_keypair: &KeyPairEnum,
-    node_id: NodeUUID,
+    node_id: NodeId,
     soft_name: &str,
     soft_version: &str,
     my_current_blockstamp: &Blockstamp,
diff --git a/ws2p-v1-legacy/lib.rs b/ws2p-v1-legacy/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e9fc2140de438ded492a9c8ec97c5e9115d13274
--- /dev/null
+++ b/ws2p-v1-legacy/lib.rs
@@ -0,0 +1,1181 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! WebSocketToPeer API for the Duniter project.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
+#![deny(
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    unsafe_code,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications
+)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate serde_derive;
+#[macro_use]
+extern crate serde_json;
+
+extern crate byteorder;
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate rand;
+extern crate sqlite;
+extern crate ws;
+
+mod ack_message;
+mod connect_message;
+pub mod constants;
+mod datas;
+mod heads;
+mod ok_message;
+pub mod parsers;
+pub mod serializer;
+pub mod ws2p_connection;
+pub mod ws2p_db;
+pub mod ws2p_requests;
+
+use ack_message::WS2PAckMessageV1;
+use connect_message::WS2PConnectMessageV1;
+use constants::*;
+use datas::*;
+use duniter_conf::DuRsConf;
+use duniter_crypto::keys::*;
+use duniter_dal::dal_event::DALEvent;
+use duniter_dal::dal_requests::{DALReqBlockchain, DALRequest, DALResBlockchain, DALResponse};
+use duniter_documents::Blockstamp;
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use duniter_network::network_endpoint::*;
+use duniter_network::network_head::*;
+use duniter_network::*;
+use ok_message::WS2POkMessageV1;
+use parsers::blocks::parse_json_block;
+use std::collections::HashMap;
+use std::ops::Deref;
+use std::path::PathBuf;
+use std::sync::mpsc;
+use std::thread;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use ws::Message;
+use ws2p_connection::*;
+use ws2p_requests::network_request_to_json;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// WS2P Configuration
+pub struct WS2PConf {
+    /// Limit of outcoming connections
+    pub outcoming_quota: usize,
+    /// Default WS2P endpoints provides by configuration file
+    pub sync_endpoints: Vec<EndpointEnum>,
+}
+
+impl Default for WS2PConf {
+    fn default() -> Self {
+        WS2PConf {
+            outcoming_quota: *WS2P_DEFAULT_OUTCOMING_QUOTA,
+            sync_endpoints: vec![
+                EndpointEnum::parse_from_raw(
+                    "WS2P c1c39a0a g1-monit.librelois.fr 443 /ws2p",
+                    PubKey::Ed25519(
+                        ed25519::PublicKey::from_base58(
+                            "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+                        ).unwrap(),
+                    ),
+                    0,
+                    0,
+                    1u16,
+                ).unwrap(),
+                EndpointEnum::parse_from_raw(
+                    "WS2P b48824f0 g1.monnaielibreoccitanie.org 443 /ws2p",
+                    PubKey::Ed25519(
+                        ed25519::PublicKey::from_base58(
+                            "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef",
+                        ).unwrap(),
+                    ),
+                    0,
+                    0,
+                    1u16,
+                ).unwrap(),
+            ],
+        }
+    }
+}
+
+#[derive(Debug)]
+/// Store a Signal receive from network (after message treatment)
+pub enum WS2PSignal {
+    /// Receive a websocket error from a connextion. `NodeFullId` store the identifier of connection.
+    WSError(NodeFullId),
+    /// A new connection is successfully established with `NodeFullId`.
+    ConnectionEstablished(NodeFullId),
+    NegociationTimeout(NodeFullId),
+    Timeout(NodeFullId),
+    DalRequest(NodeFullId, ModuleReqId, serde_json::Value),
+    PeerCard(NodeFullId, serde_json::Value, Vec<EndpointEnum>),
+    Heads(NodeFullId, Vec<NetworkHead>),
+    Document(NodeFullId, NetworkDocument),
+    ReqResponse(ModuleReqId, NetworkRequest, NodeFullId, serde_json::Value),
+    Empty,
+    NoConnection,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum NetworkConsensusError {
+    InsufficientData(usize),
+    Fork,
+}
+
+#[derive(Debug)]
+pub enum SendRequestError {
+    RequestTypeMustNotBeTransmitted(),
+    WSError(usize, Vec<ws::Error>),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct WS2PModule {}
+
+#[derive(Debug)]
+pub enum WS2PThreadSignal {
+    DuniterMessage(Box<DuniterMessage>),
+    WS2PConnectionMessage(WS2PConnectionMessage),
+}
+
+pub trait WS2PMessage: Sized {
+    fn parse(v: &serde_json::Value, currency: String) -> Option<Self>;
+    fn to_raw(&self) -> String;
+    fn sign(&self, key_pair: KeyPairEnum) -> Sig {
+        key_pair.sign(self.to_raw().as_bytes())
+    }
+    fn verify(&self) -> bool;
+    //fn parse_and_verify(v: serde_json::Value, currency: String) -> bool;
+}
+
+impl Default for WS2PModule {
+    fn default() -> WS2PModule {
+        WS2PModule {}
+    }
+}
+
+#[derive(Debug)]
+/// WS2PFeaturesParseError
+pub enum WS2PFeaturesParseError {
+    /// UnknowApiFeature
+    UnknowApiFeature(String),
+}
+
+impl ApiModule<DuRsConf, DuniterMessage> for WS2PModule {
+    type ParseErr = WS2PFeaturesParseError;
+    /// Parse raw api features
+    fn parse_raw_api_features(str_features: &str) -> Result<ApiFeatures, Self::ParseErr> {
+        let str_features: Vec<&str> = str_features.split(' ').collect();
+        let mut api_features = Vec::with_capacity(0);
+        for str_feature in str_features {
+            match str_feature {
+                "DEF" => api_features[0] += 1u8,
+                "LOW" => api_features[0] += 2u8,
+                "ABF" => api_features[0] += 4u8,
+                _ => {
+                    return Err(WS2PFeaturesParseError::UnknowApiFeature(String::from(
+                        str_feature,
+                    )))
+                }
+            }
+        }
+        Ok(ApiFeatures(api_features))
+    }
+}
+
+impl NetworkModule<DuRsConf, DuniterMessage> for WS2PModule {
+    fn sync(
+        _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        _keys: RequiredKeysContent,
+        _conf: WS2PConf,
+        _main_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        _sync_endpoint: SyncEndpoint,
+    ) -> Result<(), ModuleInitError> {
+        println!("Downlaod blockchain from network...");
+        println!("Error : not yet implemented !");
+        Ok(())
+    }
+}
+
+impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule {
+    type ModuleConf = WS2PConf;
+
+    fn id() -> ModuleId {
+        ModuleId(String::from("ws2p"))
+    }
+    fn priority() -> ModulePriority {
+        ModulePriority::Essential()
+    }
+    fn ask_required_keys() -> RequiredKeys {
+        RequiredKeys::NetworkKeyPair()
+    }
+    fn start(
+        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        keys: RequiredKeysContent,
+        conf: WS2PConf,
+        rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        load_conf_only: bool,
+    ) -> Result<(), ModuleInitError> {
+        // Get start time
+        let start_time = SystemTime::now();
+
+        // Define WS2PModuleDatas
+        let mut ws2p_module = WS2PModuleDatas {
+            followers: Vec::new(),
+            key_pair: None,
+            currency: None,
+            conf,
+            node_id: NodeId(soft_meta_datas.conf.my_node_id()),
+            main_thread_channel: mpsc::channel(),
+            ws2p_endpoints: HashMap::new(),
+            websockets: HashMap::new(),
+            requests_awaiting_response: HashMap::new(),
+            heads_cache: HashMap::new(),
+            my_head: None,
+            uids_cache: HashMap::new(),
+        };
+
+        // load conf
+        let key_pair = match keys {
+            RequiredKeysContent::NetworkKeyPair(key_pair) => key_pair,
+            _ => panic!("WS2PModule fatal error at load_conf() : keys != NetworkKeyPair"),
+        };
+        let mut ws2p_endpoints = HashMap::new();
+        for ep in ws2p_module.conf.sync_endpoints.clone() {
+            ws2p_endpoints.insert(
+                ep.node_full_id()
+                    .expect("Fail to get endpoint node_full_id"),
+                (ep.clone(), WS2PConnectionState::Close),
+            );
+            info!("Load sync endpoint {}", ep.raw());
+        }
+        ws2p_module.key_pair = Some(key_pair);
+        ws2p_module.currency = Some(soft_meta_datas.conf.currency().to_string());
+        ws2p_module.ws2p_endpoints = ws2p_endpoints;
+
+        // Create ws2p main thread channel
+        let ws2p_sender_clone = ws2p_module.main_thread_channel.0.clone();
+
+        // Create proxy channel
+        let (proxy_sender, proxy_receiver): (
+            mpsc::Sender<DuniterMessage>,
+            mpsc::Receiver<DuniterMessage>,
+        ) = mpsc::channel();
+        let proxy_sender_clone = proxy_sender.clone();
+
+        // Launch a proxy thread that transform DuniterMessage to WS2PThreadSignal(DuniterMessage)
+        thread::spawn(move || {
+            // Send proxy sender to main
+            rooter_sender
+                .send(RooterThreadMessage::ModuleSender(proxy_sender_clone))
+                .expect("Fatal error : ws2p module fail to send is sender channel !");
+            debug!("Send ws2p sender to main thread.");
+            loop {
+                match proxy_receiver.recv() {
+                    Ok(message) => {
+                        ws2p_sender_clone
+                            .send(WS2PThreadSignal::DuniterMessage(Box::new(message.clone())))
+                            .expect(
+                                "Fatal error : fail to relay DuniterMessage to ws2p main thread !",
+                            );
+                        if let DuniterMessage::Stop() = message {
+                            break;
+                        };
+                    }
+                    Err(e) => panic!(format!("{}", e)),
+                }
+            }
+        });
+
+        // open ws2p bdd
+        let mut db_path =
+            duniter_conf::datas_path(&soft_meta_datas.profile, &soft_meta_datas.conf.currency());
+        db_path.push("ws2p.db");
+        let db = WS2PModuleDatas::open_db(&db_path).expect("Fatal error : fail to open WS2P DB !");
+
+        // Get ws2p endpoints in BDD
+        let mut count = 0;
+        let dal_enpoints =
+            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
+        for ep in dal_enpoints {
+            if ep.api() == NetworkEndpointApi(String::from("WS2P"))
+                && (cfg!(feature = "ssl") || ep.port() != 443)
+            {
+                count += 1;
+                ws2p_module.ws2p_endpoints.insert(
+                    ep.node_full_id()
+                        .expect("WS2P: Fail to get ep.node_full_id() !"),
+                    (ep.clone(), WS2PConnectionState::from(ep.status())),
+                );
+            }
+        }
+        info!("Load {} endpoints from bdd !", count);
+
+        // Stop here in load_conf_only mode
+        if load_conf_only {
+            return Ok(());
+        }
+
+        // Initialize variables
+        let mut last_ws2p_connecting_wave = SystemTime::now();
+        let mut last_ws2p_connections_print = SystemTime::now();
+        let mut endpoints_to_update_status: HashMap<NodeFullId, SystemTime> = HashMap::new();
+        let mut last_identities_request = UNIX_EPOCH;
+        let mut current_blockstamp = Blockstamp::default();
+        let mut next_receiver = 0;
+
+        // Start
+        ws2p_module.connect_to_know_endpoints();
+        loop {
+            match ws2p_module
+                .main_thread_channel
+                .1
+                .recv_timeout(Duration::from_millis(200))
+            {
+                Ok(message) => match message {
+                    WS2PThreadSignal::DuniterMessage(ref duniter_mesage) => {
+                        match *duniter_mesage.deref() {
+                            DuniterMessage::Stop() => break,
+                            DuniterMessage::Followers(ref new_followers) => {
+                                info!("WS2P module receive followers !");
+                                for new_follower in new_followers {
+                                    debug!("WS2PModule : push one follower.");
+                                    ws2p_module.followers.push(new_follower.clone());
+                                    if current_blockstamp == Blockstamp::default() {
+                                        // Request local current blockstamp
+                                        ws2p_module.send_dal_request(
+                                            &DALRequest::BlockchainRequest(
+                                                DALReqBlockchain::CurrentBlock(ModuleReqFullId(
+                                                    WS2PModule::id(),
+                                                    ModuleReqId(0),
+                                                )),
+                                            ),
+                                        );
+                                    } else {
+                                        if ws2p_module.my_head.is_none() {
+                                            ws2p_module.my_head = Some(heads::generate_my_head(
+                                                &key_pair,
+                                                NodeId(soft_meta_datas.conf.my_node_id()),
+                                                soft_meta_datas.soft_name,
+                                                soft_meta_datas.soft_version,
+                                                &current_blockstamp,
+                                                None,
+                                            ));
+                                        }
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveHeads(vec![
+                                                ws2p_module.my_head.clone().unwrap(),
+                                            ]),
+                                        );
+                                    }
+                                }
+                            }
+                            DuniterMessage::NetworkRequest(ref request) => match *request {
+                                NetworkRequest::GetBlocks(
+                                    ref req_id,
+                                    ref receiver,
+                                    ref count,
+                                    ref from,
+                                ) => {
+                                    if *receiver == NodeFullId::default() {
+                                        let mut receiver_index = 0;
+                                        let mut real_receiver = NodeFullId::default();
+                                        for (ws2p_full_id, (_ep, state)) in
+                                            ws2p_module.ws2p_endpoints.clone()
+                                        {
+                                            if let WS2PConnectionState::Established = state {
+                                                if receiver_index == next_receiver {
+                                                    real_receiver = ws2p_full_id;
+                                                    break;
+                                                }
+                                                receiver_index += 1;
+                                            }
+                                        }
+                                        if real_receiver == NodeFullId::default() {
+                                            next_receiver = 0;
+                                            for (ws2p_full_id, (_ep, state)) in
+                                                ws2p_module.ws2p_endpoints.clone()
+                                            {
+                                                if let WS2PConnectionState::Established = state {
+                                                    real_receiver = ws2p_full_id;
+                                                    break;
+                                                }
+                                            }
+                                        } else {
+                                            next_receiver += 1;
+                                        }
+                                        if real_receiver != NodeFullId::default() {
+                                            let _blocks_request_result = ws2p_module
+                                                .send_request_to_specific_node(
+                                                    &real_receiver,
+                                                    &NetworkRequest::GetBlocks(
+                                                        req_id.clone(),
+                                                        *receiver,
+                                                        *count,
+                                                        *from,
+                                                    ),
+                                                );
+                                        }
+                                    } else {
+                                        let _blocks_request_result = ws2p_module
+                                            .send_request_to_specific_node(
+                                                &receiver,
+                                                &NetworkRequest::GetBlocks(
+                                                    req_id.clone(),
+                                                    *receiver,
+                                                    *count,
+                                                    *from,
+                                                ),
+                                            );
+                                    }
+                                }
+                                NetworkRequest::GetEndpoints(ref _request) => {}
+                                _ => {}
+                            },
+                            DuniterMessage::DALEvent(ref dal_event) => match *dal_event {
+                                DALEvent::StackUpValidBlock(ref _block, ref blockstamp) => {
+                                    current_blockstamp = *blockstamp;
+                                    debug!(
+                                        "WS2PModule : current_blockstamp = {}",
+                                        current_blockstamp
+                                    );
+                                    ws2p_module.my_head = Some(heads::generate_my_head(
+                                        &key_pair,
+                                        NodeId(soft_meta_datas.conf.my_node_id()),
+                                        soft_meta_datas.soft_name,
+                                        soft_meta_datas.soft_version,
+                                        &current_blockstamp,
+                                        None,
+                                    ));
+                                    ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
+                                        vec![ws2p_module.my_head.clone().unwrap()],
+                                    ));
+                                    // Send my head to all connections
+                                    let my_json_head = serializer::serialize_head(
+                                        ws2p_module.my_head.clone().unwrap(),
+                                    );
+                                    trace!("Send my HEAD: {:#?}", my_json_head);
+                                    let _results: Result<
+                                        (),
+                                        ws::Error,
+                                    > = ws2p_module
+                                        .websockets
+                                        .iter_mut()
+                                        .map(|ws| {
+                                            (ws.1).0.send(Message::text(
+                                                json!({
+                                                "name": "HEAD",
+                                                "body": {
+                                                    "heads": [my_json_head]
+                                                }
+                                            }).to_string(),
+                                            ))
+                                        }).collect();
+                                }
+                                DALEvent::RevertBlocks(ref _blocks) => {}
+                                _ => {}
+                            },
+                            DuniterMessage::DALResponse(ref dal_res) => match *dal_res.deref() {
+                                DALResponse::Blockchain(ref dal_res_bc) => {
+                                    match *dal_res_bc.deref() {
+                                        DALResBlockchain::CurrentBlock(
+                                            ref _requester_full_id,
+                                            ref current_block,
+                                            ref current_blockstamp_,
+                                        ) => {
+                                            let _current_block = current_block.deref();
+                                            debug!(
+                                                "WS2PModule : receive DALResBc::CurrentBlock({})",
+                                                current_blockstamp
+                                            );
+                                            current_blockstamp = *current_blockstamp_;
+                                            if ws2p_module.my_head.is_none() {
+                                                ws2p_module.my_head =
+                                                    Some(heads::generate_my_head(
+                                                        &key_pair,
+                                                        NodeId(soft_meta_datas.conf.my_node_id()),
+                                                        soft_meta_datas.soft_name,
+                                                        soft_meta_datas.soft_version,
+                                                        &current_blockstamp,
+                                                        None,
+                                                    ));
+                                            }
+                                            ws2p_module.send_network_event(
+                                                &NetworkEvent::ReceiveHeads(vec![
+                                                    ws2p_module.my_head.clone().unwrap(),
+                                                ]),
+                                            );
+                                        }
+                                        DALResBlockchain::UIDs(ref uids) => {
+                                            // Add uids to heads
+                                            for head in ws2p_module.heads_cache.values_mut() {
+                                                if let Some(uid_option) = uids.get(&head.pubkey()) {
+                                                    if let Some(ref uid) = *uid_option {
+                                                        head.set_uid(uid);
+                                                        ws2p_module
+                                                            .uids_cache
+                                                            .insert(head.pubkey(), uid.to_string());
+                                                    } else {
+                                                        ws2p_module
+                                                            .uids_cache
+                                                            .remove(&head.pubkey());
+                                                    }
+                                                }
+                                            }
+                                            // Resent heads to other modules
+                                            ws2p_module.send_network_event(
+                                                &NetworkEvent::ReceiveHeads(
+                                                    ws2p_module
+                                                        .heads_cache
+                                                        .values()
+                                                        .cloned()
+                                                        .collect(),
+                                                ),
+                                            );
+                                            // Resent to other modules connections that match receive uids
+                                            for (node_full_id, (ep, conn_state)) in
+                                                &ws2p_module.ws2p_endpoints
+                                            {
+                                                if let Some(uid_option) = uids.get(&node_full_id.1)
+                                                {
+                                                    ws2p_module.send_network_event(
+                                                        &NetworkEvent::ConnectionStateChange(
+                                                            *node_full_id,
+                                                            *conn_state as u32,
+                                                            uid_option.clone(),
+                                                            ep.get_url(false, false)
+                                                                .expect("Endpoint unreachable !"),
+                                                        ),
+                                                    );
+                                                }
+                                            }
+                                        }
+                                        _ => {}
+                                    }
+                                }
+                                DALResponse::Pendings(_, _) => {}
+                            },
+                            _ => {}
+                        }
+                    }
+                    WS2PThreadSignal::WS2PConnectionMessage(ws2p_conn_message) => match ws2p_module
+                        .ws2p_conn_message_pretreatment(ws2p_conn_message)
+                    {
+                        WS2PSignal::NoConnection => {
+                            warn!("WS2PSignal::NoConnection");
+                        }
+                        WS2PSignal::ConnectionEstablished(ws2p_full_id) => {
+                            let req_id =
+                                ModuleReqId(ws2p_module.requests_awaiting_response.len() as u32);
+                            let module_id = WS2PModule::id();
+                            let _current_request_result = ws2p_module
+                                .send_request_to_specific_node(
+                                    &ws2p_full_id,
+                                    &NetworkRequest::GetCurrent(
+                                        ModuleReqFullId(module_id, req_id),
+                                        ws2p_full_id,
+                                    ),
+                                );
+                            if ws2p_module.uids_cache.get(&ws2p_full_id.1).is_none() {
+                                ws2p_module.send_dal_request(&DALRequest::BlockchainRequest(
+                                    DALReqBlockchain::UIDs(vec![ws2p_full_id.1]),
+                                ));
+                            }
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Established as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module.ws2p_endpoints[&ws2p_full_id]
+                                    .0
+                                    .get_url(false, false)
+                                    .expect("Endpoint unreachable !"),
+                            ));
+                        }
+                        WS2PSignal::WSError(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::WSError as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module.ws2p_endpoints[&ws2p_full_id]
+                                    .0
+                                    .get_url(false, false)
+                                    .expect("Endpoint unreachable !"),
+                            ));
+                        }
+                        WS2PSignal::NegociationTimeout(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Denial as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module.ws2p_endpoints[&ws2p_full_id]
+                                    .0
+                                    .get_url(false, false)
+                                    .expect("Endpoint unreachable !"),
+                            ));
+                        }
+                        WS2PSignal::Timeout(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Close as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module.ws2p_endpoints[&ws2p_full_id]
+                                    .0
+                                    .get_url(false, false)
+                                    .expect("Endpoint unreachable !"),
+                            ));
+                        }
+                        WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => {
+                            //trace!("WS2PSignal::PeerCard({})", ws2p_full_id);
+                            //ws2p_module.send_network_event(NetworkEvent::ReceivePeers(_));
+                            for ep in ws2p_endpoints {
+                                match ws2p_module.ws2p_endpoints.get(
+                                    &ep.node_full_id()
+                                        .expect("WS2P: Fail to get ep.node_full_id() !"),
+                                ) {
+                                    Some(_) => {}
+                                    None => {
+                                        if let Some(_api) =
+                                            ws2p_db::string_to_api(&ep.api().0.clone())
+                                        {
+                                            endpoints_to_update_status.insert(
+                                                ep.node_full_id().expect(
+                                                    "WS2P: Fail to get ep.node_full_id() !",
+                                                ),
+                                                SystemTime::now(),
+                                            );
+                                        }
+                                        if cfg!(feature = "ssl") || ep.port() != 443 {
+                                            ws2p_module.connect_to(&ep);
+                                        }
+                                    }
+                                };
+                            }
+                        }
+                        WS2PSignal::Heads(ws2p_full_id, heads) => {
+                            trace!("WS2PSignal::Heads({}, {:?})", ws2p_full_id, heads.len());
+                            ws2p_module.send_dal_request(&DALRequest::BlockchainRequest(
+                                DALReqBlockchain::UIDs(heads.iter().map(|h| h.pubkey()).collect()),
+                            ));
+                            ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
+                                heads
+                                    .iter()
+                                    .map(|head| {
+                                        let mut new_head = head.clone();
+                                        if let Some(uid) =
+                                            ws2p_module.uids_cache.get(&head.pubkey())
+                                        {
+                                            new_head.set_uid(uid);
+                                        }
+                                        new_head
+                                    }).collect(),
+                            ));
+                        }
+                        WS2PSignal::Document(ws2p_full_id, network_doc) => {
+                            trace!("WS2PSignal::Document({})", ws2p_full_id);
+                            ws2p_module.send_network_event(&NetworkEvent::ReceiveDocuments(vec![
+                                network_doc,
+                            ]));
+                        }
+                        WS2PSignal::ReqResponse(req_id, req, recipient_full_id, response) => {
+                            match req {
+                                NetworkRequest::GetCurrent(ref _req_id, _receiver) => {
+                                    info!(
+                                        "WS2PSignal::ReceiveCurrent({}, {:?}, {:#?})",
+                                        req_id.0, req, response
+                                    );
+                                    if let Some(block) = parse_json_block(&response) {
+                                        ws2p_module.send_network_event(&NetworkEvent::ReqResponse(
+                                            Box::new(NetworkResponse::CurrentBlock(
+                                                ModuleReqFullId(WS2PModule::id(), req_id),
+                                                recipient_full_id,
+                                                Box::new(block),
+                                            )),
+                                        ));
+                                    }
+                                }
+                                NetworkRequest::GetBlocks(ref _req_id, _receiver, _count, from) => {
+                                    info!("WS2PSignal::ReceiveChunk({}, {:?})", req_id.0, req);
+                                    if response.is_array() {
+                                        let mut chunk = Vec::new();
+                                        for json_block in response.as_array().unwrap() {
+                                            if let Some(block) = parse_json_block(json_block) {
+                                                chunk.push(NetworkDocument::Block(block));
+                                            } else {
+                                                warn!("WS2PModule: Error : fail to parse one json block !");
+                                            }
+                                        }
+                                        debug!("Send chunk to followers : {}", from);
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveDocuments(chunk),
+                                        );
+                                    }
+                                }
+                                NetworkRequest::GetRequirementsPending(
+                                    _req_id,
+                                    _receiver,
+                                    min_cert,
+                                ) => {
+                                    info!(
+                                        "WS2PSignal::ReceiveRequirementsPending({}, {})",
+                                        req_id.0, min_cert
+                                    );
+                                    debug!("----------------------------------------");
+                                    debug!("-      BEGIN IDENTITIES PENDING        -");
+                                    debug!("----------------------------------------");
+                                    debug!("{:#?}", response);
+                                    debug!("----------------------------------------");
+                                    debug!("-       END IDENTITIES PENDING         -");
+                                    debug!("----------------------------------------");
+                                }
+                                _ => {}
+                            }
+                        }
+                        WS2PSignal::Empty => {}
+                        _ => {}
+                    },
+                },
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Disconnected ws2p module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {}
+                },
+            }
+            if SystemTime::now()
+                .duration_since(last_ws2p_connections_print)
+                .unwrap()
+                > Duration::new(5, 0)
+            {
+                last_ws2p_connections_print = SystemTime::now();
+                let mut connected_nodes = Vec::new();
+                for (k, (_ep, state)) in ws2p_module.ws2p_endpoints.clone() {
+                    if let WS2PConnectionState::Established = state {
+                        connected_nodes.push(k);
+                    }
+                }
+                // Print network consensus
+                match ws2p_module.get_network_consensus() {
+                    Ok(consensus_blockstamp) => {
+                        debug!(
+                            "WS2PModule : get_network_consensus() = {:?}",
+                            consensus_blockstamp
+                        );
+                        if current_blockstamp.id.0 < (consensus_blockstamp.id.0 + 2) {
+                            warn!("We probably are in a fork branch !");
+                        }
+                    }
+                    Err(e) => warn!("{:?}", e),
+                }
+                // Print current_blockstamp
+                info!(
+                    "WS2PModule : current_blockstamp() = {:?}",
+                    current_blockstamp
+                );
+                // New WS2P connection wave
+                if connected_nodes.len() < ws2p_module.conf.clone().outcoming_quota
+                    && (SystemTime::now()
+                        .duration_since(last_ws2p_connecting_wave)
+                        .unwrap()
+                        > Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)
+                        || (SystemTime::now()
+                            .duration_since(last_ws2p_connecting_wave)
+                            .unwrap()
+                            > Duration::new(*WS2P_OUTCOMING_INTERVAL_AT_STARTUP, 0)
+                            && SystemTime::now().duration_since(start_time).unwrap()
+                                < Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)))
+                {
+                    last_ws2p_connecting_wave = SystemTime::now();
+                    info!("Connected to know endpoints...");
+                    ws2p_module.connect_to_know_endpoints();
+                }
+                /*// Request blocks from network
+                if SystemTime::now()
+                    .duration_since(last_blocks_request)
+                    .unwrap() > Duration::new(*BLOCKS_REQUEST_INTERVAL, 0)
+                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
+                {
+                    let mut request_blocks_from = current_blockstamp.id.0;
+                    if request_blocks_from > 0 {
+                        request_blocks_from += 1;
+                    }
+                    info!("get chunks from all connections...");
+                    let module_id = WS2PModule::id();
+                    let _blocks_request_result =
+                        ws2p_module.send_request_to_all_connections(&NetworkRequest::GetBlocks(
+                            ModuleReqFullId(module_id, ModuleReqId(0 as u32)),
+                            NodeFullId::default(),
+                            50,
+                            request_blocks_from,
+                        ));
+                    last_blocks_request = SystemTime::now();
+                }*/
+                // Request pending_identities from network
+                if SystemTime::now()
+                    .duration_since(last_identities_request)
+                    .unwrap()
+                    > Duration::new(*PENDING_IDENTITIES_REQUEST_INTERVAL, 0)
+                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
+                {
+                    /*info!("get pending_identities from all connections...");
+                                    let _blocks_request_result = ws2p_module.send_request_to_all_connections(
+                                        &NetworkRequest::GetRequirementsPending(ModuleReqId(0 as u32), 5),
+                                    );*/
+                    last_identities_request = SystemTime::now();
+                }
+                // Write pending endpoints
+                for (ep_full_id, received_time) in endpoints_to_update_status.clone() {
+                    if SystemTime::now().duration_since(received_time).unwrap()
+                        > Duration::new(*DURATION_BEFORE_RECORDING_ENDPOINT, 0)
+                    {
+                        if let Some(&(ref ep, ref state)) =
+                            ws2p_module.ws2p_endpoints.get(&ep_full_id)
+                        {
+                            /*let dal_endpoint = duniter_dal::endpoint::DALEndpoint::new(
+                                                state.clone() as u32,
+                                                ep.node_uuid().unwrap().0,
+                                                ep.pubkey(),
+                                                duniter_dal::endpoint::string_to_api(&ep.api().0).unwrap(),
+                                                1,
+                                                ep.to_string(),
+                                                received_time.duration_since(UNIX_EPOCH).unwrap(),
+                                            );*/
+                            ws2p_db::write_endpoint(
+                                &db,
+                                &ep,
+                                state.to_u32(),
+                                SystemTime::now()
+                                    .duration_since(UNIX_EPOCH)
+                                    .unwrap()
+                                    .as_secs(),
+                            );
+                        }
+                        endpoints_to_update_status.remove(&ep_full_id);
+                    } else {
+                        info!(
+                            "Write {} endpoint in {} secs.",
+                            ep_full_id,
+                            *DURATION_BEFORE_RECORDING_ENDPOINT - SystemTime::now()
+                                .duration_since(received_time)
+                                .unwrap()
+                                .as_secs()
+                        );
+                    }
+                }
+                // ..
+            }
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    extern crate duniter_conf;
+    extern crate duniter_crypto;
+    extern crate duniter_dal;
+    extern crate duniter_documents;
+    extern crate duniter_message;
+    extern crate duniter_module;
+    extern crate duniter_network;
+
+    use super::parsers::blocks::parse_json_block;
+    use super::*;
+    use duniter_crypto::keys::PublicKey;
+    use duniter_documents::blockchain::v10::documents::BlockDocument;
+    use duniter_module::DuniterModule;
+    use duniter_network::network_endpoint::{EndpointEnum, NetworkEndpointApi};
+    use duniter_network::NetworkBlock;
+    use std::fs;
+    use std::path::PathBuf;
+    use std::time::{SystemTime, UNIX_EPOCH};
+
+    #[test]
+    fn test_parse_json_block() {
+        let json_block = json!({
+            "fork": false,
+            "version": 10,
+            "nonce": 10500000059239 as u64,
+            "number": 109966,
+            "powMin": 88,
+            "time": 1523300656,
+            "medianTime": 1523295259,
+            "membersCount": 933,
+            "monetaryMass": 146881563,
+            "unitbase": 0,
+            "issuersCount": 44,
+            "issuersFrame": 221,
+            "issuersFrameVar": 0,
+            "currency": "g1",
+            "issuer": "GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4",
+            "signature": "GCg2Lti3TdxWlhA8JF8pRI+dRQ0XZVtcC4BqO/COTpjTQFdWG6qmUNVvdeYCtR/lu1JQe3N/IhrbyV6L/6I+Cg==",
+            "hash": "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74",
+            "parameters": "",
+            "previousHash": "000004C00602F8A27AE078DE6351C0DDA1EA0974A78D2BEFA7DFBE7B7C3146FD",
+            "previousIssuer": "5SwfQubSat5SunNafCsunEGTY93nVM4kLSsuprNqQb6S",
+            "inner_hash": "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C",
+            "dividend": null,
+            "identities": [],
+            "joiners": [],
+            "actives": [],
+            "leavers": [],
+            "revoked": [],
+            "excluded": [],
+            "certifications": [
+                "Hm5qjaNuHogNRdGZ4vgnLA9DMZVUu5YWzVup5mubuxCc:8AmdBsimcLziXaCS4AcVUfPx7rkjeic7482dLbBkuZw6:109964:yHKBGMeuxyIqFb295gVNK6neRC+U0tmsX1Zed3TLjS3ZZHYYycE1piLcYsTKll4ifNVp6rm+hd/CLdHYB+29CA==",
+                "BncjgJeFpGsMCCsUfzNLEexjsbuX3V2mg9P67ov2LkwK:DyBUBNpzpfvjtwYYSaVMM6ST6t2DNg3NCE9CU9bRQFhF:105864:cJEGW9WxJwlMA2+4LNAK4YieyseUy1WIkFh1YLYD+JJtJEoCSnIQRXzhiAoRpGaj0bRz8sTpwI6PRkuVoDJJDQ=="
+            ],
+            "transactions": [
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "80FE1E83DC4D0B722CA5F8363EFC6A3E29071032EBB71C1E0DF8D4FEA589C698",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT"
+                ],
+                "inputs": [
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98284",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98519",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98779",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99054",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99326",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99599",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99884",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100174",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100469",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100746",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101036",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101327"
+                ],
+                "outputs": [
+                    "12000:0:SIG(HmH5beJqKGMeotcQUrSW7Wo5tKvAksHmfYXfiSQ9EbWz)",
+                    "12:0:SIG(6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)",
+                    "3:SIG(0)",
+                    "4:SIG(0)",
+                    "5:SIG(0)",
+                    "6:SIG(0)",
+                    "7:SIG(0)",
+                    "8:SIG(0)",
+                    "9:SIG(0)",
+                    "10:SIG(0)",
+                    "11:SIG(0)"
+                ],
+                "signatures": [
+                    "MZxoKxYgwufh/s5mwLCsYEZXtIsP1hEKCyAzLipJsvCbR9xj7wXUw0C/ahwvZfBtR7+QVPIfLmwYEol1JcHjDw=="
+                ],
+                "comment": "Adhesion 2018"
+                },
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "B80507412B35BD5EB437AE0D3EB97E60E3A4974F5CDEA1AF7E2127C0E943481F",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU"
+                ],
+                "inputs": [
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91560",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91850",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92111",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92385",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92635"
+                ],
+                "outputs": [
+                    "5000:0:SIG(BzHnbec1Gov7dLSt1EzJS7vikoQCECeuvZs4wamZAcT1)",
+                    "5:0:SIG(8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)",
+                    "3:SIG(0)",
+                    "4:SIG(0)"
+                ],
+                "signatures": [
+                    "A+ukwRvLWs1gZQ0KAqAnknEgmRQHdrnOvNuBx/WZqje17BAPrVxSxKpqwU6MiajU+ppigsYp6Bu0FdPf/tGnCQ=="
+                ],
+                "comment": ""
+                },
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "D8970E6629C0381A78534EEDD86803E9215A7EC4C494BAEA79EB19425F9B4D31",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg"
+                ],
+                "inputs": [
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36597",
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36880",
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:37082"
+                ],
+                "outputs": [
+                    "3000:0:SIG(BBC8Rnh4CWN1wBrPLevK7GRFFVDVw7Lu24YNMUmhqoHU)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)"
+                ],
+                "signatures": [
+                    "OpiF/oQfIigOeAtsteukU0w9FPSELE+BVTxhmsQ8bEeYGlwovG2VF8ZFiJkLLPi6vFuKgwzULJfjNGd97twZCw=="
+                ],
+                "comment": "1 billet pour une seance.pour un chouette film"
+                }
+            ],
+        });
+        let mut block: BlockDocument =
+            match parse_json_block(&json_block).expect("Fail to parse test json block !") {
+                NetworkBlock::V10(network_block_v10) => network_block_v10.uncompleted_block_doc,
+                _ => {
+                    panic!("Test block must be a v10 block !");
+                }
+            };
+        assert_eq!(
+            block
+                .inner_hash
+                .expect("Try to get inner_hash of an uncompleted or reduce block !")
+                .to_hex(),
+            "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C"
+        );
+        block.compute_hash();
+        assert_eq!(
+            block
+                .hash
+                .expect("Try to get hash of an uncompleted or reduce block !")
+                .0
+                .to_hex(),
+            "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74"
+        );
+    }
+
+    #[test]
+    fn endpoint_db_tests() {
+        let test_db_path = PathBuf::from("test.db");
+        if test_db_path.as_path().exists() {
+            fs::remove_file(&test_db_path).unwrap();
+        }
+        let db = WS2PModuleDatas::open_db(&test_db_path).unwrap();
+
+        let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+
+        let mut endpoint = EndpointEnum::parse_from_raw(
+            "WS2P cb06a19b g1.imirhil.fr 53012 /",
+            PubKey::Ed25519(
+                ed25519::PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv")
+                    .unwrap(),
+            ),
+            1,
+            current_time.as_secs(),
+            1,
+        ).expect("Failt to parse test endpoint !");
+
+        ws2p_db::write_endpoint(&db, &endpoint, 1, current_time.as_secs());
+        let mut written_endpoints =
+            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
+        assert_eq!(endpoint, written_endpoints.pop().unwrap());
+
+        // Test status update
+        endpoint.set_status(3);
+        ws2p_db::write_endpoint(&db, &endpoint, 3, current_time.as_secs());
+        let mut written_endpoints =
+            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
+        assert_eq!(endpoint, written_endpoints.pop().unwrap());
+    }
+
+    #[test]
+    fn ws2p_requests() {
+        let module_id = WS2PModule::id();
+        let request = NetworkRequest::GetBlocks(
+            ModuleReqFullId(module_id, ModuleReqId(58)),
+            NodeFullId::default(),
+            50,
+            0,
+        );
+        assert_eq!(
+            network_request_to_json(&request),
+            json!({
+            "reqId": format!("{:x}", 58),
+            "body": {
+                "name": "BLOCKS_CHUNK",
+                "params": {
+                    "count": 50,
+                    "fromNumber": 0
+                }
+            }
+        })
+        );
+        assert_eq!(
+            network_request_to_json(&request).to_string(),
+            "{\"body\":{\"name\":\"BLOCKS_CHUNK\",\"params\":{\"count\":50,\"fromNumber\":0}},\"reqId\":\"3a\"}"
+        );
+    }
+
+    #[test]
+    fn ws2p_parse_head() {
+        let head = json!({
+            "message": "WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3",
+            "sig": "trtK9GXvTdfND995ohWEderpO3NkIqi1X6mBeVvMcaHckq+lIGqjWvJ9t9Vccz5t+VGaSmGUihDl4q6eldIYBw==",
+            "messageV2": "WS2POTMIC:HEAD:2:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3:25:22",
+            "sigV2": "x6ehPMuYjGY+z7wEGnJGyMBxMKUdu01RWaF0b0XCtoVjg67cCvT4H0V/Qcxn4bAGqzy5ux2fA7NiI+81bBnqDw==",
+            "step": 0
+        });
+        let mut heads_count = 0;
+        if let Ok(head) = NetworkHead::from_json_value(&head) {
+            if let NetworkHead::V2(ref head_v2) = head {
+                heads_count += 1;
+                assert_eq!(
+                    head_v2.message.to_string(),
+                    String::from("WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3")
+                );
+            }
+            assert_eq!(head.verify(), true);
+        } else {
+            panic!("Fail to parse head !")
+        }
+        assert_eq!(heads_count, 1);
+    }
+}
diff --git a/ws2p/ok_message.rs b/ws2p-v1-legacy/ok_message.rs
similarity index 100%
rename from ws2p/ok_message.rs
rename to ws2p-v1-legacy/ok_message.rs
diff --git a/ws2p/parsers/blocks.rs b/ws2p-v1-legacy/parsers/blocks.rs
similarity index 96%
rename from ws2p/parsers/blocks.rs
rename to ws2p-v1-legacy/parsers/blocks.rs
index 15545b8375fa59af08f166c72c6ce6bc250278be..4a8fdc0c972d65508a139f1dd5c7e82be21e0256 100644
--- a/ws2p/parsers/blocks.rs
+++ b/ws2p-v1-legacy/parsers/blocks.rs
@@ -3,13 +3,13 @@ extern crate serde_json;
 use super::excluded::parse_exclusions_from_json_value;
 use super::identities::parse_compact_identity;
 use super::transactions::parse_transaction;
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
-use duniter_documents::blockchain::v10::documents::block::{
-    BlockV10Parameters, CurrencyName, TxDocOrTxHash,
-};
+use duniter_documents::blockchain::v10::documents::block::{BlockV10Parameters, TxDocOrTxHash};
 use duniter_documents::blockchain::v10::documents::membership::*;
 use duniter_documents::blockchain::v10::documents::BlockDocument;
-use duniter_documents::{BlockHash, BlockId, Hash};
+use duniter_documents::CurrencyName;
+use duniter_documents::{BlockHash, BlockId};
 use duniter_network::{NetworkBlock, NetworkBlockV10};
 use std::str::FromStr;
 
diff --git a/ws2p/parsers/excluded.rs b/ws2p-v1-legacy/parsers/excluded.rs
similarity index 100%
rename from ws2p/parsers/excluded.rs
rename to ws2p-v1-legacy/parsers/excluded.rs
diff --git a/ws2p/parsers/identities.rs b/ws2p-v1-legacy/parsers/identities.rs
similarity index 100%
rename from ws2p/parsers/identities.rs
rename to ws2p-v1-legacy/parsers/identities.rs
diff --git a/ws2p/parsers/memberships.rs b/ws2p-v1-legacy/parsers/memberships.rs
similarity index 100%
rename from ws2p/parsers/memberships.rs
rename to ws2p-v1-legacy/parsers/memberships.rs
diff --git a/ws2p/parsers/mod.rs b/ws2p-v1-legacy/parsers/mod.rs
similarity index 100%
rename from ws2p/parsers/mod.rs
rename to ws2p-v1-legacy/parsers/mod.rs
diff --git a/ws2p/parsers/transactions.rs b/ws2p-v1-legacy/parsers/transactions.rs
similarity index 98%
rename from ws2p/parsers/transactions.rs
rename to ws2p-v1-legacy/parsers/transactions.rs
index e0440879359b8d2562075ea96af189a76769bd29..fdd1dd353470e270e33ee7fb3f76642cd59d3db9 100644
--- a/ws2p/parsers/transactions.rs
+++ b/ws2p-v1-legacy/parsers/transactions.rs
@@ -1,13 +1,14 @@
 extern crate serde;
 extern crate serde_json;
 
+use duniter_crypto::hashs::Hash;
 use duniter_crypto::keys::*;
 use duniter_documents::blockchain::v10::documents::transaction::{
     TransactionDocument, TransactionDocumentBuilder, TransactionInput, TransactionInputUnlocks,
     TransactionOutput,
 };
 use duniter_documents::blockchain::DocumentBuilder;
-use duniter_documents::{Blockstamp, Hash};
+use duniter_documents::Blockstamp;
 
 pub fn parse_transaction(
     currency: &str,
diff --git a/ws2p/serializer.rs b/ws2p-v1-legacy/serializer.rs
similarity index 100%
rename from ws2p/serializer.rs
rename to ws2p-v1-legacy/serializer.rs
diff --git a/ws2p/ws2p_connection.rs b/ws2p-v1-legacy/ws2p_connection.rs
similarity index 97%
rename from ws2p/ws2p_connection.rs
rename to ws2p-v1-legacy/ws2p_connection.rs
index 0ecffdf1d01717d8d73656210fe8a1683733b564..cdc43a9160acc32f788add3e952dbea8331d5def 100644
--- a/ws2p/ws2p_connection.rs
+++ b/ws2p-v1-legacy/ws2p_connection.rs
@@ -1,8 +1,8 @@
 use constants::*;
 use duniter_crypto::keys::*;
 use duniter_module::ModuleReqId;
-use duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
-use duniter_network::{NetworkDocument, NodeUUID};
+use duniter_network::network_endpoint::{EndpointEnum, NetworkEndpointApi};
+use duniter_network::{NetworkDocument, NodeId};
 use parsers::blocks::parse_json_block;
 use rand::Rng;
 use std::sync::mpsc;
@@ -270,7 +270,7 @@ pub enum WS2PConnectionMessagePayload {
     ValidAckMessage(String, WS2PConnectionState),
     ValidOk(WS2PConnectionState),
     DalRequest(ModuleReqId, serde_json::Value),
-    PeerCard(serde_json::Value, Vec<NetworkEndpoint>),
+    PeerCard(serde_json::Value, Vec<EndpointEnum>),
     Heads(Vec<serde_json::Value>),
     Document(NetworkDocument),
     ReqResponse(ModuleReqId, serde_json::Value),
@@ -295,7 +295,7 @@ pub enum WS2PCloseConnectionReason {
 #[derive(Debug, Clone)]
 pub struct WS2PConnectionMetaDatas {
     pub state: WS2PConnectionState,
-    pub remote_uuid: Option<NodeUUID>,
+    pub remote_uuid: Option<NodeId>,
     pub remote_pubkey: Option<PubKey>,
     pub challenge: String,
     pub remote_challenge: String,
@@ -536,18 +536,17 @@ impl WS2PConnectionMetaDatas {
                 Some(raw_pubkey) => {
                     match ed25519::PublicKey::from_base58(raw_pubkey.as_str().unwrap_or("")) {
                         Ok(pubkey) => {
-                            let mut ws2p_endpoints: Vec<
-                                NetworkEndpoint,
-                            > = Vec::new();
+                            let mut ws2p_endpoints: Vec<EndpointEnum> = Vec::new();
                             match peer.get("endpoints") {
                                 Some(endpoints) => match endpoints.as_array() {
                                     Some(array_endpoints) => {
                                         for endpoint in array_endpoints {
-                                            if let Some(ep) = NetworkEndpoint::parse_from_raw(
+                                            if let Ok(ep) = EndpointEnum::parse_from_raw(
                                                 endpoint.as_str().unwrap_or(""),
                                                 PubKey::Ed25519(pubkey),
                                                 0,
                                                 0,
+                                                1u16,
                                             ) {
                                                 if ep.api()
                                                     == NetworkEndpointApi(String::from("WS2P"))
@@ -577,7 +576,7 @@ impl WS2PConnectionMetaDatas {
 }
 
 pub fn get_random_connection<S: ::std::hash::BuildHasher>(
-    connections: &HashMap<NodeFullId, (NetworkEndpoint, WS2PConnectionState), S>,
+    connections: &HashMap<NodeFullId, (EndpointEnum, WS2PConnectionState), S>,
 ) -> NodeFullId {
     let mut rng = rand::thread_rng();
     let mut loop_count = 0;
@@ -597,13 +596,13 @@ pub fn get_random_connection<S: ::std::hash::BuildHasher>(
 }
 
 pub fn connect_to_ws2p_endpoint(
-    endpoint: &NetworkEndpoint,
+    endpoint: &EndpointEnum,
     conductor_sender: &mpsc::Sender<WS2PThreadSignal>,
     currency: &str,
     key_pair: KeyPairEnum,
 ) -> ws::Result<()> {
     // Get endpoint url
-    let ws_url = endpoint.get_url(true);
+    let ws_url = endpoint.get_url(true, false).expect("Endpoint unreachable");
 
     // Create WS2PConnectionMetaDatass
     let mut conn_meta_datas = WS2PConnectionMetaDatas::new(
diff --git a/ws2p/ws2p_db.rs b/ws2p-v1-legacy/ws2p_db.rs
similarity index 84%
rename from ws2p/ws2p_db.rs
rename to ws2p-v1-legacy/ws2p_db.rs
index 5d34ff28aeb62a6c6bf5f08f3df319972295497b..725347be2659331e049b69db1e96218b38b16bda 100644
--- a/ws2p/ws2p_db.rs
+++ b/ws2p-v1-legacy/ws2p_db.rs
@@ -7,7 +7,7 @@ extern crate serde_json;
 extern crate sqlite;
 
 use duniter_crypto::keys::*;
-use duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
+use duniter_network::network_endpoint::{EndpointEnum, NetworkEndpointApi};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum EndpointApi {
@@ -54,7 +54,7 @@ pub fn api_to_integer(api: &NetworkEndpointApi) -> i64 {
 pub fn get_endpoints_for_api(
     db: &sqlite::Connection,
     api: &NetworkEndpointApi,
-) -> Vec<NetworkEndpoint> {
+) -> Vec<EndpointEnum> {
     let mut cursor:sqlite::Cursor = db
         .prepare("SELECT hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check FROM endpoints WHERE api=? ORDER BY status DESC;")
         .expect("get_endpoints_for_api() : Error in SQL request !")
@@ -71,14 +71,18 @@ pub fn get_endpoints_for_api(
         let raw_ep = row[6].as_string().unwrap().to_string();
         let ep_issuer =
             PubKey::Ed25519(ed25519::PublicKey::from_base58(row[3].as_string().unwrap()).unwrap());
-        let mut ep = match NetworkEndpoint::parse_from_raw(
+        let mut ep = match EndpointEnum::parse_from_raw(
             &raw_ep,
             ep_issuer,
             row[1].as_integer().unwrap() as u32,
             row[7].as_integer().unwrap() as u64,
+            1u16,
         ) {
-            Some(ep) => ep,
-            None => panic!(format!("Fail to parse endpoint : {}", raw_ep)),
+            Ok(ep) => ep,
+            Err(e) => panic!(format!(
+                "Fail to parse endpoint : {} (Error: {:?})",
+                raw_ep, e
+            )),
         };
         ep.set_status(row[1].as_integer().unwrap() as u32);
         ep.set_last_check(row[7].as_integer().unwrap() as u64);
@@ -90,7 +94,7 @@ pub fn get_endpoints_for_api(
 
 pub fn write_endpoint(
     db: &sqlite::Connection,
-    endpoint: &NetworkEndpoint,
+    endpoint: &EndpointEnum,
     new_status: u32,
     new_last_check: u64,
 ) {
@@ -119,14 +123,14 @@ pub fn write_endpoint(
                 hash_full_id
             )).expect("Fail to parse SQL request update endpoint  status !");
         }
-    } else if let NetworkEndpoint::V1(ref ep_v1) = *endpoint {
+    } else if let EndpointEnum::V1(ref ep_v10) = *endpoint {
         db
                     .execute(
                         format!(
                             "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});",
-                            ep_v1.hash_full_id.expect("ep_v1.hash_full_id = None"), new_status, ep_v1.node_id.expect("ep_v1.node_id = None").0,
-                            ep_v1.issuer.to_string(), api_to_integer(&ep_v1.api),
-                            ep_v1.version, ep_v1.raw_endpoint, new_last_check
+                            ep_v10.hash_full_id.expect("ep_v10.hash_full_id = None"), new_status, ep_v10.node_id.expect("ep_v10.node_id = None").0,
+                            ep_v10.issuer.to_string(), api_to_integer(&ep_v10.api),
+                            ep_v10.version, ep_v10.raw_endpoint, new_last_check
                         )
                     )
                     .expect("Fail to parse SQL request INSERT endpoint !");
diff --git a/ws2p/ws2p_requests.rs b/ws2p-v1-legacy/ws2p_requests.rs
similarity index 100%
rename from ws2p/ws2p_requests.rs
rename to ws2p-v1-legacy/ws2p_requests.rs
diff --git a/ws2p/Cargo.toml b/ws2p/Cargo.toml
index 861528b1d45ffa546c45fc43a60f0909faec9afa..80b49ca3e9700da74ad3117e684ea2540a6b8a8b 100644
--- a/ws2p/Cargo.toml
+++ b/ws2p/Cargo.toml
@@ -1,8 +1,8 @@
 [package]
-name = "duniter-ws2p"
+name = "durs-ws2p"
 version = "0.1.0-a0.1"
 authors = ["librelois <elois@ifee.fr>"]
-description = "WebSocketToPeer API for the Duniter project."
+description = "WebSocketToPeer API for DURS Project."
 license = "AGPL-3.0"
 
 [lib]
@@ -11,24 +11,14 @@ path = "lib.rs"
 [dependencies]
 duniter-conf = { path = "../conf" }
 duniter-crypto = { path = "../crypto" }
-duniter-dal = { path = "../dal" }
-duniter-documents = { path = "../documents" }
-duniter-message =  { path = "../message" }
+duniter-message= { path = "../message" }
 duniter-module = { path = "../module" }
 duniter-network = { path = "../network" }
-duniter-wotb = { path = "../wotb" }
-lazy_static = "1.0.*"
+durs-ws2p-messages = { path = "../ws2p-messages" }
 log = "0.4.*"
-rand = "0.4.*"
-regex = "0.2.*"
-rust-crypto = "0.2.*"
-sqlite = "0.23.*"
 serde = "1.0.*"
 serde_derive = "1.0.*"
-serde_json = "1.0.*"
-ws = { version = "0.7.*", features = ["permessage-deflate"] }
 
 [features]
-ssl = ["ws/ssl"]
 # Treat warnings as a build error.
 strict = []
\ No newline at end of file
diff --git a/ws2p/constants.rs b/ws2p/constants.rs
index bc8844462cb4e4e94db783f0ce47d604b17d3c69..c934b358976521c0010c3d821d11517b59542ede 100644
--- a/ws2p/constants.rs
+++ b/ws2p/constants.rs
@@ -1,18 +1,24 @@
-extern crate regex;
+//  Copyright (C) 2018  The Durs Project Developers.
+//
+// This program 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, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 
-use self::regex::Regex;
+pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10;
 
-lazy_static! {
-    #[derive(Debug)]
-    pub static ref WS2P_V1_ENDPOINT_REGEX: Regex = Regex::new(
-        "^WS2P (?P<version>[1-9][0-9]* )?(?P<uuid>[a-f0-9]{6,8}) (?P<host>[a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) (?P<port>[0-9]+)(?: /?(?P<path>.+)?)? *$"
-    ).unwrap();
-}
-pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75;
+/*pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75;
 pub static WS2P_OUTCOMING_INTERVAL: &'static u64 = &300;
-pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10;
 pub static WS2P_NEGOTIATION_TIMEOUT: &'static u64 = &15;
-//pub static WS2P_REQUEST_TIMEOUT : &'static u64 = &30;
+pub static WS2P_REQUEST_TIMEOUT: &'static u64 = &30;
 pub static WS2P_EXPIRE_TIMEOUT: &'static u64 = &120;
 pub static WS2P_SPAM_INTERVAL_IN_MILLI_SECS: &'static u64 = &80;
 pub static WS2P_SPAM_LIMIT: &'static usize = &6;
@@ -20,3 +26,4 @@ pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &'static u64 = &100;
 pub static DURATION_BEFORE_RECORDING_ENDPOINT: &'static u64 = &180;
 pub static BLOCKS_REQUEST_INTERVAL: &'static u64 = &60;
 pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &'static u64 = &40;
+*/
diff --git a/ws2p/lib.rs b/ws2p/lib.rs
index f1e34fdfbf14cf079431a177985099eaa31274a3..14796e5e671c0ad96115c82caf6ce6794e36deba 100644
--- a/ws2p/lib.rs
+++ b/ws2p/lib.rs
@@ -13,11 +13,11 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-//! Crate containing Duniter-rust core.
+//! WebSocketToPeer API for the Duniter project.
 
 #![cfg_attr(feature = "strict", deny(warnings))]
-#![cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
 #![deny(
+    missing_docs,
     missing_debug_implementations,
     missing_copy_implementations,
     trivial_casts,
@@ -26,65 +26,29 @@
     unused_import_braces,
     unused_qualifications
 )]
-#![recursion_limit = "256"]
 
-#[macro_use]
-extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate serde_derive;
-#[macro_use]
-extern crate serde_json;
 
 extern crate duniter_conf;
 extern crate duniter_crypto;
-extern crate duniter_dal;
-extern crate duniter_documents;
 extern crate duniter_message;
 extern crate duniter_module;
 extern crate duniter_network;
-extern crate rand;
-extern crate sqlite;
-extern crate ws;
+extern crate durs_ws2p_messages;
 
-mod ack_message;
-mod connect_message;
-pub mod constants;
-mod datas;
-mod heads;
-mod ok_message;
-pub mod parsers;
-pub mod serializer;
-pub mod ws2p_connection;
-pub mod ws2p_db;
-pub mod ws2p_requests;
+mod constants;
 
-use ack_message::WS2PAckMessageV1;
-use connect_message::WS2PConnectMessageV1;
 use constants::*;
-use datas::*;
 use duniter_conf::DuRsConf;
 use duniter_crypto::keys::*;
-use duniter_dal::dal_event::DALEvent;
-use duniter_dal::dal_requests::{DALReqBlockchain, DALRequest, DALResBlockchain, DALResponse};
-use duniter_documents::Blockstamp;
 use duniter_message::DuniterMessage;
 use duniter_module::*;
 use duniter_network::network_endpoint::*;
-use duniter_network::network_head::*;
 use duniter_network::*;
-use ok_message::WS2POkMessageV1;
-use parsers::blocks::parse_json_block;
-use std::collections::HashMap;
-use std::ops::Deref;
-use std::path::PathBuf;
 use std::sync::mpsc;
-use std::thread;
-use std::time::{Duration, SystemTime, UNIX_EPOCH};
-use ws::Message;
-use ws2p_connection::*;
-use ws2p_requests::network_request_to_json;
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// WS2P Configuration
@@ -92,7 +56,7 @@ pub struct WS2PConf {
     /// Limit of outcoming connections
     pub outcoming_quota: usize,
     /// Default WS2P endpoints provides by configuration file
-    pub sync_endpoints: Vec<NetworkEndpoint>,
+    pub sync_endpoints: Vec<EndpointEnum>,
 }
 
 impl Default for WS2PConf {
@@ -100,7 +64,7 @@ impl Default for WS2PConf {
         WS2PConf {
             outcoming_quota: *WS2P_DEFAULT_OUTCOMING_QUOTA,
             sync_endpoints: vec![
-                NetworkEndpoint::parse_from_raw(
+                EndpointEnum::parse_from_raw(
                     "WS2P c1c39a0a g1-monit.librelois.fr 443 /ws2p",
                     PubKey::Ed25519(
                         ed25519::PublicKey::from_base58(
@@ -109,8 +73,9 @@ impl Default for WS2PConf {
                     ),
                     0,
                     0,
+                    1u16,
                 ).unwrap(),
-                NetworkEndpoint::parse_from_raw(
+                EndpointEnum::parse_from_raw(
                     "WS2P b48824f0 g1.monnaielibreoccitanie.org 443 /ws2p",
                     PubKey::Ed25519(
                         ed25519::PublicKey::from_base58(
@@ -119,68 +84,57 @@ impl Default for WS2PConf {
                     ),
                     0,
                     0,
+                    1u16,
                 ).unwrap(),
             ],
         }
     }
 }
 
-#[derive(Debug)]
-/// Store a Signal receive from network (after message treatment)
-pub enum WS2PSignal {
-    /// Receive a websocket error from a connextion. `NodeFullId` store the identifier of connection.
-    WSError(NodeFullId),
-    /// A new connection is successfully established with `NodeFullId`.
-    ConnectionEstablished(NodeFullId),
-    NegociationTimeout(NodeFullId),
-    Timeout(NodeFullId),
-    DalRequest(NodeFullId, ModuleReqId, serde_json::Value),
-    PeerCard(NodeFullId, serde_json::Value, Vec<NetworkEndpoint>),
-    Heads(NodeFullId, Vec<NetworkHead>),
-    Document(NodeFullId, NetworkDocument),
-    ReqResponse(ModuleReqId, NetworkRequest, NodeFullId, serde_json::Value),
-    Empty,
-    NoConnection,
-}
-
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum NetworkConsensusError {
-    InsufficientData(usize),
-    Fork,
-}
+/// WS2Pv2 Module
+pub struct WS2Pv2Module {}
 
-#[derive(Debug)]
-pub enum SendRequestError {
-    RequestTypeMustNotBeTransmitted(),
-    WSError(usize, Vec<ws::Error>),
+impl Default for WS2Pv2Module {
+    fn default() -> WS2Pv2Module {
+        WS2Pv2Module {}
+    }
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct WS2PModule {}
-
 #[derive(Debug)]
-pub enum WS2PThreadSignal {
-    DuniterMessage(Box<DuniterMessage>),
-    WS2PConnectionMessage(WS2PConnectionMessage),
+/// WS2PFeaturesParseError
+pub enum WS2PFeaturesParseError {
+    /// UnknowApiFeature
+    UnknowApiFeature(String),
 }
 
-pub trait WS2PMessage: Sized {
-    fn parse(v: &serde_json::Value, currency: String) -> Option<Self>;
-    fn to_raw(&self) -> String;
-    fn sign(&self, key_pair: KeyPairEnum) -> Sig {
-        key_pair.sign(self.to_raw().as_bytes())
-    }
-    fn verify(&self) -> bool;
-    //fn parse_and_verify(v: serde_json::Value, currency: String) -> bool;
-}
-
-impl Default for WS2PModule {
-    fn default() -> WS2PModule {
-        WS2PModule {}
+impl ApiModule<DuRsConf, DuniterMessage> for WS2Pv2Module {
+    type ParseErr = WS2PFeaturesParseError;
+    /// Parse raw api features
+    fn parse_raw_api_features(str_features: &str) -> Result<ApiFeatures, Self::ParseErr> {
+        let str_features: Vec<&str> = str_features.split(' ').collect();
+        let mut api_features = Vec::with_capacity(0);
+        for str_feature in str_features {
+            match str_feature {
+                "DEF" => api_features[0] += 1u8,
+                "LOW" => api_features[0] += 2u8,
+                "ABF" => api_features[0] += 4u8,
+                _ => {
+                    debug!(
+                        "parse_raw_api_features() = UnknowApiFeature({})",
+                        str_feature
+                    );
+                    return Err(WS2PFeaturesParseError::UnknowApiFeature(String::from(
+                        str_feature,
+                    )));
+                }
+            }
+        }
+        Ok(ApiFeatures(api_features))
     }
 }
 
-impl NetworkModule<DuRsConf, DuniterMessage> for WS2PModule {
+impl NetworkModule<DuRsConf, DuniterMessage> for WS2Pv2Module {
     fn sync(
         _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
         _keys: RequiredKeysContent,
@@ -188,13 +142,11 @@ impl NetworkModule<DuRsConf, DuniterMessage> for WS2PModule {
         _main_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
         _sync_endpoint: SyncEndpoint,
     ) -> Result<(), ModuleInitError> {
-        println!("Downlaod blockchain from network...");
-        println!("Error : not yet implemented !");
-        Ok(())
+        unimplemented!()
     }
 }
 
-impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule {
+impl DuniterModule<DuRsConf, DuniterMessage> for WS2Pv2Module {
     type ModuleConf = WS2PConf;
 
     fn id() -> ModuleId {
@@ -207,927 +159,12 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule {
         RequiredKeys::NetworkKeyPair()
     }
     fn start(
-        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
-        keys: RequiredKeysContent,
-        conf: WS2PConf,
-        rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
-        load_conf_only: bool,
+        _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        _keys: RequiredKeysContent,
+        _conf: WS2PConf,
+        _rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        _load_conf_only: bool,
     ) -> Result<(), ModuleInitError> {
-        // Get start time
-        let start_time = SystemTime::now();
-
-        // Define WS2PModuleDatas
-        let mut ws2p_module = WS2PModuleDatas {
-            followers: Vec::new(),
-            key_pair: None,
-            currency: None,
-            conf,
-            node_id: NodeUUID(soft_meta_datas.conf.my_node_id()),
-            main_thread_channel: mpsc::channel(),
-            ws2p_endpoints: HashMap::new(),
-            websockets: HashMap::new(),
-            requests_awaiting_response: HashMap::new(),
-            heads_cache: HashMap::new(),
-            my_head: None,
-            uids_cache: HashMap::new(),
-        };
-
-        // load conf
-        let key_pair = match keys {
-            RequiredKeysContent::NetworkKeyPair(key_pair) => key_pair,
-            _ => panic!("WS2PModule fatal error at load_conf() : keys != NetworkKeyPair"),
-        };
-        let mut ws2p_endpoints = HashMap::new();
-        for ep in ws2p_module.conf.sync_endpoints.clone() {
-            ws2p_endpoints.insert(
-                ep.node_full_id()
-                    .expect("Fail to get endpoint node_full_id"),
-                (ep.clone(), WS2PConnectionState::Close),
-            );
-            info!("Load sync endpoint {}", ep.raw());
-        }
-        ws2p_module.key_pair = Some(key_pair);
-        ws2p_module.currency = Some(soft_meta_datas.conf.currency().to_string());
-        ws2p_module.ws2p_endpoints = ws2p_endpoints;
-
-        // Create ws2p main thread channel
-        let ws2p_sender_clone = ws2p_module.main_thread_channel.0.clone();
-
-        // Create proxy channel
-        let (proxy_sender, proxy_receiver): (
-            mpsc::Sender<DuniterMessage>,
-            mpsc::Receiver<DuniterMessage>,
-        ) = mpsc::channel();
-        let proxy_sender_clone = proxy_sender.clone();
-
-        // Launch a proxy thread that transform DuniterMessage to WS2PThreadSignal(DuniterMessage)
-        thread::spawn(move || {
-            // Send proxy sender to main
-            rooter_sender
-                .send(RooterThreadMessage::ModuleSender(proxy_sender_clone))
-                .expect("Fatal error : ws2p module fail to send is sender channel !");
-            debug!("Send ws2p sender to main thread.");
-            loop {
-                match proxy_receiver.recv() {
-                    Ok(message) => {
-                        ws2p_sender_clone
-                            .send(WS2PThreadSignal::DuniterMessage(Box::new(message.clone())))
-                            .expect(
-                                "Fatal error : fail to relay DuniterMessage to ws2p main thread !",
-                            );
-                        if let DuniterMessage::Stop() = message {
-                            break;
-                        };
-                    }
-                    Err(e) => panic!(format!("{}", e)),
-                }
-            }
-        });
-
-        // open ws2p bdd
-        let mut db_path =
-            duniter_conf::datas_path(&soft_meta_datas.profile, &soft_meta_datas.conf.currency());
-        db_path.push("ws2p.db");
-        let db = WS2PModuleDatas::open_db(&db_path).expect("Fatal error : fail to open WS2P DB !");
-
-        // Get ws2p endpoints in BDD
-        let mut count = 0;
-        let dal_enpoints =
-            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
-        for ep in dal_enpoints {
-            if ep.api() == NetworkEndpointApi(String::from("WS2P"))
-                && (cfg!(feature = "ssl") || ep.port() != 443)
-            {
-                count += 1;
-                ws2p_module.ws2p_endpoints.insert(
-                    ep.node_full_id()
-                        .expect("WS2P: Fail to get ep.node_full_id() !"),
-                    (ep.clone(), WS2PConnectionState::from(ep.status())),
-                );
-            }
-        }
-        info!("Load {} endpoints from bdd !", count);
-
-        // Stop here in load_conf_only mode
-        if load_conf_only {
-            return Ok(());
-        }
-
-        // Initialize variables
-        let mut last_ws2p_connecting_wave = SystemTime::now();
-        let mut last_ws2p_connections_print = SystemTime::now();
-        let mut endpoints_to_update_status: HashMap<NodeFullId, SystemTime> = HashMap::new();
-        let mut last_identities_request = UNIX_EPOCH;
-        let mut current_blockstamp = Blockstamp::default();
-        let mut next_receiver = 0;
-
-        // Start
-        ws2p_module.connect_to_know_endpoints();
-        loop {
-            match ws2p_module
-                .main_thread_channel
-                .1
-                .recv_timeout(Duration::from_millis(200))
-            {
-                Ok(message) => match message {
-                    WS2PThreadSignal::DuniterMessage(ref duniter_mesage) => {
-                        match *duniter_mesage.deref() {
-                            DuniterMessage::Stop() => break,
-                            DuniterMessage::Followers(ref new_followers) => {
-                                info!("WS2P module receive followers !");
-                                for new_follower in new_followers {
-                                    debug!("WS2PModule : push one follower.");
-                                    ws2p_module.followers.push(new_follower.clone());
-                                    if current_blockstamp == Blockstamp::default() {
-                                        // Request local current blockstamp
-                                        ws2p_module.send_dal_request(
-                                            &DALRequest::BlockchainRequest(
-                                                DALReqBlockchain::CurrentBlock(ModuleReqFullId(
-                                                    WS2PModule::id(),
-                                                    ModuleReqId(0),
-                                                )),
-                                            ),
-                                        );
-                                    } else {
-                                        if ws2p_module.my_head.is_none() {
-                                            ws2p_module.my_head = Some(heads::generate_my_head(
-                                                &key_pair,
-                                                NodeUUID(soft_meta_datas.conf.my_node_id()),
-                                                soft_meta_datas.soft_name,
-                                                soft_meta_datas.soft_version,
-                                                &current_blockstamp,
-                                                None,
-                                            ));
-                                        }
-                                        ws2p_module.send_network_event(
-                                            &NetworkEvent::ReceiveHeads(vec![
-                                                ws2p_module.my_head.clone().unwrap(),
-                                            ]),
-                                        );
-                                    }
-                                }
-                            }
-                            DuniterMessage::NetworkRequest(ref request) => match *request {
-                                NetworkRequest::GetBlocks(
-                                    ref req_id,
-                                    ref receiver,
-                                    ref count,
-                                    ref from,
-                                ) => {
-                                    if *receiver == NodeFullId::default() {
-                                        let mut receiver_index = 0;
-                                        let mut real_receiver = NodeFullId::default();
-                                        for (ws2p_full_id, (_ep, state)) in
-                                            ws2p_module.ws2p_endpoints.clone()
-                                        {
-                                            if let WS2PConnectionState::Established = state {
-                                                if receiver_index == next_receiver {
-                                                    real_receiver = ws2p_full_id;
-                                                    break;
-                                                }
-                                                receiver_index += 1;
-                                            }
-                                        }
-                                        if real_receiver == NodeFullId::default() {
-                                            next_receiver = 0;
-                                            for (ws2p_full_id, (_ep, state)) in
-                                                ws2p_module.ws2p_endpoints.clone()
-                                            {
-                                                if let WS2PConnectionState::Established = state {
-                                                    real_receiver = ws2p_full_id;
-                                                    break;
-                                                }
-                                            }
-                                        } else {
-                                            next_receiver += 1;
-                                        }
-                                        if real_receiver != NodeFullId::default() {
-                                            let _blocks_request_result = ws2p_module
-                                                .send_request_to_specific_node(
-                                                    &real_receiver,
-                                                    &NetworkRequest::GetBlocks(
-                                                        req_id.clone(),
-                                                        *receiver,
-                                                        *count,
-                                                        *from,
-                                                    ),
-                                                );
-                                        }
-                                    } else {
-                                        let _blocks_request_result = ws2p_module
-                                            .send_request_to_specific_node(
-                                                &receiver,
-                                                &NetworkRequest::GetBlocks(
-                                                    req_id.clone(),
-                                                    *receiver,
-                                                    *count,
-                                                    *from,
-                                                ),
-                                            );
-                                    }
-                                }
-                                NetworkRequest::GetEndpoints(ref _request) => {}
-                                _ => {}
-                            },
-                            DuniterMessage::DALEvent(ref dal_event) => match *dal_event {
-                                DALEvent::StackUpValidBlock(ref _block, ref blockstamp) => {
-                                    current_blockstamp = *blockstamp;
-                                    debug!(
-                                        "WS2PModule : current_blockstamp = {}",
-                                        current_blockstamp
-                                    );
-                                    ws2p_module.my_head = Some(heads::generate_my_head(
-                                        &key_pair,
-                                        NodeUUID(soft_meta_datas.conf.my_node_id()),
-                                        soft_meta_datas.soft_name,
-                                        soft_meta_datas.soft_version,
-                                        &current_blockstamp,
-                                        None,
-                                    ));
-                                    ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
-                                        vec![ws2p_module.my_head.clone().unwrap()],
-                                    ));
-                                    // Send my head to all connections
-                                    let my_json_head = serializer::serialize_head(
-                                        ws2p_module.my_head.clone().unwrap(),
-                                    );
-                                    trace!("Send my HEAD: {:#?}", my_json_head);
-                                    let _results: Result<
-                                        (),
-                                        ws::Error,
-                                    > = ws2p_module
-                                        .websockets
-                                        .iter_mut()
-                                        .map(|ws| {
-                                            (ws.1).0.send(Message::text(
-                                                json!({
-                                                "name": "HEAD",
-                                                "body": {
-                                                    "heads": [my_json_head]
-                                                }
-                                            }).to_string(),
-                                            ))
-                                        }).collect();
-                                }
-                                DALEvent::RevertBlocks(ref _blocks) => {}
-                                _ => {}
-                            },
-                            DuniterMessage::DALResponse(ref dal_res) => match *dal_res.deref() {
-                                DALResponse::Blockchain(ref dal_res_bc) => {
-                                    match *dal_res_bc.deref() {
-                                        DALResBlockchain::CurrentBlock(
-                                            ref _requester_full_id,
-                                            ref current_block,
-                                            ref current_blockstamp_,
-                                        ) => {
-                                            let _current_block = current_block.deref();
-                                            debug!(
-                                                "WS2PModule : receive DALResBc::CurrentBlock({})",
-                                                current_blockstamp
-                                            );
-                                            current_blockstamp = *current_blockstamp_;
-                                            if ws2p_module.my_head.is_none() {
-                                                ws2p_module.my_head =
-                                                    Some(heads::generate_my_head(
-                                                        &key_pair,
-                                                        NodeUUID(soft_meta_datas.conf.my_node_id()),
-                                                        soft_meta_datas.soft_name,
-                                                        soft_meta_datas.soft_version,
-                                                        &current_blockstamp,
-                                                        None,
-                                                    ));
-                                            }
-                                            ws2p_module.send_network_event(
-                                                &NetworkEvent::ReceiveHeads(vec![
-                                                    ws2p_module.my_head.clone().unwrap(),
-                                                ]),
-                                            );
-                                        }
-                                        DALResBlockchain::UIDs(ref uids) => {
-                                            // Add uids to heads
-                                            for head in ws2p_module.heads_cache.values_mut() {
-                                                if let Some(uid_option) = uids.get(&head.pubkey()) {
-                                                    if let Some(ref uid) = *uid_option {
-                                                        head.set_uid(uid);
-                                                        ws2p_module
-                                                            .uids_cache
-                                                            .insert(head.pubkey(), uid.to_string());
-                                                    } else {
-                                                        ws2p_module
-                                                            .uids_cache
-                                                            .remove(&head.pubkey());
-                                                    }
-                                                }
-                                            }
-                                            // Resent heads to other modules
-                                            ws2p_module.send_network_event(
-                                                &NetworkEvent::ReceiveHeads(
-                                                    ws2p_module
-                                                        .heads_cache
-                                                        .values()
-                                                        .cloned()
-                                                        .collect(),
-                                                ),
-                                            );
-                                            // Resent to other modules connections that match receive uids
-                                            for (node_full_id, (ep, conn_state)) in
-                                                &ws2p_module.ws2p_endpoints
-                                            {
-                                                if let Some(uid_option) = uids.get(&node_full_id.1)
-                                                {
-                                                    ws2p_module.send_network_event(
-                                                        &NetworkEvent::ConnectionStateChange(
-                                                            *node_full_id,
-                                                            *conn_state as u32,
-                                                            uid_option.clone(),
-                                                            ep.get_url(false),
-                                                        ),
-                                                    );
-                                                }
-                                            }
-                                        }
-                                        _ => {}
-                                    }
-                                }
-                                DALResponse::Pendings(_, _) => {}
-                            },
-                            _ => {}
-                        }
-                    }
-                    WS2PThreadSignal::WS2PConnectionMessage(ws2p_conn_message) => match ws2p_module
-                        .ws2p_conn_message_pretreatment(ws2p_conn_message)
-                    {
-                        WS2PSignal::NoConnection => {
-                            warn!("WS2PSignal::NoConnection");
-                        }
-                        WS2PSignal::ConnectionEstablished(ws2p_full_id) => {
-                            let req_id =
-                                ModuleReqId(ws2p_module.requests_awaiting_response.len() as u32);
-                            let module_id = WS2PModule::id();
-                            let _current_request_result = ws2p_module
-                                .send_request_to_specific_node(
-                                    &ws2p_full_id,
-                                    &NetworkRequest::GetCurrent(
-                                        ModuleReqFullId(module_id, req_id),
-                                        ws2p_full_id,
-                                    ),
-                                );
-                            if ws2p_module.uids_cache.get(&ws2p_full_id.1).is_none() {
-                                ws2p_module.send_dal_request(&DALRequest::BlockchainRequest(
-                                    DALReqBlockchain::UIDs(vec![ws2p_full_id.1]),
-                                ));
-                            }
-                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
-                                ws2p_full_id,
-                                WS2PConnectionState::Established as u32,
-                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
-                                ws2p_module.ws2p_endpoints[&ws2p_full_id].0.get_url(false),
-                            ));
-                        }
-                        WS2PSignal::WSError(ws2p_full_id) => {
-                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
-                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
-                                ws2p_full_id,
-                                WS2PConnectionState::WSError as u32,
-                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
-                                ws2p_module.ws2p_endpoints[&ws2p_full_id].0.get_url(false),
-                            ));
-                        }
-                        WS2PSignal::NegociationTimeout(ws2p_full_id) => {
-                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
-                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
-                                ws2p_full_id,
-                                WS2PConnectionState::Denial as u32,
-                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
-                                ws2p_module.ws2p_endpoints[&ws2p_full_id].0.get_url(false),
-                            ));
-                        }
-                        WS2PSignal::Timeout(ws2p_full_id) => {
-                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
-                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
-                                ws2p_full_id,
-                                WS2PConnectionState::Close as u32,
-                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
-                                ws2p_module.ws2p_endpoints[&ws2p_full_id].0.get_url(false),
-                            ));
-                        }
-                        WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => {
-                            //trace!("WS2PSignal::PeerCard({})", ws2p_full_id);
-                            //ws2p_module.send_network_event(NetworkEvent::ReceivePeers(_));
-                            for ep in ws2p_endpoints {
-                                match ws2p_module.ws2p_endpoints.get(
-                                    &ep.node_full_id()
-                                        .expect("WS2P: Fail to get ep.node_full_id() !"),
-                                ) {
-                                    Some(_) => {}
-                                    None => {
-                                        if let Some(_api) =
-                                            ws2p_db::string_to_api(&ep.api().0.clone())
-                                        {
-                                            endpoints_to_update_status.insert(
-                                                ep.node_full_id().expect(
-                                                    "WS2P: Fail to get ep.node_full_id() !",
-                                                ),
-                                                SystemTime::now(),
-                                            );
-                                        }
-                                        if cfg!(feature = "ssl") || ep.port() != 443 {
-                                            ws2p_module.connect_to(&ep);
-                                        }
-                                    }
-                                };
-                            }
-                        }
-                        WS2PSignal::Heads(ws2p_full_id, heads) => {
-                            trace!("WS2PSignal::Heads({}, {:?})", ws2p_full_id, heads.len());
-                            ws2p_module.send_dal_request(&DALRequest::BlockchainRequest(
-                                DALReqBlockchain::UIDs(heads.iter().map(|h| h.pubkey()).collect()),
-                            ));
-                            ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
-                                heads
-                                    .iter()
-                                    .map(|head| {
-                                        let mut new_head = head.clone();
-                                        if let Some(uid) =
-                                            ws2p_module.uids_cache.get(&head.pubkey())
-                                        {
-                                            new_head.set_uid(uid);
-                                        }
-                                        new_head
-                                    }).collect(),
-                            ));
-                        }
-                        WS2PSignal::Document(ws2p_full_id, network_doc) => {
-                            trace!("WS2PSignal::Document({})", ws2p_full_id);
-                            ws2p_module.send_network_event(&NetworkEvent::ReceiveDocuments(vec![
-                                network_doc,
-                            ]));
-                        }
-                        WS2PSignal::ReqResponse(req_id, req, recipient_full_id, response) => {
-                            match req {
-                                NetworkRequest::GetCurrent(ref _req_id, _receiver) => {
-                                    info!(
-                                        "WS2PSignal::ReceiveCurrent({}, {:?}, {:#?})",
-                                        req_id.0, req, response
-                                    );
-                                    if let Some(block) = parse_json_block(&response) {
-                                        ws2p_module.send_network_event(&NetworkEvent::ReqResponse(
-                                            Box::new(NetworkResponse::CurrentBlock(
-                                                ModuleReqFullId(WS2PModule::id(), req_id),
-                                                recipient_full_id,
-                                                Box::new(block),
-                                            )),
-                                        ));
-                                    }
-                                }
-                                NetworkRequest::GetBlocks(ref _req_id, _receiver, _count, from) => {
-                                    info!("WS2PSignal::ReceiveChunk({}, {:?})", req_id.0, req);
-                                    if response.is_array() {
-                                        let mut chunk = Vec::new();
-                                        for json_block in response.as_array().unwrap() {
-                                            if let Some(block) = parse_json_block(json_block) {
-                                                chunk.push(NetworkDocument::Block(block));
-                                            } else {
-                                                warn!("WS2PModule: Error : fail to parse one json block !");
-                                            }
-                                        }
-                                        debug!("Send chunk to followers : {}", from);
-                                        ws2p_module.send_network_event(
-                                            &NetworkEvent::ReceiveDocuments(chunk),
-                                        );
-                                    }
-                                }
-                                NetworkRequest::GetRequirementsPending(
-                                    _req_id,
-                                    _receiver,
-                                    min_cert,
-                                ) => {
-                                    info!(
-                                        "WS2PSignal::ReceiveRequirementsPending({}, {})",
-                                        req_id.0, min_cert
-                                    );
-                                    debug!("----------------------------------------");
-                                    debug!("-      BEGIN IDENTITIES PENDING        -");
-                                    debug!("----------------------------------------");
-                                    debug!("{:#?}", response);
-                                    debug!("----------------------------------------");
-                                    debug!("-       END IDENTITIES PENDING         -");
-                                    debug!("----------------------------------------");
-                                }
-                                _ => {}
-                            }
-                        }
-                        WS2PSignal::Empty => {}
-                        _ => {}
-                    },
-                },
-                Err(e) => match e {
-                    mpsc::RecvTimeoutError::Disconnected => {
-                        panic!("Disconnected ws2p module !");
-                    }
-                    mpsc::RecvTimeoutError::Timeout => {}
-                },
-            }
-            if SystemTime::now()
-                .duration_since(last_ws2p_connections_print)
-                .unwrap()
-                > Duration::new(5, 0)
-            {
-                last_ws2p_connections_print = SystemTime::now();
-                let mut connected_nodes = Vec::new();
-                for (k, (_ep, state)) in ws2p_module.ws2p_endpoints.clone() {
-                    if let WS2PConnectionState::Established = state {
-                        connected_nodes.push(k);
-                    }
-                }
-                // Print network consensus
-                match ws2p_module.get_network_consensus() {
-                    Ok(consensus_blockstamp) => {
-                        debug!(
-                            "WS2PModule : get_network_consensus() = {:?}",
-                            consensus_blockstamp
-                        );
-                        if current_blockstamp.id.0 < (consensus_blockstamp.id.0 + 2) {
-                            warn!("We probably are in a fork branch !");
-                        }
-                    }
-                    Err(e) => warn!("{:?}", e),
-                }
-                // Print current_blockstamp
-                info!(
-                    "WS2PModule : current_blockstamp() = {:?}",
-                    current_blockstamp
-                );
-                // New WS2P connection wave
-                if connected_nodes.len() < ws2p_module.conf.clone().outcoming_quota
-                    && (SystemTime::now()
-                        .duration_since(last_ws2p_connecting_wave)
-                        .unwrap()
-                        > Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)
-                        || (SystemTime::now()
-                            .duration_since(last_ws2p_connecting_wave)
-                            .unwrap()
-                            > Duration::new(*WS2P_OUTCOMING_INTERVAL_AT_STARTUP, 0)
-                            && SystemTime::now().duration_since(start_time).unwrap()
-                                < Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)))
-                {
-                    last_ws2p_connecting_wave = SystemTime::now();
-                    info!("Connected to know endpoints...");
-                    ws2p_module.connect_to_know_endpoints();
-                }
-                /*// Request blocks from network
-                if SystemTime::now()
-                    .duration_since(last_blocks_request)
-                    .unwrap() > Duration::new(*BLOCKS_REQUEST_INTERVAL, 0)
-                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
-                {
-                    let mut request_blocks_from = current_blockstamp.id.0;
-                    if request_blocks_from > 0 {
-                        request_blocks_from += 1;
-                    }
-                    info!("get chunks from all connections...");
-                    let module_id = WS2PModule::id();
-                    let _blocks_request_result =
-                        ws2p_module.send_request_to_all_connections(&NetworkRequest::GetBlocks(
-                            ModuleReqFullId(module_id, ModuleReqId(0 as u32)),
-                            NodeFullId::default(),
-                            50,
-                            request_blocks_from,
-                        ));
-                    last_blocks_request = SystemTime::now();
-                }*/
-                // Request pending_identities from network
-                if SystemTime::now()
-                    .duration_since(last_identities_request)
-                    .unwrap()
-                    > Duration::new(*PENDING_IDENTITIES_REQUEST_INTERVAL, 0)
-                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
-                {
-                    /*info!("get pending_identities from all connections...");
-                                    let _blocks_request_result = ws2p_module.send_request_to_all_connections(
-                                        &NetworkRequest::GetRequirementsPending(ModuleReqId(0 as u32), 5),
-                                    );*/
-                    last_identities_request = SystemTime::now();
-                }
-                // Write pending endpoints
-                for (ep_full_id, received_time) in endpoints_to_update_status.clone() {
-                    if SystemTime::now().duration_since(received_time).unwrap()
-                        > Duration::new(*DURATION_BEFORE_RECORDING_ENDPOINT, 0)
-                    {
-                        if let Some(&(ref ep, ref state)) =
-                            ws2p_module.ws2p_endpoints.get(&ep_full_id)
-                        {
-                            /*let dal_endpoint = duniter_dal::endpoint::DALEndpoint::new(
-                                                state.clone() as u32,
-                                                ep.node_uuid().unwrap().0,
-                                                ep.pubkey(),
-                                                duniter_dal::endpoint::string_to_api(&ep.api().0).unwrap(),
-                                                1,
-                                                ep.to_string(),
-                                                received_time.duration_since(UNIX_EPOCH).unwrap(),
-                                            );*/
-                            ws2p_db::write_endpoint(
-                                &db,
-                                &ep,
-                                state.to_u32(),
-                                SystemTime::now()
-                                    .duration_since(UNIX_EPOCH)
-                                    .unwrap()
-                                    .as_secs(),
-                            );
-                        }
-                        endpoints_to_update_status.remove(&ep_full_id);
-                    } else {
-                        info!(
-                            "Write {} endpoint in {} secs.",
-                            ep_full_id,
-                            *DURATION_BEFORE_RECORDING_ENDPOINT - SystemTime::now()
-                                .duration_since(received_time)
-                                .unwrap()
-                                .as_secs()
-                        );
-                    }
-                }
-                // ..
-            }
-        }
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    extern crate duniter_conf;
-    extern crate duniter_crypto;
-    extern crate duniter_dal;
-    extern crate duniter_documents;
-    extern crate duniter_message;
-    extern crate duniter_module;
-    extern crate duniter_network;
-
-    use super::parsers::blocks::parse_json_block;
-    use super::*;
-    use duniter_crypto::keys::PublicKey;
-    use duniter_documents::blockchain::v10::documents::BlockDocument;
-    use duniter_module::DuniterModule;
-    use duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
-    use duniter_network::NetworkBlock;
-    use std::fs;
-    use std::path::PathBuf;
-    use std::time::{SystemTime, UNIX_EPOCH};
-
-    #[test]
-    fn test_parse_json_block() {
-        let json_block = json!({
-            "fork": false,
-            "version": 10,
-            "nonce": 10500000059239 as u64,
-            "number": 109966,
-            "powMin": 88,
-            "time": 1523300656,
-            "medianTime": 1523295259,
-            "membersCount": 933,
-            "monetaryMass": 146881563,
-            "unitbase": 0,
-            "issuersCount": 44,
-            "issuersFrame": 221,
-            "issuersFrameVar": 0,
-            "currency": "g1",
-            "issuer": "GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4",
-            "signature": "GCg2Lti3TdxWlhA8JF8pRI+dRQ0XZVtcC4BqO/COTpjTQFdWG6qmUNVvdeYCtR/lu1JQe3N/IhrbyV6L/6I+Cg==",
-            "hash": "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74",
-            "parameters": "",
-            "previousHash": "000004C00602F8A27AE078DE6351C0DDA1EA0974A78D2BEFA7DFBE7B7C3146FD",
-            "previousIssuer": "5SwfQubSat5SunNafCsunEGTY93nVM4kLSsuprNqQb6S",
-            "inner_hash": "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C",
-            "dividend": null,
-            "identities": [],
-            "joiners": [],
-            "actives": [],
-            "leavers": [],
-            "revoked": [],
-            "excluded": [],
-            "certifications": [
-                "Hm5qjaNuHogNRdGZ4vgnLA9DMZVUu5YWzVup5mubuxCc:8AmdBsimcLziXaCS4AcVUfPx7rkjeic7482dLbBkuZw6:109964:yHKBGMeuxyIqFb295gVNK6neRC+U0tmsX1Zed3TLjS3ZZHYYycE1piLcYsTKll4ifNVp6rm+hd/CLdHYB+29CA==",
-                "BncjgJeFpGsMCCsUfzNLEexjsbuX3V2mg9P67ov2LkwK:DyBUBNpzpfvjtwYYSaVMM6ST6t2DNg3NCE9CU9bRQFhF:105864:cJEGW9WxJwlMA2+4LNAK4YieyseUy1WIkFh1YLYD+JJtJEoCSnIQRXzhiAoRpGaj0bRz8sTpwI6PRkuVoDJJDQ=="
-            ],
-            "transactions": [
-                {
-                "version": 10,
-                "currency": "g1",
-                "locktime": 0,
-                "hash": "80FE1E83DC4D0B722CA5F8363EFC6A3E29071032EBB71C1E0DF8D4FEA589C698",
-                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
-                "blockstampTime": 0,
-                "issuers": [
-                    "6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT"
-                ],
-                "inputs": [
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98284",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98519",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98779",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99054",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99326",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99599",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99884",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100174",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100469",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100746",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101036",
-                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101327"
-                ],
-                "outputs": [
-                    "12000:0:SIG(HmH5beJqKGMeotcQUrSW7Wo5tKvAksHmfYXfiSQ9EbWz)",
-                    "12:0:SIG(6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT)"
-                ],
-                "unlocks": [
-                    "0:SIG(0)",
-                    "1:SIG(0)",
-                    "2:SIG(0)",
-                    "3:SIG(0)",
-                    "4:SIG(0)",
-                    "5:SIG(0)",
-                    "6:SIG(0)",
-                    "7:SIG(0)",
-                    "8:SIG(0)",
-                    "9:SIG(0)",
-                    "10:SIG(0)",
-                    "11:SIG(0)"
-                ],
-                "signatures": [
-                    "MZxoKxYgwufh/s5mwLCsYEZXtIsP1hEKCyAzLipJsvCbR9xj7wXUw0C/ahwvZfBtR7+QVPIfLmwYEol1JcHjDw=="
-                ],
-                "comment": "Adhesion 2018"
-                },
-                {
-                "version": 10,
-                "currency": "g1",
-                "locktime": 0,
-                "hash": "B80507412B35BD5EB437AE0D3EB97E60E3A4974F5CDEA1AF7E2127C0E943481F",
-                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
-                "blockstampTime": 0,
-                "issuers": [
-                    "8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU"
-                ],
-                "inputs": [
-                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91560",
-                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91850",
-                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92111",
-                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92385",
-                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92635"
-                ],
-                "outputs": [
-                    "5000:0:SIG(BzHnbec1Gov7dLSt1EzJS7vikoQCECeuvZs4wamZAcT1)",
-                    "5:0:SIG(8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU)"
-                ],
-                "unlocks": [
-                    "0:SIG(0)",
-                    "1:SIG(0)",
-                    "2:SIG(0)",
-                    "3:SIG(0)",
-                    "4:SIG(0)"
-                ],
-                "signatures": [
-                    "A+ukwRvLWs1gZQ0KAqAnknEgmRQHdrnOvNuBx/WZqje17BAPrVxSxKpqwU6MiajU+ppigsYp6Bu0FdPf/tGnCQ=="
-                ],
-                "comment": ""
-                },
-                {
-                "version": 10,
-                "currency": "g1",
-                "locktime": 0,
-                "hash": "D8970E6629C0381A78534EEDD86803E9215A7EC4C494BAEA79EB19425F9B4D31",
-                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
-                "blockstampTime": 0,
-                "issuers": [
-                    "FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg"
-                ],
-                "inputs": [
-                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36597",
-                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36880",
-                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:37082"
-                ],
-                "outputs": [
-                    "3000:0:SIG(BBC8Rnh4CWN1wBrPLevK7GRFFVDVw7Lu24YNMUmhqoHU)"
-                ],
-                "unlocks": [
-                    "0:SIG(0)",
-                    "1:SIG(0)",
-                    "2:SIG(0)"
-                ],
-                "signatures": [
-                    "OpiF/oQfIigOeAtsteukU0w9FPSELE+BVTxhmsQ8bEeYGlwovG2VF8ZFiJkLLPi6vFuKgwzULJfjNGd97twZCw=="
-                ],
-                "comment": "1 billet pour une seance.pour un chouette film"
-                }
-            ],
-        });
-        let mut block: BlockDocument =
-            match parse_json_block(&json_block).expect("Fail to parse test json block !") {
-                NetworkBlock::V10(network_block_v10) => network_block_v10.uncompleted_block_doc,
-                _ => {
-                    panic!("Test block must be a v10 block !");
-                }
-            };
-        assert_eq!(
-            block
-                .inner_hash
-                .expect("Try to get inner_hash of an uncompleted or reduce block !")
-                .to_hex(),
-            "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C"
-        );
-        block.compute_hash();
-        assert_eq!(
-            block
-                .hash
-                .expect("Try to get hash of an uncompleted or reduce block !")
-                .0
-                .to_hex(),
-            "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74"
-        );
-    }
-
-    #[test]
-    fn endpoint_db_tests() {
-        let test_db_path = PathBuf::from("test.db");
-        if test_db_path.as_path().exists() {
-            fs::remove_file(&test_db_path).unwrap();
-        }
-        let db = WS2PModuleDatas::open_db(&test_db_path).unwrap();
-
-        let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-
-        let mut endpoint = NetworkEndpoint::parse_from_raw(
-            "WS2P cb06a19b g1.imirhil.fr 53012 /",
-            PubKey::Ed25519(
-                ed25519::PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv")
-                    .unwrap(),
-            ),
-            1,
-            current_time.as_secs(),
-        ).expect("Failt to parse test endpoint !");
-
-        ws2p_db::write_endpoint(&db, &endpoint, 1, current_time.as_secs());
-        let mut written_endpoints =
-            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
-        assert_eq!(endpoint, written_endpoints.pop().unwrap());
-
-        // Test status update
-        endpoint.set_status(3);
-        ws2p_db::write_endpoint(&db, &endpoint, 3, current_time.as_secs());
-        let mut written_endpoints =
-            ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P")));
-        assert_eq!(endpoint, written_endpoints.pop().unwrap());
-    }
-
-    #[test]
-    fn ws2p_requests() {
-        let module_id = WS2PModule::id();
-        let request = NetworkRequest::GetBlocks(
-            ModuleReqFullId(module_id, ModuleReqId(58)),
-            NodeFullId::default(),
-            50,
-            0,
-        );
-        assert_eq!(
-            network_request_to_json(&request),
-            json!({
-            "reqId": format!("{:x}", 58),
-            "body": {
-                "name": "BLOCKS_CHUNK",
-                "params": {
-                    "count": 50,
-                    "fromNumber": 0
-                }
-            }
-        })
-        );
-        assert_eq!(
-            network_request_to_json(&request).to_string(),
-            "{\"body\":{\"name\":\"BLOCKS_CHUNK\",\"params\":{\"count\":50,\"fromNumber\":0}},\"reqId\":\"3a\"}"
-        );
-    }
-
-    #[test]
-    fn ws2p_parse_head() {
-        let head = json!({
-            "message": "WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3",
-            "sig": "trtK9GXvTdfND995ohWEderpO3NkIqi1X6mBeVvMcaHckq+lIGqjWvJ9t9Vccz5t+VGaSmGUihDl4q6eldIYBw==",
-            "messageV2": "WS2POTMIC:HEAD:2:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3:25:22",
-            "sigV2": "x6ehPMuYjGY+z7wEGnJGyMBxMKUdu01RWaF0b0XCtoVjg67cCvT4H0V/Qcxn4bAGqzy5ux2fA7NiI+81bBnqDw==",
-            "step": 0
-        });
-        let mut heads_count = 0;
-        if let Some(head) = NetworkHead::from_json_value(&head) {
-            if let NetworkHead::V2(ref head_v2) = head {
-                heads_count += 1;
-                assert_eq!(
-                    head_v2.message.to_string(),
-                    String::from("WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3")
-                );
-            }
-            assert_eq!(head.verify(), true);
-        }
-        assert_eq!(heads_count, 1);
+        unimplemented!()
     }
 }