diff --git a/Cargo.lock b/Cargo.lock
index 17ed1bc99d92b1bbb3adc6a33d179de7b785bbda..f1501134d930f89b32b494bf3142f2556913bcd0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1066,6 +1066,7 @@ dependencies = [
  "dubp-currency-params 0.2.0",
  "dubp-user-docs 0.14.0",
  "dup-crypto 0.7.0",
+ "durs-bc-db-reader 0.3.0-dev",
  "durs-module 0.3.0-dev",
  "durs-network 0.3.0-dev",
  "durs-network-documents 0.4.0",
@@ -1231,6 +1232,7 @@ dependencies = [
  "durs-network-documents 0.4.0",
  "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkstl 0.1.0",
  "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1241,16 +1243,23 @@ dependencies = [
 name = "durs-ws2p-protocol"
 version = "0.3.0-dev"
 dependencies = [
+ "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dubp-block-doc 0.1.0",
  "dubp-common-doc 0.1.0",
  "dubp-currency-params 0.2.0",
  "dubp-user-docs 0.14.0",
  "dup-crypto 0.7.0",
+ "durs-bc-db-reader 0.3.0-dev",
  "durs-common-tools 0.2.0",
+ "durs-conf 0.3.0-dev",
+ "durs-message 0.3.0-dev",
  "durs-module 0.3.0-dev",
+ "durs-network 0.3.0-dev",
  "durs-network-documents 0.4.0",
  "durs-ws2p-messages 0.3.0-dev",
  "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkstl 0.1.0",
  "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2229,6 +2238,18 @@ dependencies = [
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "rand"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "rand"
 version = "0.6.5"
@@ -3525,6 +3546,7 @@ dependencies = [
 "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
 "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
 "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
 "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
 "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
 "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
diff --git a/lib/core/message/Cargo.toml b/lib/core/message/Cargo.toml
index 96c9a980002b11cf0ee066691eb6f7cc01d28e0e..8ff61fd2b25202f5f8d048b8c63ce4e8c5515779 100644
--- a/lib/core/message/Cargo.toml
+++ b/lib/core/message/Cargo.toml
@@ -18,6 +18,7 @@ dup-crypto = { path = "../../crypto" }
 durs-module = { path = "../module" }
 durs-network = { path = "../network" }
 durs-network-documents = { path = "../../dunp/network-documents" }
+durs-bc-db-reader = { path = "../../modules-lib/bc-db-reader" }
 serde = "1.0.*"
 serde_derive = "1.0.*"
 serde_json = "1.0.*"
diff --git a/lib/core/message/src/lib.rs b/lib/core/message/src/lib.rs
index 35340e3af1ba8aac517edd5b7b9f69260cd34d02..c4923ee4a9958b2adee511d45f356f280dd0dc72 100644
--- a/lib/core/message/src/lib.rs
+++ b/lib/core/message/src/lib.rs
@@ -28,6 +28,7 @@
     unused_qualifications
 )]
 
+use core::time::Duration;
 use durs_module::*;
 use durs_network_documents::network_endpoint::EndpointEnum;
 
@@ -44,7 +45,10 @@ use crate::events::*;
 use crate::requests::*;
 use crate::responses::*;
 
-/// Message exchanged between Dunitrust modules
+/// timeout for intermodule request
+pub static MODULE_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
+
+/// Message exchanged between Durs modules
 #[derive(Debug, Clone)]
 pub enum DursMsg {
     /// Dunitrust module event
diff --git a/lib/core/message/src/requests.rs b/lib/core/message/src/requests.rs
index c598dc39361e6fd301bf3f1a0c984b40ebd47391..f829c54c5f3f31167177cec1e322d0dd2531d756 100644
--- a/lib/core/message/src/requests.rs
+++ b/lib/core/message/src/requests.rs
@@ -17,15 +17,16 @@ use crate::*;
 use dubp_common_doc::BlockNumber;
 use dup_crypto::hashs::Hash;
 use dup_crypto::keys::*;
-use durs_network::requests::OldNetworkRequest;
+use durs_network::requests::{NetworkRequest, OldNetworkRequest};
+// use durs_bc_db_reader::indexes::identities::IdentitiesFilter;
 
 #[derive(Debug, Clone)]
 /// Modules request content
 pub enum DursReqContent {
     /// Request to the old network module
     OldNetworkRequest(OldNetworkRequest),
-    /// Network request (Not yet implemented)
-    NetworkRequest(),
+    /// Network request
+    NetworkRequest(NetworkRequest),
     /// Blockchain datas request
     BlockchainRequest(BlockchainRequest),
     /// Mem pool datas request
@@ -48,6 +49,11 @@ pub enum BlockchainRequest {
         /// Block number
         block_number: BlockNumber,
     },
+    /// asks milestones
+    Milestones {
+        /// chunk size determines how many blocks separate each milestone
+        chunk_size: usize,
+    },
     /// Chunk (block pack)
     Chunk {
         /// First block number
diff --git a/lib/core/message/src/responses.rs b/lib/core/message/src/responses.rs
index ee7dfbde9b964162df5c0937fdeabd3526b79125..d9bb27d7bdce94e5fa7f4383de60343444824bd8 100644
--- a/lib/core/message/src/responses.rs
+++ b/lib/core/message/src/responses.rs
@@ -14,6 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use dubp_block_doc::BlockDocument;
+use dubp_common_doc::BlockHash;
 use dubp_common_doc::BlockNumber;
 use dubp_common_doc::Blockstamp;
 use dubp_user_docs::documents::certification::CertificationDocument;
@@ -23,7 +24,7 @@ use dubp_user_docs::documents::revocation::RevocationDocumentV10;
 use dup_crypto::hashs::Hash;
 use dup_crypto::keys::*;
 use durs_module::ModuleReqId;
-use durs_network::requests::NetworkResponse;
+use durs_network::requests::OldNetworkResponse;
 use std::collections::HashMap;
 
 /// Dunitrust request response message
@@ -34,7 +35,7 @@ pub enum DursResContent {
     /// MemPoolResponse
     MemPoolResponse(MemPoolResponse),
     /// Response of OldNetworkRequest
-    NetworkResponse(NetworkResponse),
+    OldNetworkResponse(OldNetworkResponse),
     /// Pow module response
     ProverResponse(BlockNumber, Sig, u64),
 }
@@ -63,6 +64,8 @@ pub enum BlockchainResponse {
     CurrentBlock(Box<BlockDocument>, Blockstamp),
     /// Block by number
     BlockByNumber(Box<BlockDocument>),
+    /// vector of milestone block's hash
+    Milestones(Vec<BlockHash>),
     /// Chunk (block pack)
     Chunk(Vec<BlockDocument>),
     /// Usernames corresponding to the public keys in parameter
diff --git a/lib/core/network/cli/sync.rs b/lib/core/network/cli/sync.rs
index 46a10e2be24ed04d0e731c443608b9569492d30e..e822847046b08e64fdda70ca48ffbf923c329c6a 100644
--- a/lib/core/network/cli/sync.rs
+++ b/lib/core/network/cli/sync.rs
@@ -37,7 +37,7 @@ pub struct SyncOpt {
     pub local_path: Option<PathBuf>,
     /// The source of datas (url of the node from which to synchronize)
     pub source: Option<Url>,
-    /// Start node after sync (not yet implemented)
+    /// Start node after sync
     #[structopt(short = "s", long = "start", hidden = true)]
     pub start: bool,
     /// Sync module name
diff --git a/lib/core/network/requests.rs b/lib/core/network/requests.rs
index ac7b1c878dfb941a75496cfe54e2eaeb1d850be3..52b7068d0c8a115aed97f2ec40c5ef1024e3d6ea 100644
--- a/lib/core/network/requests.rs
+++ b/lib/core/network/requests.rs
@@ -16,8 +16,9 @@
 //! Defined network requests.
 
 use crate::*;
-use dubp_block_doc::BlockDocument;
+use dubp_block_doc::block::BlockDocument;
 use dubp_common_doc::blockstamp::Blockstamp;
+use dubp_common_doc::BlockNumber;
 use dubp_user_docs::documents::UserDocumentDUBP;
 
 #[derive(Debug, Copy, Clone)]
@@ -69,9 +70,47 @@ pub enum OldNetworkRequestError {
     ReceiverUnreachable(),
 }
 
+// implement new network request
+#[derive(Debug, Copy, Clone)]
+/// Enum of different possible requests to the network module
+pub enum NetworkRequest {
+    /// Get the block number x
+    GetBlock(BlockNumber),
+    /// Get a blocks chunk from specified block number
+    GetBlocks(BlockNumber, u32),
+    /// Get pending wot documents from specified node
+    GetRequirementsPending {
+        /// minimum cert number
+        min_cert: u32,
+    },
+    /// Obtain the current network consensus
+    GetConsensus,
+    /// Getting the heads cache
+    GetHeadsCache,
+    /// Get a list of known endpoints
+    GetEndpoints,
+}
+
+// impl NetworkRequest {
+//     // nothing
+// }
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Type returned when the network module does not get a satisfying answer to a request
+pub enum NetworkRequestError {
+    /// Receiving an invalid format response
+    WrongFormat,
+    /// Unknow error
+    UnknowError,
+    /// No response received
+    NoResponse,
+    /// Unable to reach the target node
+    ReceiverUnreachable,
+}
+
 #[derive(Debug, Clone)]
 /// Type containing the response to a network request
-pub enum NetworkResponse {
+pub enum OldNetworkResponse {
     /// CurrentBlock
     CurrentBlock(ModuleReqFullId, NodeFullId, Box<BlockDocument>),
     /// Block
@@ -86,16 +125,16 @@ pub enum NetworkResponse {
     HeadsCache(ModuleReqFullId, Box<NetworkHead>),
 }
 
-impl NetworkResponse {
+impl OldNetworkResponse {
     /// Get request full identifier
     pub fn get_req_full_id(&self) -> ModuleReqFullId {
         match *self {
-            NetworkResponse::CurrentBlock(ref req_id, _, _)
-            | NetworkResponse::Block(ref req_id, _, _)
-            | NetworkResponse::Chunk(ref req_id, _, _)
-            | NetworkResponse::PendingDocuments(ref req_id, _)
-            | NetworkResponse::Consensus(ref req_id, _)
-            | NetworkResponse::HeadsCache(ref req_id, _) => *req_id,
+            OldNetworkResponse::CurrentBlock(ref req_id, _, _)
+            | OldNetworkResponse::Block(ref req_id, _, _)
+            | OldNetworkResponse::Chunk(ref req_id, _, _)
+            | OldNetworkResponse::PendingDocuments(ref req_id, _)
+            | OldNetworkResponse::Consensus(ref req_id, _)
+            | OldNetworkResponse::HeadsCache(ref req_id, _) => *req_id,
         }
     }
     /// Get request identifier
diff --git a/lib/crypto/src/keys/bin_signable.rs b/lib/crypto/src/keys/bin_signable.rs
deleted file mode 100644
index 7ae96fb108f110564b45cc299a3610e5cfd2135d..0000000000000000000000000000000000000000
--- a/lib/crypto/src/keys/bin_signable.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
-//
-// 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 serde::{Deserialize, Serialize};
-
-/// Signatureable in binary format
-pub trait BinSignable<'de>: Serialize + Deserialize<'de> {
-    /// Return entity issuer pubkey
-    fn issuer_pubkey(&self) -> PubKey;
-    /// Return signature
-    fn signature(&self) -> Option<Sig>;
-    /// Change signature
-    fn set_signature(&mut self, _signature: Sig);
-    /// Get binary datas without signature
-    fn get_bin_without_sig(&self) -> Result<Vec<u8>, failure::Error>;
-    /// Add signature to bin datas
-    fn add_sig_to_bin_datas(&self, bin_datas: &mut Vec<u8>);
-    /// Sign entity with a signator
-    fn sign(&mut self, signator: &SignatorEnum) -> Result<Vec<u8>, SignError> {
-        if self.signature().is_some() {
-            return Err(SignError::AlreadySign);
-        }
-        match self.issuer_pubkey() {
-            PubKey::Ed25519(_) => {
-                let mut bin_msg = self
-                    .get_bin_without_sig()
-                    .map_err(|e| SignError::SerdeError(e.to_string()))?;
-                let sig = signator.sign(&bin_msg);
-                self.set_signature(sig);
-                self.add_sig_to_bin_datas(&mut bin_msg);
-                Ok(bin_msg)
-            }
-            _ => Err(SignError::WrongAlgo),
-        }
-    }
-    /// Check signature of entity
-    fn verify(&self) -> Result<(), SigError> {
-        if let Some(signature) = self.signature() {
-            match self.issuer_pubkey() {
-                PubKey::Ed25519(pubkey) => match signature {
-                    Sig::Ed25519(sig) => {
-                        let signed_part: Vec<u8> = self
-                            .get_bin_without_sig()
-                            .map_err(|e| SigError::SerdeError(format!("{}", e)))?;
-                        pubkey.verify(&signed_part, &sig)
-                        /*
-                        if pubkey.verify(&signed_part, &sig) {
-                            Ok(())
-                        } else {
-                            Err(SigError::InvalidSig())
-                        }
-                        */
-                    }
-                    _ => Err(SigError::NotSameAlgo),
-                },
-                _ => Err(SigError::NotSameAlgo),
-            }
-        } else {
-            Err(SigError::NotSig)
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-    use bincode;
-
-    #[derive(Deserialize, Serialize)]
-    struct BinSignableTestImpl {
-        datas: Vec<u8>,
-        issuer: PubKey,
-        sig: Option<Sig>,
-    }
-
-    impl BinSignable<'_> for BinSignableTestImpl {
-        #[inline]
-        fn add_sig_to_bin_datas(&self, bin_datas: &mut Vec<u8>) {
-            bin_datas
-                .extend_from_slice(&bincode::serialize(&self.sig).expect("Fail to binarize sig !"));
-        }
-        #[inline]
-        fn get_bin_without_sig(&self) -> Result<Vec<u8>, failure::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));
-            Ok(bin_msg)
-        }
-        fn issuer_pubkey(&self) -> PubKey {
-            self.issuer
-        }
-        fn signature(&self) -> Option<Sig> {
-            self.sig
-        }
-        fn set_signature(&mut self, new_signature: Sig) {
-            self.sig = Some(new_signature);
-        }
-    }
-
-    #[test]
-    fn test_bin_signable() {
-        let key_pair = ed25519::KeyPairFromSeed32Generator::generate(Seed32::new([
-            0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-            10, 11, 12, 13, 14, 15,
-        ]));
-
-        let signator = SignatorEnum::Ed25519(
-            key_pair
-                .generate_signator()
-                .expect("fail to generate signator !"),
-        );
-
-        let mut bin_signable_datas = BinSignableTestImpl {
-            datas: vec![0, 1, 2, 3],
-            issuer: PubKey::Ed25519(key_pair.pubkey),
-            sig: None,
-        };
-
-        assert_eq!(Err(SigError::NotSig), bin_signable_datas.verify());
-
-        let _bin_msg = bin_signable_datas
-            .sign(&signator)
-            .expect("Fail to sign datas !");
-
-        assert_eq!(
-            Err(SignError::AlreadySign),
-            bin_signable_datas.sign(&signator)
-        );
-
-        assert_eq!(Ok(()), bin_signable_datas.verify())
-    }
-}
diff --git a/lib/crypto/src/keys/mod.rs b/lib/crypto/src/keys/mod.rs
index 0b90c88f1586750735eaefdd30cbf00516ee7cfa..e42b501369dfa81bd99bc2780c31b20a5d712ea5 100644
--- a/lib/crypto/src/keys/mod.rs
+++ b/lib/crypto/src/keys/mod.rs
@@ -48,7 +48,6 @@
 //! `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`
 //! with `=` as padding character.
 
-pub mod bin_signable;
 pub mod ed25519;
 pub mod text_signable;
 
@@ -146,6 +145,7 @@ pub trait Signature: Clone + Display + Debug + PartialEq + Eq + Hash {
 
 /// Store a cryptographic signature.
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+#[serde(tag = "algo", content = "content")]
 pub enum Sig {
     /// Store a ed25519 Signature
     Ed25519(ed25519::Signature),
@@ -228,6 +228,7 @@ pub trait PublicKey: Clone + Display + Debug + PartialEq + Eq + Hash + ToBase58
 
 /// Store a cryptographic public key.
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+#[serde(tag = "algo", content = "content")]
 pub enum PubKey {
     /// Store a ed25519 public key.
     Ed25519(ed25519::PublicKey),
diff --git a/lib/dubp/block-doc/src/lib.rs b/lib/dubp/block-doc/src/lib.rs
index d668761d2235c2e73dc862b901e5fce383573e40..6e5ac8b63aa886692efffcc18b0511a76c66114d 100644
--- a/lib/dubp/block-doc/src/lib.rs
+++ b/lib/dubp/block-doc/src/lib.rs
@@ -43,12 +43,12 @@ pub use block::{
     BlockDocument, BlockDocumentStringified, BlockDocumentV10, BlockDocumentV10Stringified,
 };
 
-/// Document of DUBP (DUniter Blockhain Protocol)
+/// Document of DUBP (DUniter Blockchain Protocol)
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum DocumentDUBP {
     /// Block document.
     Block(Box<BlockDocument>),
-    /// User document of DUBP (DUniter Blockhain Protocol)
+    /// User document of DUBP (DUniter Blockchain Protocol)
     UserDocument(UserDocumentDUBP),
 }
 
diff --git a/lib/dubp/wot/Cargo.toml b/lib/dubp/wot/Cargo.toml
index a0a9ba81e6911fcecef424cb61f820b1f523b7cd..7a7ce592a45f6231b17e979835499a2d1df832c6 100644
--- a/lib/dubp/wot/Cargo.toml
+++ b/lib/dubp/wot/Cargo.toml
@@ -21,5 +21,6 @@ serde = { version = "1.0.*", features = ["derive"] }
 
 [dev-dependencies]
 bincode = "1.0.*"
+durs-common-tools = { path = "../../tools/common-tools", version = "0.2.0" }
 
 [features]
\ No newline at end of file
diff --git a/lib/dunp/network-documents/src/network_peer.rs b/lib/dunp/network-documents/src/network_peer.rs
index c125d4266a141db022f72321d7d9b49c0247a40d..b4e3fc8ab111b58bb125ab564e3f32e0a59b9c22 100644
--- a/lib/dunp/network-documents/src/network_peer.rs
+++ b/lib/dunp/network-documents/src/network_peer.rs
@@ -27,6 +27,17 @@ use dup_crypto::keys::*;
 use pest::iterators::Pair;
 use pest::Parser;
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Certifiers of peer
+pub struct PeerCert {
+    /// identity of certifier
+    certifier: PubKey,
+    /// blockstamp of the certification
+    blockstamp: Blockstamp,
+    /// signature of this blockstamp
+    sig: Sig,
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 /// Peer card V10
 pub struct PeerCardV10 {
@@ -55,6 +66,8 @@ pub struct PeerCardV11 {
     pub endpoints_str: Vec<String>,
     /// Signature
     pub sig: Option<Sig>,
+    /// Certifiers
+    pub certifiers: Vec<PeerCert>,
 }
 
 impl PeerCardV11 {
@@ -92,6 +105,7 @@ impl PeerCardV11 {
 
         Ok(PeerCardV11 {
             currency_name: CurrencyName(currency_str.to_owned()),
+            certifiers: vec![],
             issuer: issuer.expect("Grammar must ensure that peer v11 have valid issuer pubkey !"),
             node_id,
             created_on: created_on
@@ -248,6 +262,7 @@ pub struct JsonPeerCardV11<'a> {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[serde(tag = "version", rename_all = "lowercase")]
 /// Peer card
 pub enum PeerCard {
     /// Peer card V10
@@ -321,6 +336,7 @@ mod tests {
             SignatorEnum::Ed25519(keypair1.generate_signator().expect("Fail to gen signator"));
         let mut peer_card_v11 = PeerCardV11 {
             currency_name: CurrencyName(String::from("g1")),
+            certifiers: vec![],
             issuer: PubKey::Ed25519(keypair1.public_key()),
             node_id: NodeId(0),
             created_on: BlockNumber(50),
diff --git a/lib/modules-lib/bc-db-reader/src/blocks.rs b/lib/modules-lib/bc-db-reader/src/blocks.rs
index d23a149f941288fbbf4ac37b562c1a8a6bd851d4..37d849f2b7836e80226a714b6ba0eaa6da54eca9 100644
--- a/lib/modules-lib/bc-db-reader/src/blocks.rs
+++ b/lib/modules-lib/bc-db-reader/src/blocks.rs
@@ -35,7 +35,7 @@ pub struct DbBlock {
     /// Block document
     pub block: BlockDocument,
     /// List of certifications that expire in this block.
-    /// Warning : BlockNumber contain the emission block, not the written block !
+    /// Warning : BlockNumber contains the emission block, not the written block !
     /// HashMap<(Source, Target), BlockNumber>
     pub expire_certs: Option<HashMap<(WotId, WotId), BlockNumber>>,
 }
@@ -109,6 +109,26 @@ pub fn get_block<DB: DbReadable>(
     })
 }
 
+/// Get blocks between `start` and `end` blocknumbers included
+pub fn get_blocks_between<DB: DbReadable>(
+    db: &DB,
+    start: BlockNumber,
+    end: BlockNumber,
+) -> Result<Vec<DbBlock>, DbError> {
+    db.read(|r| {
+        let mut chunk = Vec::new();
+        for i in start.0..end.0 {
+            let opt_dal_block = get_dal_block_in_local_blockchain(db, r, BlockNumber(i))?;
+            if let Some(block) = opt_dal_block {
+                chunk.push(block);
+            } else {
+                break;
+            }
+        }
+        Ok(chunk)
+    })
+}
+
 /// Get fork block
 pub fn get_fork_block<DB: DbReadable, R: DbReader>(
     db: &DB,
@@ -161,7 +181,7 @@ pub fn get_db_block_in_local_blockchain<DB: DbReadable, R: DbReader>(
     }
 }
 
-/// Get several blocks in local blockchain
+/// Get several consecutive blocks in local blockchain
 pub fn get_blocks_in_local_blockchain<DB: DbReadable>(
     db: &DB,
     first_block_number: BlockNumber,
diff --git a/lib/modules-lib/bc-db-reader/src/current_meta_datas.rs b/lib/modules-lib/bc-db-reader/src/current_meta_datas.rs
index ae41fd98db03099d643060309fca543d371bbabe..9c2a4131a9b1bfaf30e3bf932b031050f984c0c5 100644
--- a/lib/modules-lib/bc-db-reader/src/current_meta_datas.rs
+++ b/lib/modules-lib/bc-db-reader/src/current_meta_datas.rs
@@ -16,10 +16,11 @@
 //! Current meta datas
 
 use crate::blocks::fork_tree::ForkTree;
+use crate::blocks::get_block_hash;
 use crate::constants::*;
 use crate::*;
 use crate::{DbReadable, DbValue};
-use dubp_common_doc::{Blockstamp, CurrencyName};
+use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp, CurrencyName};
 use durs_dbs_tools::DbError;
 use durs_wot::WotId;
 
@@ -32,7 +33,7 @@ pub enum CurrentMetaDataKey {
     CurrencyName,
     /// Current blockstamp
     CurrentBlockstamp,
-    /// Current "blokchain" time
+    /// Current "blockchain" time
     CurrentBlockchainTime,
     /// Fork tree
     ForkTree,
@@ -79,8 +80,8 @@ pub fn get_currency_name<DB: DbReadable>(db: &DB) -> Result<Option<CurrencyName>
             .get_int_store(CURRENT_METAS_DATAS)
             .get(r, CurrentMetaDataKey::CurrencyName.to_u32())?
         {
-            if let DbValue::Str(curency_name) = v {
-                Ok(Some(CurrencyName(curency_name.to_owned())))
+            if let DbValue::Str(currency_name) = v {
+                Ok(Some(CurrencyName(currency_name.to_owned())))
             } else {
                 Err(DbError::DBCorrupted)
             }
@@ -118,6 +119,22 @@ pub fn get_current_blockstamp_<DB: DbReadable, R: DbReader>(
     }
 }
 
+/// Get milestones
+pub fn get_milestones<DB: DbReadable>(db: &DB, chunk_size: u32) -> Result<Vec<BlockHash>, DbError> {
+    db.read(|r| {
+        let mut blocks = vec![]; // OPTI compute capacity and use Vec::with_capacity
+        let mut block_number_i = BlockNumber(chunk_size); // first block number
+
+        debug!("try to get milestone {:?}", block_number_i);
+        while let Some(h) = get_block_hash(db, r, block_number_i)? {
+            blocks.push(h);
+            debug!("getting milestone {:?}", block_number_i);
+            block_number_i = BlockNumber(block_number_i.0 + chunk_size); // next block number
+        }
+        Ok(blocks)
+    })
+}
+
 /// Get current common time (also named "blockchain time")
 #[inline]
 pub fn get_current_common_time<DB: DbReadable>(db: &DB) -> Result<u64, DbError> {
diff --git a/lib/modules-lib/bc-db-reader/src/lib.rs b/lib/modules-lib/bc-db-reader/src/lib.rs
index cd9a1ebb865bac6452ed4fddf0e1fe4389535cae..0b13b5fad2f29e8928f83a4f15aca6ec0288a91c 100644
--- a/lib/modules-lib/bc-db-reader/src/lib.rs
+++ b/lib/modules-lib/bc-db-reader/src/lib.rs
@@ -27,6 +27,9 @@
     unused_qualifications
 )]
 
+#[macro_use]
+extern crate log;
+
 pub mod blocks;
 pub mod constants;
 pub mod currency_params;
diff --git a/lib/modules/blockchain/blockchain/src/lib.rs b/lib/modules/blockchain/blockchain/src/lib.rs
index 4674ca8fb2735b78c8fc6b7981c1e8192e394596..d66ae67eeb0988dd2631c034905807c74e70e6d9 100644
--- a/lib/modules/blockchain/blockchain/src/lib.rs
+++ b/lib/modules/blockchain/blockchain/src/lib.rs
@@ -69,7 +69,7 @@ use durs_module::*;
 use durs_network::{
     cli::sync::SyncOpt,
     events::NetworkEvent,
-    requests::{NetworkResponse, OldNetworkRequest},
+    requests::{OldNetworkRequest, OldNetworkResponse},
 };
 // use durs_wot::data::rusty::RustyWebOfTrust;
 use durs_wot::operations::distance::RustyDistanceCalculator;
@@ -254,15 +254,17 @@ impl BlockchainModule {
         blockchain_receiver: &mpsc::Receiver<DursMsg>,
         sync_opts: Option<SyncOpt>,
     ) {
-        info!("BlockchainModule::start_blockchain()");
+        info!("BlockchainModule : start_blockchain()");
 
         // Send currency parameters to other modules
         if let Some(currency_params) = self.currency_params {
             events::sent::send_event(self, &BlockchainEvent::CurrencyParameters(currency_params));
         }
 
-        if let Some(_sync_opts) = sync_opts {
-            // TODO ...
+        if let Some(sync_opts) = sync_opts {
+            // TODO HUGO...
+            // take into account sync options
+            self.main_loop_sync(blockchain_receiver, sync_opts);
         } else {
             // Start main loop
             self.main_loop(blockchain_receiver);
@@ -349,6 +351,49 @@ impl BlockchainModule {
             }
         }
     }
+
+    /// Start blockchain main loop in sync mode TODO refactor duplicated code
+    pub fn main_loop_sync(
+        &mut self,
+        blockchain_receiver: &mpsc::Receiver<DursMsg>,
+        _sync_opt: SyncOpt,
+    ) {
+        loop {
+            // Listen received messages
+            match blockchain_receiver.recv_timeout(Duration::from_millis(1000)) {
+                Ok(durs_message) => {
+                    match durs_message {
+                        DursMsg::Request {
+                            req_from,
+                            req_id,
+                            req_content,
+                            ..
+                        } => {
+                            requests::received::receive_req(self, req_from, req_id, req_content);
+                        }
+                        DursMsg::Event {
+                            event_type,
+                            event_content,
+                            ..
+                        } => events::received::receive_event(self, event_type, event_content),
+                        DursMsg::Response {
+                            req_id,
+                            res_content,
+                            ..
+                        } => responses::received::receive_response(self, req_id, res_content),
+                        DursMsg::Stop => break,
+                        _ => {} // Others DursMsg variants
+                    }
+                }
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        fatal_error!("Disconnected blockchain module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {}
+                },
+            }
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/lib/modules/blockchain/blockchain/src/requests/received.rs b/lib/modules/blockchain/blockchain/src/requests/received.rs
index 48c4c7ef076d1a1a976a71f89696c1f2c5d2f7f0..ba167ea1d9c5e1767579dbacdc5d93b6a87c3847 100644
--- a/lib/modules/blockchain/blockchain/src/requests/received.rs
+++ b/lib/modules/blockchain/blockchain/src/requests/received.rs
@@ -103,6 +103,10 @@ pub fn receive_req(
                     )
                 }
             }
+            BlockchainRequest::Milestones {
+                // TODO remove all these outdated requests
+                ..
+            } => unimplemented!(),
             BlockchainRequest::Chunk {
                 first_block_number,
                 count,
diff --git a/lib/modules/blockchain/blockchain/src/responses/received.rs b/lib/modules/blockchain/blockchain/src/responses/received.rs
index c847eb8b9b27b779afd8b820896bbb2a6980825f..b250cbd95707cc00e2cb31d57ac7e74ebd89b2a8 100644
--- a/lib/modules/blockchain/blockchain/src/responses/received.rs
+++ b/lib/modules/blockchain/blockchain/src/responses/received.rs
@@ -22,19 +22,19 @@ pub fn receive_response(
     req_id: ModuleReqId,
     res_content: DursResContent,
 ) {
-    if let DursResContent::NetworkResponse(network_response) = res_content {
-        debug!("BlockchainModule : receive NetworkResponse() !");
+    if let DursResContent::OldNetworkResponse(network_response) = res_content {
+        debug!("BlockchainModule : receive OldNetworkResponse() !");
         if let Some(request) = bc.pending_network_requests.remove(&req_id) {
             match request {
                 OldNetworkRequest::GetConsensus(_) => {
-                    if let NetworkResponse::Consensus(_, response) = network_response {
+                    if let OldNetworkResponse::Consensus(_, response) = network_response {
                         if let Ok(blockstamp) = response {
                             bc.consensus = blockstamp;
                         }
                     }
                 }
                 OldNetworkRequest::GetBlocks(_, _, _) => {
-                    if let NetworkResponse::Chunk(_, _, blocks) = network_response {
+                    if let OldNetworkResponse::Chunk(_, _, blocks) = network_response {
                         dunp::receiver::receive_blocks(bc, blocks);
                     }
                 }
diff --git a/lib/modules/skeleton/lib.rs b/lib/modules/skeleton/lib.rs
index f6fcbf65e5f55a40496cdf7d4f519a78bab22dcf..096340c7cb03f27508aee6e06f797f3ecf488950 100644
--- a/lib/modules/skeleton/lib.rs
+++ b/lib/modules/skeleton/lib.rs
@@ -162,7 +162,7 @@ impl DursModule<DuRsConf, DursMsg> for SkeletonModule {
             test_fake_conf_field: Some(subcommand_args.new_conf_field.to_owned()),
         };
         println!(
-            "Succesfully exec skeleton subcommand whit terminal name : {} and conf={:?}!",
+            "Successfully exec skeleton subcommand whit terminal name : {} and conf={:?}!",
             subcommand_args.new_conf_field, module_conf
         );
         Some(new_skeleton_conf)
@@ -175,7 +175,7 @@ impl DursModule<DuRsConf, DursMsg> for SkeletonModule {
     ) -> Result<(), failure::Error> {
         let _start_time = SystemTime::now();
 
-        // Instanciate Skeleton module datas
+        // Instantiate Skeleton module datas
         let datas = SkeletonModuleDatas {
             child_threads: Vec::new(),
             field: 3,
@@ -191,7 +191,7 @@ impl DursModule<DuRsConf, DursMsg> for SkeletonModule {
         let (proxy_sender, proxy_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) =
             mpsc::channel();
 
-        // Launch a proxy thread that transform DursMsgContent() to SkeleonMsg::DursMsgContent(DursMsgContent())
+        // Launch a proxy thread that transform DursMsgContent() to SkeletonMsg::DursMsgContent(DursMsgContent())
         let router_sender_clone = router_sender.clone();
         let skeleton_sender_clone = skeleton_sender.clone();
         thread::spawn(move || {
diff --git a/lib/modules/ws2p-v1-legacy/src/lib.rs b/lib/modules/ws2p-v1-legacy/src/lib.rs
index 7afa513d8bc53d133f491e08f0e80617fa6480aa..0b102f96f7ddae63f2acaa3d6506c4e9626e8b55 100644
--- a/lib/modules/ws2p-v1-legacy/src/lib.rs
+++ b/lib/modules/ws2p-v1-legacy/src/lib.rs
@@ -229,7 +229,7 @@ pub enum WS2PSignal {
     },
     Timeout(NodeFullId),
     UserDocuments(NodeFullId, Vec<UserDocumentDUBP>),
-    /// Receive a websocket error from a connextion. `NodeFullId` store the identifier of connection.
+    /// Receive a websocket error from a connexion. `NodeFullId` store the identifier of connection.
     WSError(NodeFullId),
 }
 
@@ -420,7 +420,7 @@ impl DursModule<DuRsConf, DursMsg> for WS2Pv1Module {
         ModuleStaticName(MODULE_NAME)
     }
     fn priority() -> ModulePriority {
-        ModulePriority::Essential()
+        ModulePriority::Recommended()
     }
     fn ask_required_keys() -> RequiredKeys {
         RequiredKeys::NetworkKeyPair()
diff --git a/lib/modules/ws2p-v1-legacy/src/requests/received.rs b/lib/modules/ws2p-v1-legacy/src/requests/received.rs
index bfc4e98a723bcadb7b91b9abf6a2428f325d29b8..3011eb163fb1896dae3d17fbb4272eb531d739e9 100644
--- a/lib/modules/ws2p-v1-legacy/src/requests/received.rs
+++ b/lib/modules/ws2p-v1-legacy/src/requests/received.rs
@@ -50,7 +50,7 @@ pub fn receive_req(ws2p_module: &mut WS2Pv1Module, req_content: &DursReqContent)
                     ws2p_module.next_receiver += 1;
                 }
                 if let Some(real_receiver) = real_receiver {
-                    debug!("WS2P: send req to: ({:?})", real_receiver);
+                    debug!("WS2Pv1Module : send req to: ({:?})", real_receiver);
                     let _blocks_request_result =
                         crate::ws_connections::requests::sent::send_request_to_specific_node(
                             ws2p_module,
@@ -65,7 +65,7 @@ pub fn receive_req(ws2p_module: &mut WS2Pv1Module, req_content: &DursReqContent)
                             },
                         );
                 } else {
-                    warn!("WS2P: not found peer to send request !");
+                    warn!("WS2Pv1Module : found no peer to send request !");
                 }
             }
             OldNetworkRequest::GetEndpoints(ref _request) => {}
diff --git a/lib/modules/ws2p-v1-legacy/src/responses/sent.rs b/lib/modules/ws2p-v1-legacy/src/responses/sent.rs
index fc2f9144dc4343858f984ebda3cf619a48c68ee7..aec2a3a5d5c52824bd1dbeef5b9d52d6a47531b2 100644
--- a/lib/modules/ws2p-v1-legacy/src/responses/sent.rs
+++ b/lib/modules/ws2p-v1-legacy/src/responses/sent.rs
@@ -22,7 +22,7 @@ pub fn send_network_req_response(
     ws2p_module: &WS2Pv1Module,
     requester: ModuleStaticName,
     req_id: ModuleReqId,
-    response: NetworkResponse,
+    response: OldNetworkResponse,
 ) {
     ws2p_module
         .router_sender
@@ -30,7 +30,7 @@ pub fn send_network_req_response(
             res_from: WS2Pv1Module::name(),
             res_to: requester,
             req_id,
-            res_content: DursResContent::NetworkResponse(response),
+            res_content: DursResContent::OldNetworkResponse(response),
         }))
         .expect("Fail to send message to router !");
 }
diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/responses/received.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/responses/received.rs
index bb18e866b6c1e6f8d35e5feb92aef25370170d47..7d142cae15edbb82dafead4230ef63b3c67be776 100644
--- a/lib/modules/ws2p-v1-legacy/src/ws_connections/responses/received.rs
+++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/responses/received.rs
@@ -41,7 +41,7 @@ pub fn receive_response(
                         ws2p_module,
                         module_req_full_id.0,
                         module_req_full_id.1,
-                        NetworkResponse::CurrentBlock(
+                        OldNetworkResponse::CurrentBlock(
                             ModuleReqFullId(WS2Pv1Module::name(), module_req_full_id.1),
                             recipient_full_id,
                             Box::new(block),
diff --git a/lib/modules/ws2p/ws2p-messages/Cargo.toml b/lib/modules/ws2p/ws2p-messages/Cargo.toml
index c73692cfd1ad859b6513c2cf685aa4a3512e32fa..9b0c2508351d31f3ef76b404136a057efea2d564 100644
--- a/lib/modules/ws2p/ws2p-messages/Cargo.toml
+++ b/lib/modules/ws2p/ws2p-messages/Cargo.toml
@@ -10,8 +10,6 @@ edition = "2018"
 path = "lib.rs"
 
 [dependencies]
-bincode = "1.0.*"
-byteorder = "1.2.3"
 dubp-common-doc = { path = "../../../dubp/common-doc"} #, version = "0.1.0" }
 dubp-block-doc = { path = "../../../dubp/block-doc"} #, version = "0.1.0" }
 dubp-currency-params = { path = "../../../dubp/currency-params" }
@@ -21,11 +19,12 @@ durs-common-tools = { path = "../../../tools/common-tools" }
 dup-crypto = { path = "../../../crypto" }
 failure = "0.1.5"
 log = "0.4.*"
-serde = "1.0.*"
-serde_derive = "1.0.*"
-serde_json = "1.0.*"
+pkstl = { path = "../../../tools/pkstl", features = ["bin","cbor"] }
+serde = { version = "1.0.*", features = ["derive"] }
 
 [dev-dependencies]
+bincode = "1.0.1"
 pretty_assertions = "0.5.1"
+serde_json = "1.0.*"
 
-[features]
\ No newline at end of file
+[features]
diff --git a/lib/modules/ws2p/ws2p-messages/lib.rs b/lib/modules/ws2p/ws2p-messages/lib.rs
index adbb46a2a785486b6e21e75f79ba5e2f9ff20f62..77d9e5aac0d892807d795c291af16100ed7020a8 100644
--- a/lib/modules/ws2p/ws2p-messages/lib.rs
+++ b/lib/modules/ws2p/ws2p-messages/lib.rs
@@ -31,19 +31,13 @@
 #[macro_use]
 extern crate pretty_assertions;*/
 
-#[macro_use]
-extern crate serde_derive;
-#[macro_use]
-extern crate log;
-
 /// WS2Pv2 Messages
 pub mod v2;
 
 use crate::v2::WS2Pv2Message;
-use dup_crypto::hashs::Hash;
-use dup_crypto::keys::bin_signable::BinSignable;
 use dup_crypto::keys::*;
-use durs_common_tools::fatal_error;
+use pkstl::{IncomingMessage, SecureLayer};
+use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
 /// WS2Pv2Message
@@ -59,100 +53,68 @@ pub enum WS2PMessage {
 /// Enumerate errors can happen when parsing and checking messages
 #[derive(Debug)]
 pub enum WS2PMessageError {
-    /// Error at deserialization
-    DeserError(bincode::Error),
     /// Invalid hash
     InvalidHash,
+    /// Secure transport layer error
+    SecureLayerError(pkstl::Error),
     /// Invalid signature
     SigError(SigError),
+    /// Unexpected empty message
+    UnexpectedEmpty,
 }
 
-impl From<bincode::Error> for WS2PMessageError {
-    fn from(e: bincode::Error) -> Self {
-        WS2PMessageError::DeserError(e)
+impl From<pkstl::Error> for WS2PMessageError {
+    fn from(e: pkstl::Error) -> Self {
+        WS2PMessageError::SecureLayerError(e)
     }
 }
 
 impl WS2PMessage {
-    /// Get message hash
-    pub fn hash(&self) -> Option<Hash> {
-        match *self {
-            WS2PMessage::V2(ref msg_v2) => msg_v2.message_hash,
-            WS2PMessage::_V0 | WS2PMessage::_V1 => {
-                fatal_error!("Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !")
-            }
-        }
-    }
-
     /// Parse and check bin message
-    pub fn parse_and_check_bin_message(bin_msg: &[u8]) -> Result<WS2PMessage, WS2PMessageError> {
-        let msg: WS2PMessage = bincode::deserialize(&bin_msg)?;
-        let hash = msg.hash();
-        //debug!("parse_and_check_bin_message: hash={:?}", hash);
-        // Compute hash len
-        let hash_len = 33;
-        // Compute signature len
-        let sig_len = if let Some(sig) = msg.signature() {
-            match sig {
-                Sig::Ed25519(_) => 69,
-                Sig::Schnorr() => fatal_error!("Schnorr algo not yet implemented !"),
-            }
-        } else {
-            1
-        };
-
-        if hash.is_none()
-            || Hash::compute(&bin_msg[0..(bin_msg.len() - hash_len - sig_len)])
-                == hash.expect("safe unwrap")
-        {
-            match msg.verify() {
-                Ok(()) => Ok(msg),
-                Err(e) => Err(WS2PMessageError::SigError(e)),
-            }
-        } else {
-            Err(WS2PMessageError::InvalidHash)
-        }
-    }
-}
-
-impl<'de> BinSignable<'de> for WS2PMessage {
-    #[inline]
-    fn add_sig_to_bin_datas(&self, bin_datas: &mut Vec<u8>) {
-        bin_datas.extend_from_slice(
-            &bincode::serialize(&self.signature()).expect("Fail to binarize sig !"),
-        );
-    }
-    #[inline]
-    fn get_bin_without_sig(&self) -> Result<Vec<u8>, failure::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));
-        Ok(bin_msg)
-    }
-    fn issuer_pubkey(&self) -> PubKey {
-        match *self {
-            WS2PMessage::V2(ref msg_v2) => msg_v2.issuer_pubkey,
-            WS2PMessage::_V0 | WS2PMessage::_V1 => {
-                fatal_error!("Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !")
-            }
-        }
-    }
-    fn signature(&self) -> Option<Sig> {
-        match *self {
-            WS2PMessage::V2(ref msg_v2) => msg_v2.signature,
-            WS2PMessage::_V0 | WS2PMessage::_V1 => {
-                fatal_error!("Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !")
-            }
-        }
-    }
-    fn set_signature(&mut self, signature: Sig) {
-        match *self {
-            WS2PMessage::V2(ref mut msg_v2) => msg_v2.signature = Some(signature),
-            WS2PMessage::_V0 | WS2PMessage::_V1 => {
-                fatal_error!("Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !")
+    pub fn parse_and_check_incoming_bin_message(
+        secure_transport_layer: &mut SecureLayer,
+        bin_msg: &[u8],
+    ) -> Result<(Vec<WS2PMessage>, Option<PubKey>), WS2PMessageError> {
+        let incoming_msgs = secure_transport_layer.read::<WS2PMessage>(bin_msg)?;
+
+        let mut incoming_ws2p_msgs = Vec::with_capacity(incoming_msgs.len());
+        let mut remote_pubkey = None;
+        for incoming_msg in incoming_msgs {
+            match incoming_msg {
+                IncomingMessage::Connect {
+                    custom_datas: ws2p_msg_opt,
+                    peer_sig_public_key,
+                } => {
+                    if let Some(ws2p_msg) = ws2p_msg_opt {
+                        let mut pubkey_buffer = [0u8; 32];
+                        pubkey_buffer.copy_from_slice(&peer_sig_public_key[..]);
+                        remote_pubkey = Some(PubKey::Ed25519(ed25519::PublicKey(pubkey_buffer)));
+                        incoming_ws2p_msgs.push(ws2p_msg);
+                    } else {
+                        return Err(WS2PMessageError::UnexpectedEmpty);
+                    }
+                }
+                IncomingMessage::Ack {
+                    custom_datas: ws2p_msg_opt,
+                } => {
+                    if let Some(ws2p_msg) = ws2p_msg_opt {
+                        incoming_ws2p_msgs.push(ws2p_msg);
+                    } else {
+                        return Err(WS2PMessageError::UnexpectedEmpty);
+                    }
+                }
+                IncomingMessage::Message {
+                    datas: ws2p_msg_opt,
+                } => {
+                    if let Some(ws2p_msg) = ws2p_msg_opt {
+                        incoming_ws2p_msgs.push(ws2p_msg);
+                    } else {
+                        return Err(WS2PMessageError::UnexpectedEmpty);
+                    }
+                }
             }
         }
+        Ok((incoming_ws2p_msgs, remote_pubkey))
     }
 }
 
@@ -166,7 +128,6 @@ mod tests {
     use dubp_common_doc::{BlockNumber, Blockstamp};
     use dubp_currency_params::CurrencyName;
     use dubp_user_docs::documents::certification::*;
-    use dup_crypto::keys::bin_signable::BinSignable;
     use dup_crypto::keys::*;
     use durs_network_documents::network_endpoint::*;
     use durs_network_documents::network_peer::*;
@@ -212,6 +173,7 @@ mod tests {
     pub fn create_peer_card_v11() -> PeerCardV11 {
         PeerCardV11 {
             currency_name: CurrencyName(String::from("g1")),
+            certifiers: vec![],
             issuer: PubKey::Ed25519(keypair1().pubkey),
             node_id: NodeId(0),
             created_on: BlockNumber(50),
@@ -222,43 +184,22 @@ mod tests {
     }
 
     pub fn test_ws2p_message(payload: WS2Pv2MessagePayload) {
-        let keypair1 = keypair1();
-        let signator =
-            SignatorEnum::Ed25519(keypair1.generate_signator().expect("fail to gen signator"));
-        let mut ws2p_message = WS2PMessage::V2(WS2Pv2Message {
+        let ws2p_message = WS2PMessage::V2(WS2Pv2Message {
             currency_name: 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(&signator);
-        if let Ok(bin_msg) = sign_result {
-            // Test binarization
-            assert_eq!(
-                serialize(&ws2p_message).expect("Fail to serialize WS2Pv2Message !"),
-                bin_msg
-            );
-            // Test sign
-            ws2p_message
-                .verify()
-                .expect("WS2Pv2Message : Invalid signature !");
-            // Test debinarization
-            let debinarization_result: Result<WS2PMessage, 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()
-                );
-            }
+        // Test binarization and debinarization
+        let bin_msg = serialize(&ws2p_message).expect("Fail to serialize WS2Pv2Message !");
+        // Test debinarization
+        let debinarization_result: Result<WS2PMessage, bincode::Error> = deserialize(&bin_msg);
+        if let Ok(ws2p_message2) = debinarization_result {
+            assert_eq!(ws2p_message, ws2p_message2);
         } else {
             panic!(
-                "Fail to sign ws2p_message : {:?}",
-                sign_result.err().unwrap()
+                "Fail to debinarize ws2p_message : {:?}",
+                debinarization_result.err().unwrap()
             );
         }
     }
diff --git a/lib/modules/ws2p/ws2p-messages/v2/api_features.rs b/lib/modules/ws2p/ws2p-messages/v2/api_features.rs
index e37ad4d12d9a34f73da9a5a01b24dc7d8f5cdc27..0b606d7ec0b97137d88650970bb05d7c742f60c4 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/api_features.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/api_features.rs
@@ -13,8 +13,15 @@
 // 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 serde::{Deserialize, Serialize};
+
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
 /// WS2PFeatures
+/// ws2p features are used to tell other nodes which features the node has
+/// ABF [depreciated] tells if the node supports full binary format
+/// DEF [depreciated] tells if the node supports compression (managed by websocket)
+/// LOW corresponds to a low quality connection
+/// RBC tells if the node understands rust bincode format
 pub struct WS2PFeatures(pub [u8; 4]);
 
 impl WS2PFeatures {
@@ -27,16 +34,12 @@ impl WS2PFeatures {
         }
         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 {
+    /// Check flag RBC
+    pub fn rbc(self) -> bool {
         self.0[0] | 0b1111_1011 == 255u8
     }
     /// Check features compatibility
@@ -45,17 +48,20 @@ impl WS2PFeatures {
         remote_features: WS2PFeatures,
     ) -> Result<WS2PFeatures, ()> {
         let mut merged_features = self;
-        // Remove features unsuported by remote node
-        if self.def() && !remote_features.def() {
-            merged_features.0[0] &= 0b1111_1110;
-        }
+        // Remove features unsupported by remote node
+        // if self.def() && !remote_features.def() {
+        //     merged_features.0[0] &= 0b1111_1110;
+        // }
         if self.low() && !remote_features.low() {
             merged_features.0[0] &= 0b1111_1101;
         }
-        if self.abf() && !remote_features.abf() {
+        // if self.abf() && !remote_features.abf() {
+        //     merged_features.0[0] &= 0b1111_1011;
+        // }
+        if self.rbc() && !remote_features.rbc() {
             merged_features.0[0] &= 0b1111_1011;
         }
-        // Check incompatiblities
+        // Check incompatibilities
         if remote_features.low() && !self.low() {
             Err(())
         } else {
diff --git a/lib/modules/ws2p/ws2p-messages/v2/connect.rs b/lib/modules/ws2p/ws2p-messages/v2/connect.rs
index 97f938a21c1b3eca57608e7a1df5c5f81722e1a7..851e92c09f9119e07e8298735b5e5a42cdf7f0dc 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/connect.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/connect.rs
@@ -17,124 +17,58 @@
 use super::api_features::WS2PFeatures;
 use dubp_common_doc::blockstamp::Blockstamp;
 use dup_crypto::hashs::Hash;
-use durs_network_documents::network_peer::PeerCardV11;
+use durs_network_documents::network_peer::PeerCard;
+use serde::{Deserialize, Serialize};
 
 /// WS2P v2 connect message min size
 pub static CONNECT_MSG_MIN_SIZE: &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 {
-        0b1111_1110 | self.0[0] == 255u8
-    }
-    /// Check flag ASK_SYNC_CHUNK
-    pub fn ask_sync_chunk(&self) -> bool {
-        0b1111_1101 | self.0[0] == 255u8
-    }
-    /// Check flag RES_SYNC_CHUNK
-    pub fn res_sync_chunk(&self) -> bool {
-        0b1111_1011 | self.0[0] == 255u8
-    }
-    /// Check flag CLIENT
-    pub fn client(&self) -> bool {
-        0b1111_0111 | self.0[0] == 255u8
-    }
-}
-
-impl From<WS2Pv2ConnectType> for WS2PConnectFlags {
-    fn from(connect_type: WS2Pv2ConnectType) -> Self {
-        match connect_type {
-            WS2Pv2ConnectType::Incoming | WS2Pv2ConnectType::OutgoingServer => {
-                WS2PConnectFlags(vec![])
-            }
-            WS2Pv2ConnectType::OutgoingClient => WS2PConnectFlags(vec![8u8]),
-            WS2Pv2ConnectType::Sync(_) => WS2PConnectFlags(vec![1u8]),
-            WS2Pv2ConnectType::SyncAskChunk(_) => WS2PConnectFlags(vec![3u8]),
-            WS2Pv2ConnectType::SyncSendChunks => WS2PConnectFlags(vec![5u8]),
-        }
-    }
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+/// ChallengeOrEphemeralDhPublicKey to manage connection under TLS or not
+pub enum ChallengeOrEphemeralDhPublicKey {
+    /// challenge generated randomly if TLS
+    Challenge(Hash),
+    /// ephemeral dh public key hash if no TLS
+    EphemeralDbPublicKey(Hash),
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
 /// WS2Pv2ConnectType
 pub enum WS2Pv2ConnectType {
-    /// Incoming connection
+    /// Connect message sent when receiving an incoming websocket connection
     Incoming,
-    /// Client outgoing connection
-    OutgoingClient,
-    /// Server outgoing connection
-    OutgoingServer,
-    /// Sync outgoing connection (from blockstamp, or from genesis block if blockstamp is none)
-    Sync(Option<Blockstamp>),
-    /// Sync outgoing connection to request chunk
+    /// Client connection
+    Client,
+    /// Server normal connection
+    Server,
+    /// Sync connection (from blockstamp, or from genesis block if blockstamp is none)
+    Sync {
+        /// block from which the sync should start
+        from_blockstamp: Option<Blockstamp>,
+    },
+    /// Sync connection to request chunk
     SyncAskChunk(Blockstamp),
-    /// Sync outgoing connection to send chunk
-    SyncSendChunks,
-}
-
-impl WS2Pv2ConnectType {
-    /// Create WS2Pv2ConnectType from WS2PConnectFlags
-    pub fn from_flags(
-        flags: &WS2PConnectFlags,
-        blockstamp: Option<Blockstamp>,
-    ) -> WS2Pv2ConnectType {
-        if !flags.is_empty() && flags.sync() {
-            if flags.ask_sync_chunk() && blockstamp.is_some() {
-                WS2Pv2ConnectType::SyncAskChunk(blockstamp.expect("safe unwrap"))
-            } else if flags.res_sync_chunk() {
-                WS2Pv2ConnectType::SyncSendChunks
-            } else {
-                WS2Pv2ConnectType::Sync(blockstamp)
-            }
-        } else {
-            WS2Pv2ConnectType::OutgoingServer
-        }
-    }
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
-/// WS2Pv2OkWS2Pv2ConnectMsgMsg
+/// WS2Pv2ConnectMsg
 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).
+    /// 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, // TODO change to ChallengeOrEphemeralDhPublicKey | still relevant ?
+    /// 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,
+    /// connect type
+    pub connect_type: WS2Pv2ConnectType,
     /// 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([0u8; 4]),
-            flags_queries: WS2PConnectFlags(vec![]),
-            peer_card: None,
-            chunkstamp: None,
-        }
-    }
+    pub peer_card: PeerCard,
 }
 
 /// Generate connect message
@@ -142,19 +76,13 @@ pub fn generate_connect_message(
     connect_type: WS2Pv2ConnectType,
     api_features: WS2PFeatures,
     challenge: Hash,
-    peer_card: Option<PeerCardV11>,
+    peer_card: PeerCard,
 ) -> WS2Pv2ConnectMsg {
-    let chunkstamp = if let WS2Pv2ConnectType::SyncAskChunk(chunkstamp) = connect_type {
-        Some(chunkstamp)
-    } else {
-        None
-    };
     WS2Pv2ConnectMsg {
         challenge,
         api_features,
-        flags_queries: WS2PConnectFlags::from(connect_type),
+        connect_type,
         peer_card,
-        chunkstamp,
     }
 }
 
@@ -163,26 +91,8 @@ mod tests {
     use super::super::*;
     use super::*;
     use crate::tests::*;
-    use dubp_common_doc::Blockstamp;
     use dup_crypto::keys::text_signable::TextSignable;
-
-    #[test]
-    fn test_ws2p_connect_flags() {
-        // test sync()
-        assert!(WS2PConnectFlags(vec![1u8]).sync());
-        assert!(WS2PConnectFlags(vec![3u8]).sync());
-        assert!(WS2PConnectFlags(vec![5u8]).sync());
-
-        // test ask_sync_chunk()
-        assert_eq!(WS2PConnectFlags(vec![1u8]).ask_sync_chunk(), false);
-        assert!(WS2PConnectFlags(vec![3u8]).ask_sync_chunk());
-        assert_eq!(WS2PConnectFlags(vec![5u8]).ask_sync_chunk(), false);
-
-        // test res_sync_chunk()
-        assert_eq!(WS2PConnectFlags(vec![1u8]).res_sync_chunk(), false);
-        assert_eq!(WS2PConnectFlags(vec![3u8]).res_sync_chunk(), false);
-        assert!(WS2PConnectFlags(vec![5u8]).res_sync_chunk());
-    }
+    use dup_crypto::keys::*;
 
     #[test]
     fn test_ws2p_message_connect() {
@@ -195,16 +105,10 @@ mod tests {
             challenge: Hash::from_hex(
                 "000007722B243094269E548F600BD34D73449F7578C05BD370A6D301D20B5F10",
             )
-            .unwrap(),
+            .expect("wrong hash"),
             api_features: WS2PFeatures([7u8, 0, 0, 0]),
-            flags_queries: WS2PConnectFlags(vec![]),
-            peer_card: Some(peer),
-            chunkstamp: Some(
-                Blockstamp::from_string(
-                    "499-000011BABEEE1020B1F6B2627E2BC1C35BCD24375E114349634404D2C266D84F",
-                )
-                .unwrap(),
-            ),
+            connect_type: WS2Pv2ConnectType::Client,
+            peer_card: PeerCard::V11(peer),
         };
         test_ws2p_message(WS2Pv2MessagePayload::Connect(Box::new(connect_msg)));
     }
diff --git a/lib/modules/ws2p/ws2p-messages/v2/mod.rs b/lib/modules/ws2p/ws2p-messages/v2/mod.rs
index 391cd1082442e04f11b23ab2717a38819ef1e547..c7a2ae19592145d4c2f2484837a405545ef21cc6 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/mod.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/mod.rs
@@ -27,14 +27,16 @@ pub mod req_responses;
 pub mod requests;
 /// WS2P v2 SECRET_FLAGS Message
 pub mod secret_flags;
+/// WS2P v2 SYNC_INFO Message
+pub mod sync_info;
 
 use crate::v2::payload_container::*;
 use crate::WS2PMessage;
 use dubp_currency_params::CurrencyName;
-use dup_crypto::hashs::Hash;
-use dup_crypto::keys::bin_signable::BinSignable;
-use dup_crypto::keys::*;
+use dup_crypto::keys::Sig;
 use durs_network_documents::NodeId;
+use serde::{Deserialize, Serialize};
+use std::io::BufWriter;
 
 /// WS2P v2 message metadata size
 pub static WS2P_V2_MESSAGE_METADATA_SIZE: &usize = &144;
@@ -46,14 +48,18 @@ pub struct WS2Pv2Message {
     pub currency_name: CurrencyName,
     /// Issuer NodeId
     pub issuer_node_id: NodeId,
-    /// Issuer plublic key
-    pub issuer_pubkey: PubKey,
     /// Message payload
     pub payload: WS2Pv2MessagePayload,
-    /// Message hash
-    pub message_hash: Option<Hash>,
-    /// Signature
-    pub signature: Option<Sig>,
+}
+
+/// WS2PMessageSigned avoids having signature defined
+/// in message since only the message has to be signed
+#[derive(Debug, Clone)]
+pub struct WS2PMessageSigned {
+    /// message
+    pub message: WS2Pv2Message,
+    /// signature
+    pub signature: Sig,
 }
 
 impl WS2Pv2Message {
@@ -62,22 +68,49 @@ impl WS2Pv2Message {
 
     /// Encapsulate message payload
     pub fn encapsulate_payload(
+        sl: &mut pkstl::SecureLayer,
         currency_name: CurrencyName,
         issuer_node_id: NodeId,
-        issuer_signator: &SignatorEnum,
         payload: WS2Pv2MessagePayload,
-    ) -> Result<(WS2PMessage, Vec<u8>), SignError> {
-        let mut msg = WS2PMessage::V2(WS2Pv2Message {
-            currency_name,
-            issuer_node_id,
-            issuer_pubkey: issuer_signator.public_key(),
-            payload,
-            message_hash: None,
-            signature: None,
-        });
-        match msg.sign(issuer_signator) {
-            Ok(bin_msg) => Ok((msg, bin_msg)),
-            Err(e) => Err(e),
+        // TODO add serialization_format: WS2PSerializationFormat,
+    ) -> Result<Vec<u8>, pkstl::Error> {
+        match payload {
+            WS2Pv2MessagePayload::Connect(_) => {
+                let msg = WS2PMessage::V2(WS2Pv2Message {
+                    currency_name,
+                    issuer_node_id,
+                    payload,
+                });
+                let mut buffer = BufWriter::new(Vec::with_capacity(1_024));
+                sl.write_connect_msg(Some(&msg), &mut buffer)?;
+                Ok(buffer
+                    .into_inner()
+                    .expect("Vec buffer must be always flushed"))
+            }
+            WS2Pv2MessagePayload::Ack { .. } => {
+                let msg = WS2PMessage::V2(WS2Pv2Message {
+                    currency_name,
+                    issuer_node_id,
+                    payload,
+                });
+                let mut buffer = BufWriter::new(Vec::with_capacity(1_024));
+                sl.write_ack_msg(Some(&msg), &mut buffer)?;
+                Ok(buffer
+                    .into_inner()
+                    .expect("Vec buffer must be always flushed"))
+            }
+            _ => {
+                let msg = WS2PMessage::V2(WS2Pv2Message {
+                    currency_name,
+                    issuer_node_id,
+                    payload,
+                });
+                let mut buffer = BufWriter::new(Vec::with_capacity(1_024));
+                sl.write(&msg, &mut buffer)?;
+                Ok(buffer
+                    .into_inner()
+                    .expect("Vec buffer must be always flushed"))
+            }
         }
     }
 }
@@ -86,7 +119,9 @@ impl WS2Pv2Message {
 mod tests {
     use super::*;
     use crate::tests::*;
+    use dup_crypto::hashs::Hash;
     use dup_crypto::keys::text_signable::TextSignable;
+    use dup_crypto::keys::*;
 
     #[test]
     fn test_ws2p_message_ack() {
diff --git a/lib/modules/ws2p/ws2p-messages/v2/ok.rs b/lib/modules/ws2p/ws2p-messages/v2/ok.rs
index 5701efc4854c5b9d030e1df0c999d27eb636ab73..8d3b48343595951ec903d63293bc92106a4c427f 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/ok.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/ok.rs
@@ -13,65 +13,33 @@
 // 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 dubp_common_doc::blockstamp::Blockstamp;
-use dup_crypto::hashs::Hash;
+use serde::{Deserialize, Serialize};
 use std::num::NonZeroU16;
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, 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,
-        }
+        WS2Pv2OkMsg { prefix: 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 crate::tests::*;
-    use dubp_common_doc::Blockstamp;
     use std::num::NonZeroU16;
 
     #[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(WS2Pv2MessagePayload::Ok(ok_msg));
     }
diff --git a/lib/modules/ws2p/ws2p-messages/v2/payload_container.rs b/lib/modules/ws2p/ws2p-messages/v2/payload_container.rs
index 33642345d1aadbb3a0a229a69005cd4896ec3dfa..ac5b75271e46a0d173cf8de5004edeef96bc4e30 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/payload_container.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/payload_container.rs
@@ -18,6 +18,7 @@ use super::ok::WS2Pv2OkMsg;
 use super::req_responses::WS2Pv2ReqRes;
 use super::requests::WS2Pv2Request;
 use super::secret_flags::WS2Pv2SecretFlagsMsg;
+use super::sync_info::WS2Pv2SyncInfo;
 use dubp_block_doc::BlockDocument;
 use dubp_user_docs::documents::certification::CertificationDocument;
 use dubp_user_docs::documents::identity::IdentityDocument;
@@ -28,12 +29,14 @@ use dup_crypto::hashs::Hash;
 use durs_network_documents::network_head_v2::NetworkHeadV2;
 use durs_network_documents::network_head_v3::NetworkHeadV3;
 use durs_network_documents::network_peer::PeerCardV11;
+use serde::{Deserialize, Serialize};
 
 /// WS2P v2 message payload metadata size
 pub static WS2P_V2_MESSAGE_PAYLOAD_METADATA_SIZE: &usize = &8;
 
 /// WS2Pv2MessagePayload
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "type", content = "content")]
 pub enum WS2Pv2MessagePayload {
     /// CONNECT message
     Connect(Box<WS2Pv2ConnectMsg>),
@@ -42,8 +45,8 @@ pub enum WS2Pv2MessagePayload {
         /// Hash previously sent in CONNECT message
         challenge: Hash,
     },
-    /// SECRET_FLAGS Message
-    SecretFlags(WS2Pv2SecretFlagsMsg),
+    /// BLOCKS Message
+    Blocks(Vec<BlockDocument>),
     /// OK Message
     Ok(WS2Pv2OkMsg),
     /// KO Message
@@ -52,14 +55,12 @@ pub enum WS2Pv2MessagePayload {
     Request(WS2Pv2Request),
     /// REQUEST_RESPONSE Message
     ReqRes(WS2Pv2ReqRes),
-    /// PEERS Message
-    Peers(Vec<PeerCardV11>),
     /// HEADS_V2 Message
     Headsv2(Vec<NetworkHeadV2>),
     /// HEADS_V3 Message
     Heads3(Vec<NetworkHeadV3>),
-    /// BLOCKS Message
-    Blocks(Vec<BlockDocument>),
+    /// PEERS Message
+    Peers(Vec<PeerCardV11>),
     /// PENDING_IDENTITIES Message
     PendingIdentities(Vec<IdentityDocument>),
     /// PENDING_MEMBERSHIPS Message
@@ -70,4 +71,8 @@ pub enum WS2Pv2MessagePayload {
     PendingRevocations(Vec<RevocationDocumentV10>),
     /// PENDING_TXS Message
     PendingTxs(Vec<TransactionDocument>),
+    /// SECRET_FLAGS Message
+    SecretFlags(WS2Pv2SecretFlagsMsg),
+    /// SYNC_INFO Message Info needed for sync
+    SyncInfo(WS2Pv2SyncInfo),
 }
diff --git a/lib/modules/ws2p/ws2p-messages/v2/req_responses.rs b/lib/modules/ws2p/ws2p-messages/v2/req_responses.rs
index 3bdfd5753c198ed107a2c31af179cd29829d2ca8..f57d1a18542ee31c9ff7729d893701acab2a187a 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/req_responses.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/req_responses.rs
@@ -19,6 +19,7 @@ use dubp_user_docs::documents::certification::CompactCertificationDocumentV10;
 use dubp_user_docs::documents::identity::v10::CompactIdentityDocumentV10;
 use dubp_user_docs::documents::membership::v10::CompactPoolMembershipDoc;
 use dup_crypto::hashs::Hash;
+use serde::{Deserialize, Serialize};
 use std::str;
 
 /// WS2Pv2 request response
diff --git a/lib/modules/ws2p/ws2p-messages/v2/requests.rs b/lib/modules/ws2p/ws2p-messages/v2/requests.rs
index 4d7cb689a5fc1efcd968e85ff5be1125fd376ce2..7bc3f32b2e5b4da62eb5c72173c6ba7c063e11f7 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/requests.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/requests.rs
@@ -15,6 +15,7 @@
 
 use dubp_common_doc::blockstamp::Blockstamp;
 use dubp_common_doc::BlockNumber;
+use serde::{Deserialize, Serialize};
 
 /// WS2Pv2Request
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
@@ -54,9 +55,12 @@ pub enum WS2Pv2RequestBody {
     /// 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),
+    WotPool {
+        /// param1: folders_count (u16)
+        folders_count: u16,
+        /// param2: min_cert (u8)
+        min_cert: u8,
+    },
 }
 
 impl WS2Pv2RequestBody {
@@ -66,7 +70,7 @@ impl WS2Pv2RequestBody {
             WS2Pv2RequestBody::None | WS2Pv2RequestBody::Current => 1,
             WS2Pv2RequestBody::BlocksHashs(_, _) | WS2Pv2RequestBody::Chunk(_, _) => 7,
             WS2Pv2RequestBody::ChunkByHash(_) => 37,
-            WS2Pv2RequestBody::WotPool(_, _) => 4,
+            WS2Pv2RequestBody::WotPool { .. } => 4,
         }
     }
 }
diff --git a/lib/modules/ws2p/ws2p-messages/v2/secret_flags.rs b/lib/modules/ws2p/ws2p-messages/v2/secret_flags.rs
index 78193a97b149d929a93d69833c2065793d975627..3849b980120d57295be5fd2f6f4e370a33bf0d56 100644
--- a/lib/modules/ws2p/ws2p-messages/v2/secret_flags.rs
+++ b/lib/modules/ws2p/ws2p-messages/v2/secret_flags.rs
@@ -14,6 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use dup_crypto::keys::*;
+use serde::{Deserialize, Serialize};
 
 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
 /// WS2Pv2SecretFlags
@@ -67,6 +68,7 @@ mod tests {
     use super::super::*;
     use super::*;
     use crate::tests::*;
+    use dup_crypto::hashs::Hash;
 
     #[test]
     fn test_ws2p_message_secret_flags() {
diff --git a/lib/modules/ws2p/ws2p-messages/v2/sync_info.rs b/lib/modules/ws2p/ws2p-messages/v2/sync_info.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1f05dd2a157aa6407f13ab9a822a851975902ebb
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-messages/v2/sync_info.rs
@@ -0,0 +1,60 @@
+//  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 dup_crypto::hashs::Hash;
+use dubp_common_doc::BlockHash;
+use dubp_common_doc::Blockstamp;
+use durs_network_documents::network_peer::PeerCard;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] // Copy
+/// WS2Pv2SyncInfo
+pub struct WS2Pv2SyncInfo {
+    /// chunk size
+    pub chunk_size: u32,
+    /// Synchronization target. Is is the current blockstamp of the node
+    pub target_blockstamp: Blockstamp,
+    /// Hash of the last block of each chunk (i.e. blocks whose number equal -1 modulo chunk_size)
+    pub milestones: Vec<BlockHash>,
+    /// Peer Cards allows node to request chunks to other nodes
+    pub peer_cards: Vec<PeerCard>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+    use crate::tests::*;
+    use dubp_common_doc::*;
+    use dup_crypto::hashs::Hash;
+
+    #[test]
+    fn test_ws2p_message_syncinfo() {
+        let mock_blockstamp = Blockstamp {
+            id: BlockNumber(0),
+            hash: BlockHash(
+                Hash::from_hex("000007722B243094269E548F600BD34D73449F7578C05BD370A6D301D20B5F10")
+                    .unwrap(),
+            ),
+        };
+        let sync_info_msg = WS2Pv2SyncInfo {
+            chunk_size: 500,
+            target_blockstamp: mock_blockstamp,
+            milestones: vec![],
+            peer_cards: vec![],
+        };
+        test_ws2p_message(WS2Pv2MessagePayload::SyncInfo(sync_info_msg));
+    }
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/Cargo.toml b/lib/modules/ws2p/ws2p-protocol/Cargo.toml
index 958e7bcbddc08ecf929d84ea1ebd357e59f994cf..f4bafb65bbe0d930bc08378e2a290786c4e6cb51 100644
--- a/lib/modules/ws2p/ws2p-protocol/Cargo.toml
+++ b/lib/modules/ws2p/ws2p-protocol/Cargo.toml
@@ -10,16 +10,24 @@ edition = "2018"
 path = "src/lib.rs"
 
 [dependencies]
-dubp-common-doc = { path = "../../../dubp/common-doc"} #, version = "0.1.0" }
+dubp-common-doc = { path = "../../../dubp/common-doc"}
+dubp-block-doc = { path = "../../../dubp/block-doc"}
 dubp-currency-params = { path = "../../../dubp/currency-params" }
 dubp-user-docs= { path = "../../../dubp/user-docs" }
+rand = "0.5.*"
+bincode = "1.0.*"
+durs-conf= { path = "../../../core/conf" }
 durs-common-tools = { path = "../../../tools/common-tools" }
 dup-crypto = { path = "../../../crypto" }
 durs-module = { path = "../../../core/module" }
 durs-network-documents = { path = "../../../dunp/network-documents" }
 durs-ws2p-messages = { path = "../ws2p-messages" }
+durs-message = { path = "../../../core/message" }
+durs-network = { path = "../../../core/network" }
+durs-bc-db-reader = { path = "../../../modules-lib/bc-db-reader" }
 failure = "0.1.5"
 log = "0.4.*"
+pkstl = { path = "../../../tools/pkstl", features = ["bin","cbor"] }
 serde = "1.0.*"
 serde_derive = "1.0.*"
 unwrap = "1.2.1"
@@ -27,4 +35,4 @@ unwrap = "1.2.1"
 [dev-dependencies]
 pretty_assertions = "0.5.1"
 
-[features]
\ No newline at end of file
+[features]
diff --git a/lib/modules/ws2p/ws2p-protocol/src/constants.rs b/lib/modules/ws2p/ws2p-protocol/src/constants.rs
index e9828af3e7e22e2e2fbdc1bb98476a4157fc7dab..5cccc702dae1405b2ccdcda8bd7e948a0e3c7d36 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/constants.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/constants.rs
@@ -32,3 +32,21 @@ pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &u64 = &100;
 
 /// Number of invalid messages tolerated
 pub static WS2P_INVALID_MSGS_LIMIT: &usize = &5;
+
+/// Number of simultaneous outgoing connection
+pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &usize = &10;
+
+/*pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &u64 = &75;
+pub static WS2P_OUTCOMING_INTERVAL: &u64 = &300;*/
+
+/// Timeout before receiving message from service
+pub static WS2P_RECV_SERVICE_FREQ_IN_MS: &u64 = &1_000;
+/*
+pub static WS2P_REQUEST_TIMEOUT: &u64 = &30_000;
+pub static DURATION_BEFORE_RECORDING_ENDPOINT: &u64 = &180;
+pub static BLOCKS_REQUEST_INTERVAL: &u64 = &60;
+pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &u64 = &40;
+*/
+
+/// Chunk size (in blocks), can differ with blockchain chunk size
+pub static CHUNK_SIZE: &u32 = &500;
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/meta_datas.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/meta_datas.rs
index 34f9b2671e99615b225c8cf41de9199fc2d340b2..73c49fd895bbf8767fab07f2ad082afd68e4d78c 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/meta_datas.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/meta_datas.rs
@@ -18,28 +18,23 @@
 use crate::connection_state::WS2PConnectionState;
 use crate::MySelfWs2pNode;
 use dubp_common_doc::Blockstamp;
-use dubp_currency_params::CurrencyName;
 use dup_crypto::hashs::Hash;
 use dup_crypto::keys::{KeyPair, SignatorEnum};
 use durs_common_tools::fatal_error;
 use durs_network_documents::network_peer::PeerCardV11;
 use durs_network_documents::NodeFullId;
 use durs_ws2p_messages::v2::api_features::WS2PFeatures;
-use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use log::error;
 use std::time::SystemTime;
+// use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 
 #[derive(Debug)]
 /// WS2p Connection meta datas
 pub struct WS2PControllerMetaDatas {
     /// Local challenge
     pub challenge: Hash,
-    /// connect type
-    pub connect_type: WS2Pv2ConnectType,
     /// Count invalid messages
     pub count_invalid_msgs: usize,
-    /// Currency name
-    pub currency: CurrencyName,
     /// Controller creation time
     pub creation_time: SystemTime,
     /// Connection features
@@ -50,8 +45,6 @@ pub struct WS2PControllerMetaDatas {
     pub last_mess_time: SystemTime,
     /// Local node properties
     pub local_node: MySelfWs2pNode,
-    /// Remote connect type
-    pub remote_connect_type: Option<WS2Pv2ConnectType>,
     /// Remote node datas
     pub remote_node: Option<Ws2pRemoteNodeDatas>,
     /// Indicator required for the anti-spam mechanism
@@ -63,13 +56,8 @@ pub struct WS2PControllerMetaDatas {
 }
 
 impl WS2PControllerMetaDatas {
-    /// Instanciate new WS2PControllerMetaDatas
-    pub fn new(
-        challenge: Hash,
-        connect_type: WS2Pv2ConnectType,
-        currency: CurrencyName,
-        local_node: MySelfWs2pNode,
-    ) -> Self {
+    /// Instantiate new WS2PControllerMetaDatas
+    pub fn new(challenge: Hash, local_node: MySelfWs2pNode) -> Self {
         let signator = if let Ok(signator) = local_node.my_key_pair.generate_signator() {
             signator
         } else {
@@ -78,14 +66,11 @@ impl WS2PControllerMetaDatas {
 
         WS2PControllerMetaDatas {
             challenge,
-            connect_type,
             count_invalid_msgs: 0,
-            currency,
             creation_time: SystemTime::now(),
             features: None,
             last_mess_time: SystemTime::now(),
             local_node,
-            remote_connect_type: None,
             remote_node: None,
             signator,
             spam_interval: false,
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/mod.rs
similarity index 59%
rename from lib/modules/ws2p/ws2p-protocol/src/controller.rs
rename to lib/modules/ws2p/ws2p-protocol/src/controller/mod.rs
index 23d1323578eecaf9e3f1ee557336ec7aed7bc291..4be181c3832245fe056580e3018e3ec5ddaaab80 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/mod.rs
@@ -20,100 +20,127 @@ pub mod meta_datas;
 mod on_message;
 mod on_open;
 
+// use durs_common_tools::fatal_error;
 use self::meta_datas::WS2PControllerMetaDatas;
 use crate::connection_state::WS2PConnectionState;
 use crate::constants;
 use crate::orchestrator::OrchestratorMsg;
 use crate::websocket::{WebsocketAction, WebsocketIncomingEvent};
+use dup_crypto::keys::*;
 use durs_module::ModuleMessage;
 use durs_network_documents::NodeFullId;
 use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use durs_ws2p_messages::WS2PMessage;
 use failure::Fail;
-use std::sync::mpsc::{Receiver, SendError, Sender};
-use std::time::SystemTime;
+use std::sync::mpsc::{Receiver, Sender};
+use std::time::{Duration, SystemTime};
 use unwrap::unwrap;
 
-#[derive(Copy, Clone, Debug, Hash)]
-/// WS2P Controller unique identitier
-pub enum WS2PControllerId {
-    /// Client controller
-    Client {
-        /// Expected remote node full id
-        expected_remote_full_id: Option<NodeFullId>,
-    },
-    /// Server Incoming controller
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
+/// WS2P connection direction
+pub enum WS2PConnectionDirection {
+    /// any incoming connection
+    /// client or incoming sync
     Incoming,
-    /// Server outgoing controller
-    Outgoing {
-        /// Expected remote node full id
-        expected_remote_full_id: Option<NodeFullId>,
-    },
+    /// any outgoing connection
+    /// server or outgoing sync
+    Outgoing,
 }
 
-impl WS2PControllerId {
-    /// Get expected remote node full id
-    pub fn expected_remote_full_id(&self) -> Option<NodeFullId> {
-        match self {
-            WS2PControllerId::Client {
-                expected_remote_full_id,
-            }
-            | WS2PControllerId::Outgoing {
-                expected_remote_full_id,
-            } => *expected_remote_full_id,
-            WS2PControllerId::Incoming => None,
-        }
-    }
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
+/// info relative to WS2P Controller
+pub struct WS2PControllerId {
+    /// remote full node id
+    pub node_id: NodeFullId,
+    /// connection type
+    pub conn_type: WS2Pv2ConnectType,
+    /// connection direction
+    pub direction: WS2PConnectionDirection,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug)]
 /// Event transmitted to the orchestrator
 pub enum WS2PControllerEvent {
-    /// New connection established
+    /// New connection established (after Ok message)
     NewConnEstablished {
-        /// Connection type
-        conn_type: WS2Pv2ConnectType,
-        /// Remote node full id
-        remote_full_id: NodeFullId,
+        /// secure layer
+        secure_layer: pkstl::SecureLayer,
+        /// sender
+        sender: Sender<WebsocketActionOrder>,
     },
     /// Connection state change
     StateChange {
         /// New connection state
         new_state: WS2PConnectionState,
     },
-    /// The controller only reports a message if it cannot process it entirely on its own.
-    /// For example, connection negotiation messages are not sent back.
+    /// Message that needs to be processed outside controller
     RecvValidMsg {
         /// WS2P Message
         ws2p_msg: WS2PMessage,
     },
 }
 
-#[derive(Debug)]
 /// WS2P Controller
+#[derive(Debug)]
 pub struct WS2PController<M: ModuleMessage> {
-    /// Controller id
-    pub id: WS2PControllerId,
+    /// remote full node id or expected one when connecting to a known endpoint
+    pub node_id: Option<NodeFullId>,
+    /// connection type
+    pub conn_type: WS2Pv2ConnectType,
+    /// connection direction
+    pub direction: WS2PConnectionDirection,
     /// Orchestrator sender
     pub orchestrator_sender: Sender<OrchestratorMsg<M>>,
     /// Controller meta datas
     pub meta_datas: WS2PControllerMetaDatas,
+    /// Controller self sender
+    pub self_sender: Sender<WebsocketActionOrder>,
     /// Controller receiver
     pub receiver: Receiver<WebsocketActionOrder>,
+    /// Connection Security Layer
+    pub secure_layer: pkstl::SecureLayer,
+}
+
+impl<M: ModuleMessage> WS2PController<M> {
+    /// returns this controller's WS2PControllerId
+    /// only works for established connections with all info available
+    pub fn id(self: &Self) -> WS2PControllerId {
+        if let Some(node_id) = self.node_id {
+            WS2PControllerId {
+                node_id,
+                conn_type: self.conn_type,
+                direction: self.direction,
+            }
+        } else {
+            unreachable!("dev error : not supposed to call id if node_id is not known");
+        }
+    }
 }
 
-#[derive(Copy, Clone, Debug, Fail)]
+#[derive(Debug, Fail)]
 /// WS2P Controller process error
 pub enum WS2PControllerProcessError {
-    /// Orchestrator unreacheable
+    /// Orchestrator unreachable
     #[fail(display = "WS2P Orchestrator unreachable")]
     OrchestratorUnreacheable,
+    /// Receive connect message without public key
+    #[fail(display = "Receive connect message without public key")]
+    ReceiveConnectMsgWithoutPubKey,
+    /// Security error
+    #[fail(display = "Security error: {:?}", _0)]
+    SecurityError(pkstl::Error),
+}
+
+impl From<pkstl::Error> for WS2PControllerProcessError {
+    fn from(e: pkstl::Error) -> Self {
+        WS2PControllerProcessError::SecurityError(e)
+    }
 }
 
 /// Websocket action order
 #[derive(Clone, Debug)]
 pub struct WebsocketActionOrder {
-    /// Websocket actio,
+    /// Websocket action,
     pub ws_action: WebsocketAction,
     /// New state if action success
     pub new_state_if_success: Option<WS2PConnectionState>,
@@ -172,21 +199,41 @@ impl<M: ModuleMessage> WS2PController<M> {
         }
     }
 
-    /// Try to instanciate new controller
+    /// Try to instantiate new controller
     pub fn try_new(
-        id: WS2PControllerId,
+        node_id: Option<NodeFullId>,
+        conn_type: WS2Pv2ConnectType,
+        direction: WS2PConnectionDirection,
         meta_datas: WS2PControllerMetaDatas,
         orchestrator_sender: Sender<OrchestratorMsg<M>>,
-    ) -> Result<WS2PController<M>, SendError<OrchestratorMsg<M>>> {
+    ) -> Result<WS2PController<M>, WS2PControllerProcessError> {
         let (sender, receiver) = std::sync::mpsc::channel();
 
-        orchestrator_sender.send(OrchestratorMsg::ControllerSender(sender))?;
+        let mut my_key_pair_seed_buffer = [0u8; 32];
+        my_key_pair_seed_buffer.copy_from_slice(meta_datas.local_node.my_key_pair.seed().as_ref());
+
+        let secure_layer = pkstl::SecureLayer::create(
+            pkstl::SecureLayerConfig {
+                message_format: pkstl::MessageFormat::Cbor,
+                ..pkstl::SecureLayerConfig::default()
+            },
+            Some(pkstl::Seed32::new(my_key_pair_seed_buffer)),
+            if let Some(ref remote_node) = meta_datas.remote_node {
+                Some(remote_node.remote_full_id.1.to_bytes_vector())
+            } else {
+                None
+            },
+        )?;
 
         Ok(WS2PController {
-            id,
+            node_id,
+            conn_type,
+            direction,
             meta_datas,
             orchestrator_sender,
+            self_sender: sender,
             receiver,
+            secure_layer,
         })
     }
 
@@ -194,7 +241,7 @@ impl<M: ModuleMessage> WS2PController<M> {
     pub fn get_pending_ws_actions(&self) -> Vec<WebsocketActionOrder> {
         let mut ws_actions = Vec::new();
 
-        while let Ok(ws_action) = self.receiver.recv() {
+        while let Ok(ws_action) = self.receiver.recv_timeout(Duration::from_millis(100)) {
             ws_actions.push(ws_action);
         }
 
@@ -205,7 +252,7 @@ impl<M: ModuleMessage> WS2PController<M> {
     pub fn process(
         &mut self,
         event: WebsocketIncomingEvent,
-    ) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+    ) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
         match event {
             WebsocketIncomingEvent::OnOpen { remote_addr } => on_open::process(self, remote_addr),
             WebsocketIncomingEvent::OnMessage { msg } => on_message::process(self, msg),
@@ -222,17 +269,38 @@ impl<M: ModuleMessage> WS2PController<M> {
                     reason.unwrap_or_else(|| "".to_owned())
                 );
                 self.update_conn_state(WS2PConnectionState::Close)?;
-                Ok(None)
+                Ok(vec![])
             }
         }
     }
 
+    /// send event
     fn send_event(&mut self, event: WS2PControllerEvent) -> Result<(), WS2PControllerProcessError> {
         if self
             .orchestrator_sender
             .send(OrchestratorMsg::ControllerEvent {
-                controller_id: self.id,
+                controller_id: self.id(),
+                event,
+            })
+            .is_err()
+            && self.meta_datas.state != WS2PConnectionState::Close
+        {
+            Err(WS2PControllerProcessError::OrchestratorUnreacheable)
+        } else {
+            Ok(())
+        }
+    }
+
+    /// send anonymous event
+    fn send_anon_event(
+        &mut self,
+        event: WS2PControllerEvent,
+    ) -> Result<(), WS2PControllerProcessError> {
+        if self
+            .orchestrator_sender
+            .send(OrchestratorMsg::AnonControllerEvent {
                 event,
+                sender: self.self_sender.clone(),
             })
             .is_err()
             && self.meta_datas.state != WS2PConnectionState::Close
@@ -249,7 +317,13 @@ impl<M: ModuleMessage> WS2PController<M> {
         &mut self,
         new_state: WS2PConnectionState,
     ) -> Result<(), WS2PControllerProcessError> {
+        // if state was previously established (registered to orchestrator)
+        if let WS2PConnectionState::Established = self.meta_datas.state {
+            self.send_event(WS2PControllerEvent::StateChange { new_state })?;
+        } else {
+            self.send_anon_event(WS2PControllerEvent::StateChange { new_state })?;
+        }
         self.meta_datas.state = new_state;
-        self.send_event(WS2PControllerEvent::StateChange { new_state })
+        Ok(())
     }
 }
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message.rs
index e89760f790492b20c153279aab64ceb0cbb43b1d..10f76683e92cf3f356ae271aa96c93654a9d8d04 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message.rs
@@ -38,7 +38,7 @@ use std::time::{Duration, SystemTime};
 pub fn process<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
     msg: WebsocketMessage,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     // Update last_mess_time
     controller.meta_datas.last_mess_time = SystemTime::now();
 
@@ -64,67 +64,91 @@ pub fn process<M: ModuleMessage>(
             *constants::WS2P_SPAM_SLEEP_TIME_IN_SEC,
         ));
         controller.meta_datas.last_mess_time = SystemTime::now();
-        return Ok(None);
+        return Ok(vec![]);
     }
 
     if let WebsocketMessage::Bin(bin_msg) = msg {
         log::debug!("Receive new bin message there is not a spam !");
-        match WS2PMessage::parse_and_check_bin_message(&bin_msg) {
-            Ok(valid_msg) => match valid_msg {
-                WS2PMessage::V2(ref msg_v2) => {
-                    match msg_v2.payload {
-                        WS2Pv2MessagePayload::Connect(ref box_connect_msg) => {
-                            let connect_msg = box_connect_msg.deref();
-                            // Get remote node id
-                            let remote_full_id =
-                                NodeFullId(msg_v2.issuer_node_id, msg_v2.issuer_pubkey);
-                            // Process connect message
-                            connect_msg::process_ws2p_v2p_connect_msg(
-                                controller,
-                                remote_full_id,
-                                connect_msg,
-                            )
-                        }
-                        WS2Pv2MessagePayload::Ack {
-                            challenge: ack_msg_challenge,
-                        } => {
-                            // Process ack message
-                            ack_msg::process_ws2p_v2p_ack_msg(controller, ack_msg_challenge)
-                        }
-                        WS2Pv2MessagePayload::SecretFlags(ref secret_flags) => {
-                            secret_flags::process_ws2p_v2p_secret_flags_msg(
-                                controller,
-                                secret_flags,
-                            )
-                        }
-                        WS2Pv2MessagePayload::Ok(_) => {
-                            // Process ok message
-                            ok_msg::process_ws2p_v2p_ok_msg(controller)
-                        }
-                        WS2Pv2MessagePayload::Ko(_) => Ok(close_with_reason(
-                            "Receive Ko message !",
-                            WS2PConnectionState::Denial,
-                        )),
-                        _ => {
-                            if let WS2PConnectionState::Established = controller.meta_datas.state {
-                                controller
-                                    .send_event(WS2PControllerEvent::RecvValidMsg {
-                                        ws2p_msg: valid_msg,
-                                    })
-                                    .map(|_| None)
-                            } else {
-                                Ok(close_with_reason(
-                                    "Receive datas message on negociation !",
+        match WS2PMessage::parse_and_check_incoming_bin_message(
+            &mut controller.secure_layer,
+            &bin_msg,
+        ) {
+            Ok((valid_msgs, remote_pubkey_opt)) => {
+                let mut orders: Vec<WebsocketActionOrder> = Vec::new();
+                for valid_msg in valid_msgs {
+                    match valid_msg {
+                        WS2PMessage::V2(ref msg_v2) => match msg_v2.payload {
+                            WS2Pv2MessagePayload::Connect(ref box_connect_msg) => {
+                                let connect_msg = box_connect_msg.deref();
+
+                                // Get remote node id
+                                let remote_full_id = if let Some(remote_pubkey) = remote_pubkey_opt
+                                {
+                                    NodeFullId(msg_v2.issuer_node_id, remote_pubkey)
+                                } else {
+                                    return Err(
+                                        WS2PControllerProcessError::ReceiveConnectMsgWithoutPubKey,
+                                    );
+                                };
+
+                                // Process connect message
+                                orders.append(&mut connect_msg::process_ws2p_v2p_connect_msg(
+                                    controller,
+                                    remote_full_id,
+                                    connect_msg,
+                                )?);
+                            }
+                            WS2Pv2MessagePayload::Ack {
+                                challenge: ack_msg_challenge,
+                            } => {
+                                // Process ack message
+                                orders.append(&mut ack_msg::process_ws2p_v2p_ack_msg(
+                                    controller,
+                                    ack_msg_challenge,
+                                )?);
+                            }
+                            WS2Pv2MessagePayload::SecretFlags(ref secret_flags) => {
+                                // Process secret flags
+                                orders.append(
+                                    &mut secret_flags::process_ws2p_v2p_secret_flags_msg(
+                                        controller,
+                                        secret_flags,
+                                    )?,
+                                );
+                            }
+                            WS2Pv2MessagePayload::Ok(_) => {
+                                // Process ok message
+                                orders.append(&mut ok_msg::process_ws2p_v2p_ok_msg(controller)?);
+                            }
+                            WS2Pv2MessagePayload::Ko(_) => {
+                                return Ok(close_with_reason(
+                                    "Receive Ko message !",
                                     WS2PConnectionState::Denial,
                                 ))
                             }
-                        }
+                            // in other cases, connection state must be established
+                            _ => {
+                                if let WS2PConnectionState::Established =
+                                    controller.meta_datas.state
+                                {
+                                    controller.send_event(WS2PControllerEvent::RecvValidMsg {
+                                        ws2p_msg: valid_msg,
+                                    })?;
+                                } else {
+                                    return Ok(close_with_reason(
+                                        "Receive datas message on negociation !",
+                                        WS2PConnectionState::Denial,
+                                    ));
+                                }
+                            }
+                        },
+                        WS2PMessage::_V0 | WS2PMessage::_V1 => fatal_error!(
+                            "Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !"
+                        ),
                     }
                 }
-                WS2PMessage::_V0 | WS2PMessage::_V1 => {
-                    fatal_error!("Dev error: must not use WS2PMessage version < 2 in WS2Pv2+ !")
-                }
-            },
+                Ok(orders)
+            }
             Err(ws2p_msg_err) => {
                 log::warn!("Message is invalid : {:?}", ws2p_msg_err);
                 controller.meta_datas.count_invalid_msgs += 1;
@@ -134,7 +158,7 @@ pub fn process<M: ModuleMessage>(
                         WS2PConnectionState::Denial,
                     ))
                 } else {
-                    Ok(None)
+                    Ok(vec![])
                 }
             }
         }
@@ -146,12 +170,12 @@ pub fn process<M: ModuleMessage>(
     }
 }
 
-fn close_with_reason(reason: &str, new_state: WS2PConnectionState) -> Option<WebsocketActionOrder> {
-    Some(WebsocketActionOrder {
+fn close_with_reason(reason: &str, new_state: WS2PConnectionState) -> Vec<WebsocketActionOrder> {
+    vec![WebsocketActionOrder {
         ws_action: WebsocketAction::CloseConnection {
             reason: Some(reason.to_owned()),
         },
         new_state_if_success: Some(new_state),
         new_state_if_fail: new_state,
-    })
+    }]
 }
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ack_msg.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ack_msg.rs
index c987782caa9b23418bca9d922ad993c217e824b7..6e21726a3d5e4c963caa2d055931a3efcb6afd13 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ack_msg.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ack_msg.rs
@@ -30,7 +30,7 @@ use log::error;
 pub fn process_ws2p_v2p_ack_msg<M: ModuleMessage>(
     controller: &mut WS2PController<M>, // controller contains original challenge
     ack_msg_challenge: Hash,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     log::debug!("Receive ACK message !");
 
     match controller.meta_datas.state {
@@ -59,13 +59,13 @@ fn process<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
     ack_msg_challenge: Hash,
     success_status: WS2PConnectionState,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     if controller.meta_datas.challenge != ack_msg_challenge {
         controller
             .update_conn_state(WS2PConnectionState::Denial)
-            .map(|_| None)
+            .map(|_| vec![])
     } else {
-        Ok(Some(send_ok_msg(controller, success_status)))
+        Ok(vec![send_ok_msg(controller, success_status)])
     }
 }
 
@@ -78,10 +78,10 @@ fn send_ok_msg<M: ModuleMessage>(
     let ok_msg = WS2Pv2OkMsg::default();
 
     // Encapsulate and binarize OK message
-    if let Ok((_, bin_ok_msg)) = WS2Pv2Message::encapsulate_payload(
-        controller.meta_datas.currency.clone(),
+    if let Ok(bin_ok_msg) = WS2Pv2Message::encapsulate_payload(
+        &mut controller.secure_layer,
+        controller.meta_datas.local_node.my_currency.clone(),
         controller.meta_datas.local_node.my_node_id,
-        &controller.meta_datas.signator,
         WS2Pv2MessagePayload::Ok(ok_msg),
     ) {
         // Order the sending of a OK message
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/connect_msg.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/connect_msg.rs
index e8f7ec69dcad3afe4f8dc66d00d24b0e8b86f011..2bafdb74f898c31c066a4fcf67e9695b5979b31b 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/connect_msg.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/connect_msg.rs
@@ -15,9 +15,10 @@
 
 //! Sub-module process reception of CONNECT message
 
+// use crate::orchestrator::OrchestratorMsg;
 use crate::connection_state::WS2PConnectionState;
 use crate::controller::meta_datas::Ws2pRemoteNodeDatas;
-use crate::controller::{WS2PController, WS2PControllerProcessError, WebsocketActionOrder};
+use crate::controller::*;
 use crate::websocket::{WebsocketAction, WebsocketMessage};
 use durs_common_tools::fatal_error;
 use durs_module::ModuleMessage;
@@ -26,14 +27,13 @@ use durs_ws2p_messages::v2::connect::{WS2Pv2ConnectMsg, WS2Pv2ConnectType};
 use durs_ws2p_messages::v2::payload_container::WS2Pv2MessagePayload;
 use durs_ws2p_messages::v2::WS2Pv2Message;
 use log::error;
-use unwrap::unwrap;
 
 /// Process WS2P v2+ CONNECT Message
 pub fn process_ws2p_v2p_connect_msg<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
     remote_full_id: NodeFullId,
     connect_msg: &WS2Pv2ConnectMsg,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     log::debug!("Receive CONNECT message !");
 
     // Get remote node datas
@@ -45,70 +45,95 @@ pub fn process_ws2p_v2p_connect_msg<M: ModuleMessage>(
         remote_full_id,
     };
 
+    // must be waiting for a connect message to process it
     if let WS2PConnectionState::WaitingConnectMsg = controller.meta_datas.state {
-        // Check remote node datas
-        if let WS2Pv2ConnectType::Incoming = controller.meta_datas.connect_type {
-            controller.meta_datas.remote_node = Some(remote_node_datas);
-            // Get remote_connect_type
-            controller.meta_datas.remote_connect_type = Some(WS2Pv2ConnectType::from_flags(
-                &connect_msg.flags_queries,
-                connect_msg.chunkstamp,
-            ));
-        } else {
-            let expected_full_id = unwrap!(controller.id.expected_remote_full_id());
-            if remote_full_id == expected_full_id {
+        // incoming or outgoing ?
+        match controller.direction {
+            // incoming connection : get info from connect message
+            WS2PConnectionDirection::Incoming => {
                 controller.meta_datas.remote_node = Some(remote_node_datas);
-            } else {
-                return Ok(super::close_with_reason(
-                    "Unexpected PUBKEY or NODE_ID !",
-                    WS2PConnectionState::Denial,
-                ));
-            }
-            // Flags not allowed from incoming node
-            if !connect_msg.flags_queries.is_empty() {
-                super::close_with_reason(
-                    "Unexpected CONNECT FLAGS from incoming node. !",
-                    WS2PConnectionState::Denial,
+                controller.node_id = Some(remote_full_id);
+                controller.conn_type = connect_msg.connect_type;
+                debug!(
+                    "controller connection type changed to {:?}",
+                    &connect_msg.connect_type
                 );
             }
-            // Get remote_connect_type
-            controller.meta_datas.remote_connect_type = Some(WS2Pv2ConnectType::Incoming);
+            // outgoing connection : depends on connect type
+            WS2PConnectionDirection::Outgoing => {
+                match controller.conn_type {
+                    // an outgoing connection can not have incoming connect type
+                    WS2Pv2ConnectType::Incoming => {
+                        unreachable!("dev error : outgoing connection has type incoming");
+                    }
+                    // client connect message
+                    WS2Pv2ConnectType::Client => {
+                        // TODO client "node id"
+                        // in order to manage client connection, we have to give them an ID
+                    }
+                    // server connect message
+                    WS2Pv2ConnectType::Server => {
+                        // before receiving connect message, the node id is the expected one
+                        if let Some(expected_full_id) = controller.node_id {
+                            // check that remote node id is what was expected
+                            if remote_full_id == expected_full_id {
+                                controller.meta_datas.remote_node = Some(remote_node_datas);
+                            } else {
+                                return Ok(super::close_with_reason(
+                                    "Unexpected PUBKEY or NODE_ID !",
+                                    WS2PConnectionState::Denial,
+                                ));
+                            }
+                        } else {
+                            unreachable!("and outgoing server connection expects a node id")
+                        }
+                    }
+                    // received connect message from the node we want to sync with
+                    // we have to trust him on the data he gives
+                    WS2Pv2ConnectType::Sync { .. } => {
+                        controller.meta_datas.remote_node = Some(remote_node_datas);
+                        controller.node_id = Some(remote_full_id);
+                    }
+                    WS2Pv2ConnectType::SyncAskChunk(_) => unimplemented!(),
+                }
+            }
         }
-    } else {
-        super::close_with_reason("Unexpected CONNECT message !", WS2PConnectionState::Denial);
-    }
 
-    // Check features compatibility
-    match controller
-        .meta_datas
-        .local_node
-        .my_features
-        .check_features_compatibility(connect_msg.api_features)
-    {
-        Ok(merged_features) => controller.meta_datas.features = Some(merged_features),
-        Err(_) => {
-            super::close_with_reason("Unsupported features !", WS2PConnectionState::Denial);
+        // Check features compatibility
+        match controller
+            .meta_datas
+            .local_node
+            .my_features
+            .check_features_compatibility(connect_msg.api_features)
+        {
+            Ok(merged_features) => controller.meta_datas.features = Some(merged_features),
+            Err(_) => {
+                super::close_with_reason("Unsupported features !", WS2PConnectionState::Denial);
+            }
         }
-    }
 
-    // Encapsulate and binarize ACK message
-    if let Ok((_, bin_ack_msg)) = WS2Pv2Message::encapsulate_payload(
-        controller.meta_datas.currency.clone(),
-        controller.meta_datas.local_node.my_node_id,
-        &controller.meta_datas.signator,
-        WS2Pv2MessagePayload::Ack {
-            challenge: remote_challenge,
-        },
-    ) {
-        // Order the sending of a OK message
-        Ok(Some(WebsocketActionOrder {
-            ws_action: WebsocketAction::SendMessage {
-                msg: WebsocketMessage::Bin(bin_ack_msg),
+        // Encapsulate and binarize ACK message
+        if let Ok(bin_ack_msg) = WS2Pv2Message::encapsulate_payload(
+            &mut controller.secure_layer,
+            controller.meta_datas.local_node.my_currency.clone(),
+            controller.meta_datas.local_node.my_node_id,
+            WS2Pv2MessagePayload::Ack {
+                challenge: remote_challenge,
             },
-            new_state_if_success: Some(WS2PConnectionState::ConnectMessOk),
-            new_state_if_fail: WS2PConnectionState::Unreachable,
-        }))
+        ) {
+            // Order the sending of a ACK message
+            Ok(vec![WebsocketActionOrder {
+                ws_action: WebsocketAction::SendMessage {
+                    msg: WebsocketMessage::Bin(bin_ack_msg),
+                },
+                new_state_if_success: Some(WS2PConnectionState::ConnectMessOk),
+                new_state_if_fail: WS2PConnectionState::Unreachable,
+            }])
+        } else {
+            fatal_error!("Dev error: Fail to sign own ack message !")
+        }
     } else {
-        fatal_error!("Dev error: Fail to sign own ack message !")
+        super::close_with_reason("Unexpected CONNECT message !", WS2PConnectionState::Denial);
+        Ok(vec![])
     }
 }
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ok_msg.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ok_msg.rs
index 6dd2ee4aa8559d8cfb6b5591577a0698385f1be6..433fbdff572cc3ed4ca6d93e9851edf6596f4440 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ok_msg.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/ok_msg.rs
@@ -18,39 +18,33 @@
 use crate::connection_state::WS2PConnectionState;
 use crate::controller::{
     WS2PController, WS2PControllerEvent, WS2PControllerProcessError, WebsocketActionOrder,
-};
-use durs_common_tools::fatal_error;
+}; // WS2PControllerId
+   // use durs_common_tools::fatal_error;
 use durs_module::ModuleMessage;
-use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
-use log::error;
-use unwrap::unwrap;
+// use log::error;
+// use unwrap::unwrap;
 
 /// Process WS2P v2+ OK Message
 pub fn process_ws2p_v2p_ok_msg<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     log::debug!("Receive OK message !");
 
     match controller.meta_datas.state {
+        // ok message received first
         WS2PConnectionState::ConnectMessOk | WS2PConnectionState::SecretFlagsOkWaitingAckMsg => {
             controller.update_conn_state(WS2PConnectionState::OkMsgOkWaitingAckMsg)?;
-            Ok(None)
+            Ok(vec![])
         }
+        // already received ack msg -> connection is established
         WS2PConnectionState::AckMsgOk | WS2PConnectionState::SecretFlagsOk => {
             controller.meta_datas.state = WS2PConnectionState::Established;
+            let secure_layer = controller.secure_layer.try_clone()?;
             controller.send_event(WS2PControllerEvent::NewConnEstablished {
-                conn_type: if controller.meta_datas.connect_type != WS2Pv2ConnectType::Incoming {
-                    controller.meta_datas.connect_type
-                } else {
-                    unwrap!(controller.meta_datas.remote_connect_type)
-                },
-                remote_full_id: if let Some(ref remote_node) = controller.meta_datas.remote_node {
-                    remote_node.remote_full_id
-                } else {
-                    fatal_error!("remote_node must be valued in process_ws2p_v2p_ok_msg() !")
-                },
+                secure_layer,
+                sender: controller.self_sender.clone(),
             })?;
-            Ok(None)
+            Ok(vec![])
         }
         _ => Ok(super::close_with_reason(
             "Unexpected OK message !",
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/secret_flags.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/secret_flags.rs
index a85b5dc830703c24fb6371142bb1ae6c300e5cb7..a6d6b93f327705da9f41cf6e10a79530e7810e9b 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/secret_flags.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_message/secret_flags.rs
@@ -27,7 +27,7 @@ use durs_ws2p_messages::v2::secret_flags::WS2Pv2SecretFlagsMsg;
 pub fn process_ws2p_v2p_secret_flags_msg<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
     secret_flags: &WS2Pv2SecretFlagsMsg,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     // SECRET_FLAGS informations must never be logged in prod
     #[cfg(test)]
     log::debug!("Receive SECRET_FLAGS message !");
@@ -38,9 +38,9 @@ pub fn process_ws2p_v2p_secret_flags_msg<M: ModuleMessage>(
             secret_flags,
             WS2PConnectionState::SecretFlagsOkWaitingAckMsg,
         )
-        .map(|_| None),
+        .map(|_| vec![]),
         WS2PConnectionState::AckMsgOk => {
-            process(controller, secret_flags, WS2PConnectionState::SecretFlagsOk).map(|_| None)
+            process(controller, secret_flags, WS2PConnectionState::SecretFlagsOk).map(|_| vec![])
         }
         _ => Ok(super::close_with_reason(
             "Unexpected SECRET_FLAGS message !",
@@ -54,6 +54,6 @@ fn process<M: ModuleMessage>(
     _secret_flags: &WS2Pv2SecretFlagsMsg,
     success_state: WS2PConnectionState,
 ) -> Result<(), WS2PControllerProcessError> {
-    // TODO .. traitement des secrets flags
+    // TODO HUGO .. traitement des secrets flags
     controller.update_conn_state(success_state)
 }
diff --git a/lib/modules/ws2p/ws2p-protocol/src/controller/on_open.rs b/lib/modules/ws2p/ws2p-protocol/src/controller/on_open.rs
index ca8c4d21dfb85194a58ebf675bf657a8f9336642..8802160c79ed6964cb105a0c2a2ffbff30b25983 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/controller/on_open.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/controller/on_open.rs
@@ -30,7 +30,7 @@ use std::net::SocketAddr;
 pub fn process<M: ModuleMessage>(
     controller: &mut WS2PController<M>,
     remote_addr_opt: Option<SocketAddr>,
-) -> Result<Option<WebsocketActionOrder>, WS2PControllerProcessError> {
+) -> Result<Vec<WebsocketActionOrder>, WS2PControllerProcessError> {
     log::debug!("open websocket from {}", print_opt_addr(remote_addr_opt));
 
     // Update connection state
@@ -38,27 +38,27 @@ pub fn process<M: ModuleMessage>(
 
     // Generate connect message
     let connect_msg = generate_connect_message(
-        controller.meta_datas.connect_type,
+        controller.conn_type,
         controller.meta_datas.local_node.my_features,
         controller.meta_datas.challenge,
-        None,
+        controller.meta_datas.local_node.my_peer.clone(),
     );
 
     // Encapsulate and binarize connect message
-    if let Ok((_ws2p_full_msg, bin_connect_msg)) = WS2Pv2Message::encapsulate_payload(
-        controller.meta_datas.currency.clone(),
+    if let Ok(bin_connect_msg) = WS2Pv2Message::encapsulate_payload(
+        &mut controller.secure_layer,
+        controller.meta_datas.local_node.my_currency.clone(),
         controller.meta_datas.local_node.my_node_id,
-        &controller.meta_datas.signator,
         WS2Pv2MessagePayload::Connect(Box::new(connect_msg)),
     ) {
         // Order the sending of a CONNECT message
-        Ok(Some(WebsocketActionOrder {
+        Ok(vec![WebsocketActionOrder {
             ws_action: WebsocketAction::SendMessage {
                 msg: WebsocketMessage::Bin(bin_connect_msg),
             },
             new_state_if_success: Some(WS2PConnectionState::WaitingConnectMsg),
             new_state_if_fail: WS2PConnectionState::Unreachable,
-        }))
+        }])
     } else {
         fatal_error!("Dev error: Fail to sign own connect message !")
     }
diff --git a/lib/modules/ws2p/ws2p/src/services/mod.rs b/lib/modules/ws2p/ws2p-protocol/src/errors.rs
similarity index 57%
rename from lib/modules/ws2p/ws2p/src/services/mod.rs
rename to lib/modules/ws2p/ws2p-protocol/src/errors.rs
index cbd379be523f0b6cdabeb1b0bf58e9335648d979..f8f7c394391f424a2f593286ba4c63706d73da58 100644
--- a/lib/modules/ws2p/ws2p/src/services/mod.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/errors.rs
@@ -13,28 +13,19 @@
 // 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 Services
+//! error management
 
-use dup_crypto::keys::KeyPairEnum;
-use durs_network_documents::*;
-use durs_ws2p_messages::v2::api_features::WS2PFeatures;
+use failure::Fail;
 
-pub mod outgoing;
-
-/// Websocket Error
-#[derive(Debug, Copy, Clone)]
-pub enum WsError {
-    /// Unknown error
-    UnknownError,
+/// WS2P protocol errors
+#[derive(Copy, Clone, Debug, Fail)]
+pub enum WS2PProtocolError {
+    /// can not contact controller
+    #[fail(display = "WS2P: can not contact controller")]
+    ContactError, // sans paramètre
+    /// security error
+    #[fail(display = "security error, can not encapsulate payload")]
+    SecurityError, // pkstl error
 }
 
-/// Store self WS2P properties
-#[derive(Debug, Clone, PartialEq)]
-pub struct MySelfWs2pNode {
-    /// Local node id
-    pub my_node_id: NodeId,
-    /// Local network keypair
-    pub my_key_pair: KeyPairEnum,
-    /// Local node WWS2PFeatures
-    pub my_features: WS2PFeatures,
-}
+// TODO continue error management
diff --git a/lib/modules/ws2p/ws2p-protocol/src/events.rs b/lib/modules/ws2p/ws2p-protocol/src/events.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fafebcca3530f8602fbcccd4a816faf5b85e41b6
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/events.rs
@@ -0,0 +1,59 @@
+//  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/>.
+
+//! process events from other modules
+
+use crate::constants::CHUNK_SIZE;
+use crate::orchestrator::WS2POrchestrator;
+use dubp_block_doc::BlockDocument;
+use dubp_common_doc::Blockstamp;
+use durs_common_tools::fatal_error;
+use durs_message::events::*;
+
+impl WS2POrchestrator {
+    /// process event from other module
+    pub fn process_event(self: &mut Self, event_content: DursEvent) {
+        match event_content {
+            DursEvent::BlockchainEvent(blockchain_event) => {
+                match *blockchain_event {
+                    // new block has been added to local blockchain
+                    BlockchainEvent::StackUpValidBlock(block) => {
+                        match *block {
+                            BlockDocument::V10(block) => {
+                                // updates current blockstamp
+                                self.current_blockstamp = Some(Blockstamp {
+                                    id: block.number,
+                                    hash: block.hash.unwrap(),
+                                });
+                                // updates current milestones if necessary
+                                if block.number.0 % CHUNK_SIZE == CHUNK_SIZE - 1 {
+                                    if let Some(milestones) = &mut self.milestones {
+                                        milestones.push(block.hash.unwrap());
+                                    } else {
+                                        fatal_error!(
+                                            "can not stack block while milestones are not set"
+                                        );
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    _ => warn!("received unexpected blockchain event"),
+                }
+            }
+            _ => warn!("received unexpected event"),
+        }
+    }
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/lib.rs b/lib/modules/ws2p/ws2p-protocol/src/lib.rs
index 2f0a93997480e358fabb57d270ea3a3d9b83a3bc..859d7f22fabb3ad0c5d14e734320e44869dbd52b 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/lib.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/lib.rs
@@ -28,13 +28,21 @@
     unused_qualifications
 )]
 
+#[macro_use]
+extern crate log;
+
 pub mod connection_state;
 pub mod constants;
 pub mod controller;
+pub mod errors;
+pub mod events;
 pub mod orchestrator;
+pub mod responses;
 pub mod websocket;
 
+use dubp_common_doc::CurrencyName;
 use dup_crypto::keys::{KeyPair, KeyPairEnum};
+use durs_network_documents::network_peer::PeerCard;
 use durs_network_documents::{NodeFullId, NodeId};
 use durs_ws2p_messages::v2::api_features::WS2PFeatures;
 
@@ -45,8 +53,12 @@ pub struct MySelfWs2pNode {
     pub my_node_id: NodeId,
     /// Local network keypair
     pub my_key_pair: KeyPairEnum,
-    /// Local node WWS2PFeatures
+    /// Local node WS2PFeatures
     pub my_features: WS2PFeatures,
+    /// Local peer card
+    pub my_peer: PeerCard,
+    /// Currency
+    pub my_currency: CurrencyName,
 }
 
 impl MySelfWs2pNode {
@@ -54,4 +66,20 @@ impl MySelfWs2pNode {
     pub fn get_full_id(&self) -> NodeFullId {
         NodeFullId(self.my_node_id, self.my_key_pair.public_key())
     }
+    /// constructor
+    pub fn new(
+        node_id: NodeId,
+        key_pair: KeyPairEnum,
+        features: WS2PFeatures,
+        self_peer_card: PeerCard,
+        currency: CurrencyName,
+    ) -> Self {
+        MySelfWs2pNode {
+            my_node_id: node_id,
+            my_key_pair: key_pair,
+            my_features: features,
+            my_peer: self_peer_card,
+            my_currency: currency,
+        }
+    }
 }
diff --git a/lib/modules/ws2p/ws2p-protocol/src/orchestrator.rs b/lib/modules/ws2p/ws2p-protocol/src/orchestrator.rs
deleted file mode 100644
index 0c084c0b214c909ae2e19801a8ca02c6bfee52fe..0000000000000000000000000000000000000000
--- a/lib/modules/ws2p/ws2p-protocol/src/orchestrator.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
-//
-// 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 V2+ API Protocol.
-//! Orchestrator manage WS2P Node.
-
-use std::sync::mpsc::Sender;
-
-use crate::controller::{WS2PControllerEvent, WS2PControllerId, WebsocketActionOrder};
-use durs_module::ModuleMessage;
-
-/// Orchestrator message
-#[derive(Debug)]
-pub enum OrchestratorMsg<M: ModuleMessage> {
-    /// Controller sender
-    ControllerSender(Sender<WebsocketActionOrder>),
-    /// Controller event
-    ControllerEvent {
-        /// Controller unique identifier
-        controller_id: WS2PControllerId,
-        /// Controller event
-        event: WS2PControllerEvent,
-    },
-    /// Module message
-    ModuleMessage(M),
-}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/orchestrator/event.rs b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/event.rs
new file mode 100644
index 0000000000000000000000000000000000000000..51ff2cab127e725eb331682286d1ab1cef78fd68
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/event.rs
@@ -0,0 +1,284 @@
+//  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/>.
+
+//! orchestrator controller events
+
+// use pkstl::SecureLayer;
+use crate::connection_state::WS2PConnectionState;
+use crate::constants::CHUNK_SIZE;
+use crate::controller::*;
+use crate::orchestrator::service::ServiceMsg;
+use crate::orchestrator::WS2POrchestrator; // IncomingConnection
+use durs_common_tools::fatal_error;
+use durs_message::events::DursEvent;
+use durs_message::DursMsg;
+use durs_module::*;
+use durs_network::events::NetworkEvent;
+use durs_network_documents::network_peer::*;
+use durs_network_documents::NodeFullId;
+use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
+use durs_ws2p_messages::v2::payload_container::WS2Pv2MessagePayload;
+use durs_ws2p_messages::v2::sync_info::WS2Pv2SyncInfo;
+use durs_ws2p_messages::WS2PMessage;
+
+/// process controllers events
+pub fn process_controller_event(
+    ws2p_orchestrator: &mut WS2POrchestrator,
+    controller_id: WS2PControllerId,
+    event: WS2PControllerEvent,
+) {
+    match event {
+        WS2PControllerEvent::NewConnEstablished {
+            mut secure_layer,
+            sender,
+        } => {
+            info!("New connection established : {:?}", &controller_id);
+            match controller_id.conn_type {
+                // when connection is established, it can not be incoming anymore
+                WS2Pv2ConnectType::Incoming => {
+                    unreachable!("dev error : established connection can not be incoming anymore");
+                }
+
+                // new client connection
+                WS2Pv2ConnectType::Client => {
+                    match controller_id.direction {
+                        // new client connecting to the node
+                        WS2PConnectionDirection::Incoming => {
+                            if ws2p_orchestrator
+                                .service_sender
+                                .send(ServiceMsg::NewEstablishedConnection {
+                                    controller_id,
+                                    secure_layer,
+                                    sender,
+                                })
+                                .is_ok()
+                            {
+                                debug!("new client connected, forwarding to connection manager");
+                            } else {
+                                error!("can not join connection manager");
+                            }
+                        }
+                        // theoretically impossible
+                        WS2PConnectionDirection::Outgoing => {
+                            unreachable!("dev error, I am not a client !")
+                        }
+                    }
+                }
+
+                // new server connection, forward it to connection manager
+                WS2Pv2ConnectType::Server => {
+                    if ws2p_orchestrator
+                        .service_sender
+                        .send(ServiceMsg::NewEstablishedConnection {
+                            controller_id,
+                            secure_layer,
+                            sender,
+                        })
+                        .is_ok()
+                    {
+                        debug!("new server connected, forwarding to connection manager");
+                    } else {
+                        error!("can not join connection manager");
+                    }
+                }
+
+                // new sync connection
+                // transfer SyncInfo to the connection manager, it will decide wether to send chunks or not himself
+                WS2Pv2ConnectType::Sync {
+                    from_blockstamp: _from_blockstamp,
+                } => {
+                    match controller_id.direction {
+                        // new incoming sync connection : reply to it with sync info
+                        WS2PConnectionDirection::Incoming => {
+                            // forward to connection manager
+                            if ws2p_orchestrator
+                                .service_sender
+                                .send(ServiceMsg::NewEstablishedConnection {
+                                    controller_id,
+                                    secure_layer: secure_layer.try_clone().expect(""),
+                                    sender,
+                                })
+                                .is_ok()
+                            {
+                                debug!(
+                                "new incoming sync connection, forwarding to connection manager"
+                            );
+                            } else {
+                                error!("can not join connection manager");
+                            };
+                            // get sync info (blockstamp and milestones) and gives it to connection manager
+                            if let Some(target_blockstamp) = ws2p_orchestrator.current_blockstamp {
+                                if let Some(milestones) = &ws2p_orchestrator.milestones {
+                                    let peer_cards = ws2p_orchestrator
+                                        .peers
+                                        .values()
+                                        .cloned()
+                                        .collect::<Vec<PeerCard>>();
+                                    let sync_info = WS2Pv2SyncInfo {
+                                        chunk_size: *CHUNK_SIZE,
+                                        target_blockstamp,
+                                        milestones: milestones.to_vec(), // OPTI TODO only send necessary milestones according to _from_blockstamp
+                                        peer_cards,
+                                    };
+                                    // send sync info to connection manager
+                                    if ws2p_orchestrator
+                                        .service_sender
+                                        .send(ServiceMsg::SendSyncInfo {
+                                            controller_id,
+                                            sync_info,
+                                        })
+                                        .is_ok()
+                                    {
+                                        debug!("forwarding sync info to connection manager");
+                                    } else {
+                                        error!("can not join connection manager");
+                                    };
+                                } else {
+                                    fatal_error!(
+                                        "can not send sync info if milestones are not known"
+                                    );
+                                }
+                            } else {
+                                fatal_error!(
+                                    "can not send sync info if current blockstamp is not known"
+                                );
+                            }
+                        }
+                        // new sync connection to target node, waiting for sync info
+                        WS2PConnectionDirection::Outgoing => {
+                            debug!("A new outgoing sync connection has been established. Waiting for SyncInfo");
+                        }
+                    }
+                }
+
+                // new sync ask chunk connection
+                WS2Pv2ConnectType::SyncAskChunk(_blockstamp) => {
+                    match controller_id.direction {
+                        // new incoming sync ask chunk connection
+                        WS2PConnectionDirection::Incoming => {
+                            // === TODO reply with chunks or relay to another node if not wanting to
+                            if ws2p_orchestrator
+                                .service_sender
+                                .send(ServiceMsg::NewEstablishedConnection {
+                                    controller_id,
+                                    secure_layer,
+                                    sender,
+                                })
+                                .is_ok()
+                            {
+                                debug!("new incoming asking chunk connection, forwarding to connection manager");
+                            } else {
+                                error!("can not join connection manager");
+                            };
+                        }
+                        // established sync ask chunk connection, waiting for chunks
+                        WS2PConnectionDirection::Outgoing => {}
+                    }
+                }
+            }
+        }
+        WS2PControllerEvent::RecvValidMsg { ws2p_msg } => match ws2p_msg {
+            WS2PMessage::V2(ws2p_msg) => match ws2p_msg.payload {
+                WS2Pv2MessagePayload::Connect(_)
+                | WS2Pv2MessagePayload::Ack { .. }
+                | WS2Pv2MessagePayload::Ok(_) => {
+                    fatal_error!("dev error : not supposed to process message at this level");
+                }
+                WS2Pv2MessagePayload::SyncInfo(sync_info) => {
+                    let target_blockstamp = sync_info.target_blockstamp;
+                    // load sync info data
+                    assert_eq!(sync_info.chunk_size, *CHUNK_SIZE); // FIXME manage case where chunk size is not the same
+                    ws2p_orchestrator.target_blockstamp = Some(target_blockstamp.clone());
+                    // TODO? check that milestones correspond to what is expected (number of each chunk's last block) before adding them to the orchestrator
+                    ws2p_orchestrator.milestones = Some(sync_info.milestones);
+                    // insert peer cards in orchestrator hashmap
+                    for peer_card in sync_info.peer_cards {
+                        match peer_card {
+                            PeerCard::V10(_) => unimplemented!(),
+                            PeerCard::V11(peer_card) => {
+                                ws2p_orchestrator.peers.insert(
+                                    NodeFullId(peer_card.node_id, peer_card.issuer),
+                                    PeerCard::V11(peer_card),
+                                );
+                            }
+                        }
+                    }
+                    // ===TODO order wave of SyncAskChunk connections on collected peercards
+
+                    // // transfer sync target to blockchain module TODO FIXME uncomment when ready
+                    // if ws2p_orchestrator
+                    //     .router_sender
+                    //     .send(RouterThreadMessage::ModuleMessage(DursMsg::Event {
+                    //         event_from: ws2p_orchestrator.static_name,
+                    //         event_type: ModuleEvent::GotTargetBlockstamp,
+                    //         event_content: DursEvent::NetworkEvent(NetworkEvent::ReceivedTargetBlockstamp(
+                    //             target_blockstamp,
+                    //         )),
+                    //     }))
+                    //     .is_ok()
+                    // {
+                    //     debug!("sent chunk to blockchain module");
+                    // } else {
+                    //     warn!("could not contact router");
+                    // }
+                }
+                WS2Pv2MessagePayload::Blocks(chunk) => {
+                    // send chunks to blockchain module
+                    if ws2p_orchestrator
+                        .router_sender
+                        .send(RouterThreadMessage::ModuleMessage(DursMsg::Event {
+                            event_from: ws2p_orchestrator.static_name,
+                            event_type: ModuleEvent::NewBlockFromNetwork,
+                            event_content: DursEvent::NetworkEvent(NetworkEvent::ReceiveBlocks(
+                                chunk,
+                            )),
+                        }))
+                        .is_ok()
+                    {
+                        debug!("sent chunk to blockchain module");
+                    } else {
+                        error!("could not contact router");
+                    }
+                }
+                _ => unimplemented!(),
+            },
+            _ => {
+                unimplemented!();
+            }
+        },
+        WS2PControllerEvent::StateChange { new_state } => match new_state {
+            WS2PConnectionState::NeverTry
+            | WS2PConnectionState::TryToOpenWS
+            | WS2PConnectionState::TryToSendConnectMsg
+            | WS2PConnectionState::WaitingConnectMsg
+            | WS2PConnectionState::ConnectMessOk
+            | WS2PConnectionState::OkMsgOkWaitingAckMsg
+            | WS2PConnectionState::AckMsgOk
+            | WS2PConnectionState::SecretFlagsOkWaitingAckMsg
+            | WS2PConnectionState::SecretFlagsOk
+            | WS2PConnectionState::Established => {
+                debug!("controller state changed to {:?}, that's nice", new_state);
+            }
+            WS2PConnectionState::WSError
+            | WS2PConnectionState::Denial
+            | WS2PConnectionState::Close
+            | WS2PConnectionState::NoResponse
+            | WS2PConnectionState::NegociationTimeout
+            | WS2PConnectionState::Unreachable => {
+                error!("controller state changed to {:?}, that's bad", new_state);
+            } // TODO FIXME handle these cases
+        },
+    }
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/orchestrator/mod.rs b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..814270a9af2a001ba0ed077c9b5dc889b0eb6ce2
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/mod.rs
@@ -0,0 +1,272 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// 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 V2+ API Protocol.
+//! Orchestrator manage WS2P Node.
+
+pub mod event;
+pub mod service;
+
+use crate::constants::CHUNK_SIZE;
+use crate::controller::WebsocketActionOrder;
+use crate::controller::{WS2PControllerEvent, WS2PControllerId};
+use crate::orchestrator::service::ServiceMsg;
+pub use crate::orchestrator::service::WS2PConnectionManager;
+use crate::websocket::WebsocketTrait;
+use crate::MySelfWs2pNode;
+use dubp_common_doc::BlockHash;
+use dubp_common_doc::Blockstamp;
+use dup_crypto::keys::{KeyPair, SignatorEnum};
+use durs_bc_db_reader;
+use durs_common_tools::fatal_error;
+use durs_conf::DuRsConf;
+use durs_message::DursMsg;
+use durs_module::ModuleMessage;
+use durs_module::*;
+use durs_network_documents::network_head::NetworkHead;
+use durs_network_documents::network_peer::*;
+use durs_network_documents::NodeFullId;
+use std::collections::HashMap;
+use std::sync::mpsc;
+use std::thread;
+
+/// Orchestrator message
+/// orchestrator messages can be a message from an other module or a message from a controller
+/// some will be forwarded to connection manager
+#[derive(Debug)]
+pub enum OrchestratorMsg<M: ModuleMessage> {
+    /// Controller event (NewConnEstablished, StateChange, RecvValidMsg)
+    ControllerEvent {
+        /// Controller unique identifier
+        controller_id: WS2PControllerId,
+        /// Controller event
+        event: WS2PControllerEvent,
+    },
+    /// Anonymous controller event (StateChange)
+    AnonControllerEvent {
+        /// event
+        event: WS2PControllerEvent,
+        /// sender (used if connection should be closed for example)
+        sender: mpsc::Sender<WebsocketActionOrder>,
+    },
+    /// Module message
+    ModuleMessage(M),
+}
+
+/// Main orchestrator
+/// Receives messages from other modules and transfer relevant info to connection manager
+/// Regularly asks for a new wave of outgoing connections to connection manager
+#[derive(Debug)]
+pub struct WS2POrchestrator {
+    /// module static name used for router requests
+    static_name: ModuleStaticName,
+    /// current blockstamp
+    pub current_blockstamp: Option<Blockstamp>,
+    /// target blockstamp used in sync mode
+    pub target_blockstamp: Option<Blockstamp>,
+    /// milestones: hash of the last block of each chunk
+    pub milestones: Option<Vec<BlockHash>>,
+    /// List of known peers
+    pub peers: HashMap<NodeFullId, PeerCard>,
+    /// List of known current blockstamps of other nodes
+    heads: HashMap<NodeFullId, NetworkHead>,
+    /// the channel used to send message to router
+    pub router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+    /// the channel used to send message to orchestrator itself
+    pub sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    /// Self signator
+    self_signator: SignatorEnum,
+    /// the channel used to receive message
+    receiver: mpsc::Receiver<OrchestratorMsg<DursMsg>>,
+    /// channel used to give orders to service orchestrator
+    pub service_sender: mpsc::Sender<ServiceMsg>,
+}
+
+impl WS2POrchestrator {
+    /// Constructor which returns orchestrator and connection manager
+    /// proxy channels are defined here and shared between both
+    pub fn new_pair<WS: 'static + WebsocketTrait>(
+        static_name: ModuleStaticName,
+        router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+        module_receiver: mpsc::Receiver<DursMsg>,
+        self_node: MySelfWs2pNode,
+        self_signator: SignatorEnum,
+        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        sync_mode: bool,
+    ) -> (WS2PConnectionManager<WS>, WS2POrchestrator) {
+        // Create main orchestrator channel
+        let (orchestrator_sender, orchestrator_receiver): (
+            mpsc::Sender<OrchestratorMsg<DursMsg>>,
+            mpsc::Receiver<OrchestratorMsg<DursMsg>>,
+        ) = mpsc::channel();
+
+        // starts proxy thread
+        start_proxy_thread(orchestrator_sender.clone(), module_receiver);
+
+        // get current blockstamp and milestones
+        let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone());
+        let db = durs_bc_db_reader::open_db_ro(&db_path.as_path()).expect("can not open db");
+        let current_blockstamp = durs_bc_db_reader::current_meta_datas::get_current_blockstamp(&db)
+            .expect("can not read DB"); // TODO transfer to fatal_error
+        debug!("current blockstamp read in db : {:?}", &current_blockstamp);
+        // only try to read milestones in start mode
+        let milestones = match sync_mode {
+            true => None,
+            false => {
+                match durs_bc_db_reader::current_meta_datas::get_milestones(&db, *CHUNK_SIZE) {
+                    Ok(milestones) => Some(milestones),
+                    Err(e) => {
+                        fatal_error!("error happened when trying to read milestones: {:?}", e)
+                    }
+                }
+            }
+        };
+
+        // Start ws server
+        // TODO ESZ
+
+        // Regenerate self_signator for main orchestrator
+        let self_signator_2 = self_node
+            .my_key_pair
+            .generate_signator()
+            .expect("corrupted keypair"); // TODO handle error
+
+        // MANAGER: initialize the connection manager
+        let connection_manager = WS2PConnectionManager::<WS>::new(
+            static_name,
+            current_blockstamp,
+            db,
+            self_node,
+            self_signator,
+            router_sender.clone(),
+            orchestrator_sender.clone(),
+        );
+
+        // ORCHESTRATOR: initialize the main orchestrator
+        let orchestrator = WS2POrchestrator {
+            static_name,
+            current_blockstamp,
+            target_blockstamp: None,
+            milestones,
+            peers: HashMap::new(),
+            heads: HashMap::new(),
+            router_sender,
+            sender: orchestrator_sender,
+            self_signator: self_signator_2,
+            receiver: orchestrator_receiver,
+            service_sender: connection_manager.self_sender.clone(),
+        };
+
+        (connection_manager, orchestrator)
+    }
+    /// main loop of orchestrator
+    pub fn main_loop(&mut self) -> Result<(), failure::Error> {
+        loop {
+            // could replace with while/let if no error management is needed
+            if let Ok(msg) = self.receiver.recv() {
+                match msg {
+                    OrchestratorMsg::ControllerEvent {
+                        controller_id,
+                        event,
+                    } => {
+                        event::process_controller_event(self, controller_id, event);
+                    }
+                    OrchestratorMsg::AnonControllerEvent { .. } => {
+                        // FIXME TODO process this event (example: order close of a connection of type limiting quota)
+                    }
+                    // messages from other modules wrapped into an orchestrator message
+                    OrchestratorMsg::ModuleMessage(mod_msg) => match mod_msg {
+                        DursMsg::Event {
+                            // event_from,
+                            // event_type,
+                            event_content,
+                            ..
+                        } => { self.process_event(event_content); }
+                        DursMsg::Request {
+                            // req_from,
+                            // req_to,
+                            // req_id,
+                            // req_content,
+                            ..
+                        } => {}
+                        DursMsg::Response {
+                            // res_from,
+                            // res_to,
+                            // req_id,
+                            res_content,
+                            ..
+                        } => {
+                            self.process_response(res_content);
+                        },
+                        DursMsg::Stop => {
+                            // TODO break;
+                        }
+                        DursMsg::SaveNewModuleConf(_, _) => {
+                            //
+                        }
+                        DursMsg::ModulesEndpoints(_) => {
+                            //
+                        }
+                    },
+                }
+            } else {
+                error!("could not receive message in orchestrator");
+            }
+
+            // TODO ask connection manager to perform the following
+            // new_wave_of_outgoing_connections
+            // regular_activities
+        }
+    }
+}
+
+// the proxy thread role is to convert DursMsg to OrchestratorMsg
+fn start_proxy_thread(
+    // sender for ws2p orchestrator
+    orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    // receiver of ws2p module
+    module_receiver: mpsc::Receiver<DursMsg>,
+) {
+    // Launch a proxy thread that transform DursMsg to OrchestratorMsg
+    thread::spawn(move || {
+        loop {
+            match module_receiver.recv() {
+                Ok(message) => {
+                    // stop flag
+                    let stop = if let DursMsg::Stop = message {
+                        true
+                    } else {
+                        false
+                    };
+                    // forward message to orchestrator
+                    match orchestrator_sender.send(OrchestratorMsg::ModuleMessage(message)) {
+                        Ok(_) => {
+                            if stop {
+                                break;
+                            };
+                        }
+                        Err(_) => debug!(
+                            "ws2p proxy : failed to relay DursMsg to Orchestrator main thread !"
+                        ),
+                    }
+                }
+                Err(e) => {
+                    warn!("{}", e);
+                    break;
+                }
+            }
+        }
+    });
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service.rs b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d594a4a48db5cddafe46f2a21ff3e7d391105c10
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service.rs
@@ -0,0 +1,306 @@
+//  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/>.
+
+//! WS2P outgoing Services
+//! the connection manager (service) manage controllers and execute orchestrator's order
+//! it also takes care of automatic actions like closing controllers, denying new connections, managing quotas...
+
+use crate::connection_state::WS2PConnectionState;
+use crate::constants::WS2P_DEFAULT_OUTCOMING_QUOTA;
+use crate::controller::WS2PControllerId;
+use crate::controller::WebsocketActionOrder;
+use crate::orchestrator::OrchestratorMsg;
+use crate::websocket::*;
+use crate::MySelfWs2pNode;
+use core::fmt::Debug;
+use dubp_common_doc::BlockNumber;
+use dubp_common_doc::Blockstamp;
+use dup_crypto::keys::SignatorEnum;
+use durs_bc_db_reader::*;
+use durs_message::*;
+use durs_module::*;
+use durs_network::cli::sync::SyncOpt;
+use durs_network_documents::network_endpoint::EndpointEnum;
+use durs_network_documents::{NodeFullId, NodeId};
+use durs_ws2p_messages::v2::sync_info::WS2Pv2SyncInfo;
+use failure::Error;
+use std::collections::HashMap;
+use std::net::ToSocketAddrs;
+use std::sync::mpsc;
+use std::thread;
+
+mod message;
+
+/// messages used to give orders or signals to outgoing orchestrator
+#[derive(Debug)]
+pub enum ServiceMsg {
+    /// current block changed
+    CurrentBlockstamp(Blockstamp),
+    /// send synchronization info
+    SendSyncInfo {
+        /// controller to send info to
+        controller_id: WS2PControllerId,
+        /// info to send
+        sync_info: WS2Pv2SyncInfo,
+    },
+    /// send chunk to node asking for chunk
+    SendNextChunk {
+        /// node id we should send chunk to
+        node_id: NodeFullId,
+        /// latest blockstamp of this node
+        /// None means he has no blockchain yet
+        /// Some(block_number) can be
+        /// - for the first chunk : the blocked asked by the node
+        /// - for an other chunk  : the milestone of previous chunk sent
+        /// should not be equal to current blockstamp, otherwise no chunk needed to be sent
+        latest: Option<BlockNumber>,
+    },
+    /// got new established connection
+    NewEstablishedConnection {
+        /// controller id
+        controller_id: WS2PControllerId,
+        /// secure layer
+        secure_layer: pkstl::SecureLayer,
+        /// controller sender
+        sender: mpsc::Sender<WebsocketActionOrder>,
+    },
+}
+
+/// Data allowing the service to manage a connection
+#[derive(Debug)]
+pub struct WS2PEstablishedConnection {
+    /// controller id
+    pub controller_id: WS2PControllerId,
+    /// controller sender
+    pub controller_sender: mpsc::Sender<WebsocketActionOrder>,
+    /// Endpoint
+    pub endpoint: Option<EndpointEnum>,
+    /// Connection Security Layer
+    pub secure_layer: Option<pkstl::SecureLayer>,
+}
+
+/// methods to ease connection manipulation
+impl WS2PEstablishedConnection {
+    // close connection
+    fn close(&self) {
+        if self
+            .controller_sender
+            .send(WebsocketActionOrder {
+                ws_action: WebsocketAction::CloseConnection {
+                    reason: Some("can not send chunks now".to_string()),
+                },
+                new_state_if_success: Some(WS2PConnectionState::Close),
+                new_state_if_fail: WS2PConnectionState::Close,
+            })
+            .is_err()
+        {
+            error!("can not contact controller");
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Endpoint whose last connection attempt failed
+pub struct EndpointInError {
+    /// Last attempt time
+    pub last_attempt_time: u64,
+    /// Error status
+    pub error: WS2PConnectionState,
+}
+
+#[derive(Debug)]
+/// connection management service
+/// manage all existing connections (quotas...)
+pub struct WS2PConnectionManager<WS: WebsocketTrait> {
+    /// websocket connector
+    websocket_connector: WS,
+    /// module static name used for router requests
+    static_name: ModuleStaticName,
+    /// current blockstamp
+    current_blockstamp: Option<Blockstamp>,
+    /// database reader
+    db: BcDbRo,
+    /// Local node datas
+    self_node: MySelfWs2pNode,
+    /// Self signator
+    self_signator: SignatorEnum,
+
+    /// Outgoing connections quota TODO refine by type
+    quota: usize,
+    /// Controller senders of not-yet-established connections of any type
+    /// they are kept here until timeout and are closed after
+    controller_pool: HashMap<WS2PControllerId, mpsc::Sender<WebsocketActionOrder>>,
+
+    // ===== established connections =====
+    /// List of established connections with other nodes
+    server_connections: HashMap<NodeFullId, WS2PEstablishedConnection>,
+    /// List of established incoming client connections
+    client_connections: HashMap<NodeFullId, WS2PEstablishedConnection>,
+    /// List of sync incoming connections
+    sync_connections: HashMap<NodeFullId, WS2PEstablishedConnection>,
+    /// List of sync ask chunk incoming connections
+    sync_ask_chunk_incoming_connections: HashMap<NodeFullId, WS2PEstablishedConnection>,
+    /// List of sync ask chunk outgoing connections
+    sync_ask_chunk_outgoing_connections: HashMap<NodeFullId, WS2PEstablishedConnection>,
+
+    // ===== endpoints =====
+    /// List of endpoints whose last connection attempt failed
+    endpoints_in_error: HashMap<NodeFullId, EndpointInError>,
+    /// List of endpoints that have never been contacted
+    never_try_endpoints: Vec<EndpointEnum>,
+
+    // ===== senders =====
+    /// router sender
+    router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+    /// Orchestrator sender used to send messages back to the main orchestrator
+    orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    /// Service self sender
+    pub self_sender: mpsc::Sender<ServiceMsg>,
+    /// Service receiver
+    receiver: mpsc::Receiver<ServiceMsg>,
+}
+
+impl<WS: 'static + WebsocketTrait> WS2PConnectionManager<WS> {
+    /// Instantiate WS2PConnectionManager
+    pub fn new(
+        static_name: ModuleStaticName,
+        current_blockstamp: Option<Blockstamp>,
+        db: BcDbRo,
+        self_node: MySelfWs2pNode,
+        self_signator: SignatorEnum,
+        router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+        orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    ) -> WS2PConnectionManager<WS> {
+        // Create service channel
+        let (self_sender, receiver): (mpsc::Sender<ServiceMsg>, mpsc::Receiver<ServiceMsg>) =
+            mpsc::channel();
+
+        WS2PConnectionManager {
+            // /!\ useless copy of self node, and orchestrator sender
+            websocket_connector: WS::new(self_node.clone(), orchestrator_sender.clone()),
+            static_name,
+            current_blockstamp,
+            db,
+            self_node,
+            self_signator,
+            quota: *WS2P_DEFAULT_OUTCOMING_QUOTA,
+            controller_pool: HashMap::new(),
+            server_connections: HashMap::with_capacity(*WS2P_DEFAULT_OUTCOMING_QUOTA),
+            client_connections: HashMap::with_capacity(*WS2P_DEFAULT_OUTCOMING_QUOTA),
+            sync_connections: HashMap::with_capacity(*WS2P_DEFAULT_OUTCOMING_QUOTA),
+            sync_ask_chunk_incoming_connections: HashMap::with_capacity(
+                *WS2P_DEFAULT_OUTCOMING_QUOTA,
+            ),
+            sync_ask_chunk_outgoing_connections: HashMap::with_capacity(
+                *WS2P_DEFAULT_OUTCOMING_QUOTA,
+            ),
+            endpoints_in_error: HashMap::new(),
+            never_try_endpoints: Vec::new(),
+            router_sender,
+            orchestrator_sender,
+            self_sender,
+            receiver,
+        }
+    }
+
+    /// main loop
+    ///
+    /// connection manager is in sync mode if sync options are provided
+    /// sync mode consists in :
+    /// - establishing a connection to a given node once current block is known
+    /// - processing messages sent by the main orchestrator
+    ///
+    /// start mode consists only in :
+    /// - processing messages sent by the main orchestrator
+    ///
+    pub fn main_loop(&mut self, sync_opt: Option<SyncOpt>) -> Result<(), Error> {
+        // clone variables requiring ownership
+        let websocket_connector = self.websocket_connector.clone();
+        let current_blockstamp = self.current_blockstamp.clone();
+
+        // spawn thread with listen or connect function
+        thread::spawn(move || {
+            // if in sync mode, connect to given URL, else listen on network
+            if let Some(sync_opt) = sync_opt {
+                // open connection of type sync
+                if websocket_connector
+                    .exec(WebsocketAction::ConnectToUrl {
+                        url: sync_opt.source.unwrap(),
+                        from_blockstamp: current_blockstamp,
+                    })
+                    .is_ok()
+                {
+                    info!("connection is over");
+                    Ok(())
+                } else {
+                    Err(WsError::ConnectError)
+                }
+            } else {
+                // start mode
+                // listen to incoming connections
+                listen_on(websocket_connector, "0.0.0.0:8000") // TODO find address in parameters
+            }
+        });
+
+        // main loop
+        loop {
+            // could replace with while/let if no error management is needed
+            // process messages from main orchestrator
+            if let Ok(msg) = self.receiver.recv() {
+                message::process(self, msg);
+            }
+        }
+
+        // // TODO start node depending on start option
+        // if sync_opt.start {
+        //     self.main_loop(None);
+        // }
+    }
+}
+
+/// Connect to WS2Pv2 Endpoint
+pub fn connect_to_endpoint<W: WebsocketTrait>(
+    endpoint: &EndpointEnum,
+    remote_node_id: Option<NodeId>,
+    websocket_connector: W,
+) -> Result<(), WsError> {
+    let expected_remote_full_id = if let Some(remote_node_id) = remote_node_id {
+        Some(NodeFullId(remote_node_id, endpoint.pubkey()))
+    } else {
+        None
+    };
+    match websocket_connector.exec(WebsocketAction::ConnectToEndpoint {
+        expected_remote_full_id,
+        endpoint: endpoint.clone(),
+    }) {
+        Ok(_) => Ok(()),
+        Err(_) => Err(WsError::UnknownError),
+    }
+}
+
+/// Listen on WS2Pv2 Endpoint
+pub fn listen_on<A, W>(websocket_connector: W, addr: A) -> Result<(), WsError>
+where
+    A: ToSocketAddrs + Debug + Copy,
+    W: WebsocketTrait,
+{
+    match websocket_connector.listen(addr) {
+        Ok(_) => Ok(()),
+        Err(_) => {
+            error!("can not listen on {:?}", addr);
+            Err(WsError::UnknownError)
+        }
+    }
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service/message.rs b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service/message.rs
new file mode 100644
index 0000000000000000000000000000000000000000..03905e566899c02795812d0ce04c088b08f706d9
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/orchestrator/service/message.rs
@@ -0,0 +1,303 @@
+//  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/>.
+
+//! process service message
+
+use crate::connection_state::WS2PConnectionState;
+use crate::constants::CHUNK_SIZE;
+use crate::controller::WS2PConnectionDirection;
+use crate::controller::WebsocketActionOrder;
+use crate::errors::WS2PProtocolError;
+use crate::orchestrator::service::*;
+use dubp_common_doc::BlockNumber;
+use dubp_currency_params::CurrencyName;
+use durs_common_tools::fatal_error;
+use durs_network_documents::NodeId;
+use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
+use durs_ws2p_messages::v2::payload_container::*;
+use durs_ws2p_messages::v2::*;
+use failure::Error;
+use std::cmp::Ordering;
+
+/// process ServiceMsg
+pub fn process<WS: WebsocketTrait>(
+    connection_manager: &mut WS2PConnectionManager<WS>,
+    msg: ServiceMsg,
+) {
+    match msg {
+        ServiceMsg::NewEstablishedConnection {
+            controller_id,
+            secure_layer,
+            sender: controller_sender,
+        } => {
+            debug!("received ServiceMsg::NewEstablishedConnection");
+            let new_conn = WS2PEstablishedConnection {
+                controller_id,
+                controller_sender,
+                endpoint: None, // === TODO find endpoint in endpoint list if exists
+                secure_layer: Some(secure_layer),
+            };
+            // store new connection in connection manager collections
+            match controller_id.conn_type {
+                // new established connection can not be incoming, since this state exists only before connect message
+                WS2Pv2ConnectType::Incoming => unreachable!(),
+                // client connections are necessarily incoming because this code is only for a server !
+                WS2Pv2ConnectType::Client => {
+                    connection_manager
+                        .client_connections
+                        .insert(controller_id.node_id, new_conn);
+                }
+                // server connections are symmetric
+                WS2Pv2ConnectType::Server => {
+                    connection_manager
+                        .server_connections
+                        .insert(controller_id.node_id, new_conn);
+                }
+                // sync connections are necessarily incoming in start mode and necessarily outgoing in sync mode
+                // (a node do not listen in sync mode)
+                WS2Pv2ConnectType::Sync { .. } => {
+                    connection_manager
+                        .sync_connections
+                        .insert(controller_id.node_id, new_conn);
+                }
+                // sync ask chunk can be incoming or outgoing
+                WS2Pv2ConnectType::SyncAskChunk(_chunkstamp) => match controller_id.direction {
+                    WS2PConnectionDirection::Incoming => {
+                        connection_manager
+                            .sync_ask_chunk_incoming_connections
+                            .insert(controller_id.node_id, new_conn);
+                    }
+                    WS2PConnectionDirection::Outgoing => {
+                        connection_manager
+                            .sync_ask_chunk_outgoing_connections
+                            .insert(controller_id.node_id, new_conn);
+                    }
+                },
+            }
+        }
+        // connection manager should forward the sync info to the good controller
+        ServiceMsg::SendSyncInfo {
+            controller_id,
+            sync_info,
+        } => {
+            debug!("received ServiceMsg::SendSyncInfo");
+            match connection_manager
+                .sync_connections
+                .get_mut(&controller_id.node_id)
+            {
+                Some(ref mut sync_incoming_connection) => {
+                    debug!("found corresponding sync incoming connection");
+                    if encapsulate_and_send_message(
+                        sync_incoming_connection,
+                        WS2Pv2MessagePayload::SyncInfo(sync_info),
+                        connection_manager.self_node.my_currency.clone(),
+                        connection_manager.self_node.my_node_id,
+                        None,
+                        WS2PConnectionState::Unreachable,
+                    )
+                    .is_ok()
+                    {
+                        debug!("sent SyncInfo to controller");
+                    }
+
+                    // decide wether to close the connection or to send the chunks TODO take quotas into account
+                    match should_i_send_chunks() {
+                        true => {
+                            if let WS2Pv2ConnectType::Sync { from_blockstamp } =
+                                controller_id.conn_type
+                            {
+                                if from_blockstamp == connection_manager.current_blockstamp {
+                                    // no need to send chunk
+                                    info!("closing complete sync connection");
+                                    sync_incoming_connection.close();
+                                    connection_manager
+                                        .sync_connections
+                                        .remove(&controller_id.node_id);
+                                } else {
+                                    // move connection to sync ask chunk store
+                                    let sync_incoming_connection = connection_manager
+                                        .sync_connections
+                                        .remove(&controller_id.node_id); // remove it from the hashmap and get it
+                                    if let Some(sync_incoming_connection) = sync_incoming_connection
+                                    {
+                                        // insert it in the other hashmap
+                                        connection_manager
+                                            .sync_ask_chunk_incoming_connections
+                                            .insert(
+                                                controller_id.node_id,
+                                                sync_incoming_connection,
+                                            );
+                                    } else {
+                                        fatal_error!("value was here but isn't anymore !");
+                                    };
+
+                                    // extract block number
+                                    let block_number = match from_blockstamp {
+                                        Some(blockstamp) => Some(blockstamp.id),
+                                        None => None,
+                                    };
+                                    if connection_manager
+                                        .self_sender
+                                        .send(ServiceMsg::SendNextChunk {
+                                            node_id: controller_id.node_id,
+                                            latest: block_number,
+                                        })
+                                        .is_err()
+                                    {
+                                        fatal_error!("I can not speak to myself!")
+                                    }
+                                }
+                            } else {
+                                fatal_error!("dev error: connect type must be Sync");
+                            }
+                        }
+                        false => {
+                            info!("closing connection due to too many connections");
+                            sync_incoming_connection.close();
+                        }
+                    }
+                }
+                None => {
+                    warn!("WS2P: no outgoing connection corresponding to this id, can not send sync info");
+                }
+            }
+        }
+        ServiceMsg::CurrentBlockstamp(blockstamp) => {
+            debug!("received ServiceMsg::CurrentBlockstamp");
+            connection_manager.current_blockstamp = Some(blockstamp);
+            // TODO bounce to other nodes
+        }
+        ServiceMsg::SendNextChunk { node_id, latest } => {
+            if let Some(mut sync_ask_chunk_incoming_connection) = connection_manager
+                .sync_ask_chunk_incoming_connections
+                .get_mut(&node_id)
+            {
+                // get current blockstamp
+                let current_blocknumber = match &connection_manager.current_blockstamp {
+                    Some(current_blockstamp) => current_blockstamp.id,
+                    None => fatal_error!("can not send chunk if current blockstamp is not known"),
+                };
+
+                // compute chunk limits
+                let (start, end) = match latest {
+                    Some(latest) => {
+                        // compute the milestone that is just after the latest block
+                        let milestone = latest.0 - latest.0 % *CHUNK_SIZE + *CHUNK_SIZE;
+                        match milestone.cmp(&current_blocknumber.0) {
+                            Ordering::Less => (latest.0, milestone),
+                            Ordering::Equal | Ordering::Greater => {
+                                (latest.0, current_blocknumber.0)
+                            }
+                        }
+                    }
+                    None => (0, *CHUNK_SIZE - 1), // first chunk
+                };
+
+                // get chunk from database between start and end
+                if let Ok(db_chunk) = blocks::get_blocks_between(
+                    &connection_manager.db,
+                    BlockNumber(start),
+                    BlockNumber(end),
+                ) {
+                    // collect blocks from it
+                    let mut chunk = Vec::new();
+                    for block in db_chunk {
+                        // OPTI TODO make sure db_chunk available is consumed (do not duplicate in memory)
+                        chunk.push(block.block);
+                    }
+                    // send chunk to controller
+                    if encapsulate_and_send_message(
+                        &mut sync_ask_chunk_incoming_connection,
+                        WS2Pv2MessagePayload::Blocks(chunk),
+                        connection_manager.self_node.my_currency.clone(),
+                        connection_manager.self_node.my_node_id,
+                        None,
+                        WS2PConnectionState::Unreachable,
+                    )
+                    .is_ok()
+                    {
+                        debug!("sent chunk to controller");
+                        if end < current_blocknumber.0 {
+                            if connection_manager
+                                .self_sender
+                                .send(ServiceMsg::SendNextChunk {
+                                    node_id: sync_ask_chunk_incoming_connection
+                                        .controller_id
+                                        .node_id,
+                                    latest: Some(BlockNumber(end)),
+                                })
+                                .is_err()
+                            {
+                                fatal_error!("I can not speak to myself!")
+                            }
+                        } else {
+                            info!("synchronisation completed, closing connection");
+                            sync_ask_chunk_incoming_connection.close();
+                            connection_manager
+                                .sync_ask_chunk_incoming_connections
+                                .remove(&node_id);
+                        }
+                    }
+                }
+            } else {
+                warn!("connection has been closed before sending chunk");
+            }
+        }
+    }
+}
+
+/// encapsulating a payload and sending it to a controller is a frequent operation in this file
+/// here is a function to do this
+fn encapsulate_and_send_message(
+    connection: &mut WS2PEstablishedConnection,
+    payload: WS2Pv2MessagePayload,
+    currency: CurrencyName,
+    node_id: NodeId,
+    new_state_if_success: Option<WS2PConnectionState>,
+    new_state_if_fail: WS2PConnectionState,
+) -> Result<(), Error> {
+    if let Ok(bin_connect_msg) = WS2Pv2Message::encapsulate_payload(
+        &mut connection.secure_layer.as_mut().expect("no secure layer"),
+        currency,
+        node_id,
+        payload,
+    ) {
+        if connection
+            .controller_sender
+            .send(WebsocketActionOrder {
+                ws_action: WebsocketAction::SendMessage {
+                    msg: WebsocketMessage::Bin(bin_connect_msg),
+                },
+                new_state_if_success,
+                new_state_if_fail,
+            })
+            .is_ok()
+        {
+            Ok(())
+        } else {
+            Err(WS2PProtocolError::ContactError.into())
+        }
+    } else {
+        // Security error, close the connection and ban pubkey and ip for a few minutes
+        connection.close();
+        // TODO ban pubkey and ip for a few minutes
+        Err(WS2PProtocolError::SecurityError.into())
+    }
+}
+
+/// placeholder function to know if chunks should be sent in a sync connection
+fn should_i_send_chunks() -> bool {
+    true
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/responses.rs b/lib/modules/ws2p/ws2p-protocol/src/responses.rs
new file mode 100644
index 0000000000000000000000000000000000000000..98e1021ebf6d88373f3dbcccb11c3b286743047b
--- /dev/null
+++ b/lib/modules/ws2p/ws2p-protocol/src/responses.rs
@@ -0,0 +1,33 @@
+//  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/>.
+
+//! process responses from other modules
+
+// use durs_common_tools::fatal_error;
+// use crate::orchestrator::outgoing::ServiceMsg;
+use crate::orchestrator::WS2POrchestrator;
+use durs_message::responses::*;
+
+impl WS2POrchestrator {
+    /// process response from other module
+    pub fn process_response(self: &mut Self, res_content: DursResContent) {
+        match res_content {
+            DursResContent::BlockchainResponse(_blockchain_response) => {}
+            _ => {
+                warn!("unexpected response");
+            }
+        }
+    }
+}
diff --git a/lib/modules/ws2p/ws2p-protocol/src/websocket.rs b/lib/modules/ws2p/ws2p-protocol/src/websocket.rs
index 164d808a1bf991389a31f443a4113e8f33df72c5..d61c56f9c4a82cb332c76215fd49fe6a12c02935 100644
--- a/lib/modules/ws2p/ws2p-protocol/src/websocket.rs
+++ b/lib/modules/ws2p/ws2p-protocol/src/websocket.rs
@@ -16,12 +16,24 @@
 //! WebSocketToPeer V2+ API Protocol.
 //! Define websocket message, event and action
 
+// use crate::controller::WebsocketActionOrder;
+use crate::orchestrator::OrchestratorMsg;
+use crate::MySelfWs2pNode;
+use dubp_common_doc::Blockstamp;
+use durs_message::DursMsg;
+use durs_network_documents::network_endpoint::EndpointEnum;
+use durs_network_documents::url::Url;
+use durs_network_documents::NodeFullId;
+use failure::Fail;
+use std::fmt::Debug;
 use std::net::SocketAddr;
+use std::net::ToSocketAddrs;
+use std::sync::mpsc;
 
 #[derive(Clone, Debug)]
 /// Websocket message
 pub enum WebsocketMessage {
-    /// Bnary message
+    /// Binary message
     Bin(Vec<u8>),
     /// String message
     Str(String),
@@ -53,13 +65,22 @@ pub enum WebsocketIncomingEvent {
 /// Websocket action
 pub enum WebsocketAction {
     /// Connect to websocket url
-    ConnectTo {
+    ConnectToUrl {
         /// Websocket url
-        url: String,
+        url: Url,
+        /// current blockstamp of the block if exists
+        from_blockstamp: Option<Blockstamp>,
+    },
+    /// Connect to endpoint
+    ConnectToEndpoint {
+        /// Expected id of remote node
+        expected_remote_full_id: Option<NodeFullId>,
+        /// Endpoint to connect to
+        endpoint: EndpointEnum,
     },
     /// Send message in websocket
     SendMessage {
-        /// message content
+        /// Message content
         msg: WebsocketMessage,
     },
     /// Close connection
@@ -68,3 +89,56 @@ pub enum WebsocketAction {
         reason: Option<String>,
     },
 }
+
+/// Websocket Error
+#[derive(Debug, Copy, Clone, Fail)]
+pub enum WsError {
+    /// Unknown error
+    #[fail(display = "WS: unknown error")]
+    UnknownError,
+    /// Connection error
+    #[fail(display = "WS: connect error")]
+    ConnectError,
+    /// Listen error
+    #[fail(display = "WS: listen error")]
+    ListenError,
+}
+
+/// the websocket trait is an abstraction allowing to move the logic of websocket handling
+/// in the protocol crate
+pub trait WebsocketTrait: Debug + Clone + Send {
+    /// constructor
+    fn new(
+        my_self_ws2p_node: MySelfWs2pNode,
+        orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    ) -> Self;
+    // /// connect to websocket endpoint
+    // fn connect(
+    //     &self,
+    //     expected_remote_full_id: Option<NodeFullId>,
+    //     endpoint: &EndpointEnum,
+    // ) -> Result<(), WsError>;
+    // /// connect to url
+    // fn connect_to_url(&self, ws_url: Url) -> Result<(), WsError>;
+
+    /// listen on the network
+    fn listen<A: ToSocketAddrs + Debug + Copy>(&self, addr: A) -> Result<(), WsError>;
+    // /// Execute a websocket action order. It can be a connect, listen, or message action
+    // /// also defines state change in case of success
+    // fn exec(&self, action_order: WebsocketActionOrder) -> Result<(), WsError>;
+    /// Execute websocket action without precising success or failure states
+    fn exec(&self, action: WebsocketAction) -> Result<(), WsError>;
+}
+
+// je n'ai pas bien compris la différence entre WebsocketAction et WebsocketActionOrder
+// donc j'ai fait deux fonctions exec de manière temporaire pour transformer l'un en l'autre
+
+// fn exec_action(&self, action: WebsocketAction) -> Result<(), WsError> {
+//     /// transform Action into ActionOrder
+//     let action_order = WebsocketActionOrder {
+//         ws_action: action,
+//         new_state_if_success: None,
+//         new_state_if_fail: WS2PConnectionState::Close,
+//     };
+//     self.exec(action_order)
+// }
diff --git a/lib/modules/ws2p/ws2p/Cargo.toml b/lib/modules/ws2p/ws2p/Cargo.toml
index 259f35319df918e0425c394765349ef10a9df232..a6ec2db8b9a871f96d06de2ba4baaf818b578515 100644
--- a/lib/modules/ws2p/ws2p/Cargo.toml
+++ b/lib/modules/ws2p/ws2p/Cargo.toml
@@ -34,6 +34,7 @@ ws = { version = "0.9.*", features = ["permessage-deflate"] }
 
 [dev-dependencies]
 durs-common-tests-tools = { path = "../../../tests-tools/common-tests-tools" }
+tempfile = "3.1.0"
 
 [features]
 ssl = ["ws/ssl"]
diff --git a/lib/modules/ws2p/ws2p/src/conf.rs b/lib/modules/ws2p/ws2p/src/conf.rs
new file mode 100644
index 0000000000000000000000000000000000000000..98d71a41ec912811bbc427e9c2c571c09e21a6d7
--- /dev/null
+++ b/lib/modules/ws2p/ws2p/src/conf.rs
@@ -0,0 +1,59 @@
+//  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/>.
+
+//! WS2P configuration
+
+use durs_common_tools::traits::merge::Merge;
+use durs_network_documents::network_endpoint::*;
+use durs_ws2p_protocol::constants;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// 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>,
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// WS2P Configuration
+pub struct WS2PUserConf {
+    /// Limit of outcoming connections
+    pub outcoming_quota: Option<usize>,
+    /// Default WS2P endpoints provides by configuration file
+    pub sync_endpoints: Option<Vec<EndpointEnum>>,
+}
+
+impl Merge for WS2PUserConf {
+    fn merge(self, other: Self) -> Self {
+        WS2PUserConf {
+            outcoming_quota: self.outcoming_quota.or(other.outcoming_quota),
+            sync_endpoints: self.sync_endpoints.or(other.sync_endpoints),
+        }
+    }
+}
+
+impl Default for WS2PConf {
+    fn default() -> Self {
+        WS2PConf {
+            outcoming_quota: *constants::WS2P_DEFAULT_OUTCOMING_QUOTA,
+            sync_endpoints: vec![
+                EndpointV2::parse_from_raw("WS2P 2 g1.durs.info 443 ws2p").unwrap(),
+                EndpointV2::parse_from_raw("WS2P 2 rs.g1.librelois.fr 443 ws2p").unwrap(),
+            ],
+        }
+    }
+}
diff --git a/lib/modules/ws2p/ws2p/src/constants.rs b/lib/modules/ws2p/ws2p/src/constants.rs
index 8422a00fc917cf95dcb27d56e80319231c19016a..90d419cce6550b2707cca386a2f0a3b1cf092276 100644
--- a/lib/modules/ws2p/ws2p/src/constants.rs
+++ b/lib/modules/ws2p/ws2p/src/constants.rs
@@ -15,15 +15,3 @@
 
 pub static API_NAME: &str = "WS2P";
 pub static MODULE_NAME: &str = "ws2p";
-
-pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &usize = &10;
-
-/*pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &u64 = &75;
-pub static WS2P_OUTCOMING_INTERVAL: &u64 = &300;*/
-pub static WS2P_RECV_SERVICE_FREQ_IN_MS: &u64 = &1_000;
-/*
-pub static WS2P_REQUEST_TIMEOUT: &u64 = &30_000;
-pub static DURATION_BEFORE_RECORDING_ENDPOINT: &u64 = &180;
-pub static BLOCKS_REQUEST_INTERVAL: &u64 = &60;
-pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &u64 = &40;
-*/
diff --git a/lib/modules/ws2p/ws2p/src/controllers/handler.rs b/lib/modules/ws2p/ws2p/src/controllers/handler.rs
index 4a5170f4037b9747fda03c79c9915e5a81bbe87a..eecf7702131ca30e42ab1cf0733208fefe18c7ee 100644
--- a/lib/modules/ws2p/ws2p/src/controllers/handler.rs
+++ b/lib/modules/ws2p/ws2p/src/controllers/handler.rs
@@ -15,11 +15,11 @@
 
 //! WS2P connection handler.
 
-use crate::constants;
 use crate::controllers::WsSender;
 use durs_common_tools::fatal_error;
 use durs_message::DursMsg;
 use durs_ws2p_protocol::connection_state::WS2PConnectionState;
+use durs_ws2p_protocol::constants::WS2P_RECV_SERVICE_FREQ_IN_MS;
 use durs_ws2p_protocol::controller::{WS2PController, WebsocketActionOrder};
 use durs_ws2p_protocol::websocket::{WebsocketAction, WebsocketIncomingEvent, WebsocketMessage};
 use std::net::SocketAddr;
@@ -29,7 +29,7 @@ use ws::{util::Token, CloseCode, Handler, Handshake, Message};
 const RECV_SERVICE: Token = Token(3);
 
 /// Our Handler struct.
-/// Here we explicity indicate that the Ws2pConnectionHandler needs a Sender,
+/// Here we explicitly indicate that the Ws2pConnectionHandler needs a Sender,
 /// whereas a closure captures the Sender for us automatically.
 #[derive(Debug)]
 pub struct Ws2pConnectionHandler {
@@ -44,10 +44,14 @@ pub struct Ws2pConnectionHandler {
 impl Ws2pConnectionHandler {
     fn exec_ws_action(&mut self, ws_action_order: WebsocketActionOrder) -> ws::Result<()> {
         match ws_action_order.ws_action {
-            WebsocketAction::ConnectTo { .. } => {
+            WebsocketAction::ConnectToUrl { .. } => {
+                fatal_error!("Could not generate a new connection in the context of a controller.")
+            }
+            WebsocketAction::ConnectToEndpoint { .. } => {
                 fatal_error!("Could not generate a new connection in the context of a controller.")
             }
             WebsocketAction::SendMessage { msg } => {
+                debug!("received message to send");
                 let ws_msg = match msg {
                     WebsocketMessage::Bin(bin_msg) => Message::binary(bin_msg),
                     WebsocketMessage::Str(str_msg) => Message::text(str_msg),
@@ -64,7 +68,7 @@ impl Ws2pConnectionHandler {
                         }
                         // Log
                         debug!(
-                            "Succesfully send message to '{}'",
+                            "Successfully send message to '{}'",
                             if let Some(remote_addr) = self.remote_addr_opt {
                                 remote_addr.to_string()
                             } else {
@@ -126,17 +130,16 @@ impl Handler for Ws2pConnectionHandler {
         match self.controller.process(WebsocketIncomingEvent::OnOpen {
             remote_addr: self.remote_addr_opt,
         }) {
-            Ok(ws_action_order_opt) => {
+            Ok(ws_action_orders) => {
                 // Start RECV_SERVICE timeout
                 self.ws
                     .0
-                    .timeout(*constants::WS2P_RECV_SERVICE_FREQ_IN_MS, RECV_SERVICE)?;
-                // Execute websocket action order
-                if let Some(ws_action_order) = ws_action_order_opt {
-                    self.exec_ws_action(ws_action_order)
-                } else {
-                    Ok(())
+                    .timeout(*WS2P_RECV_SERVICE_FREQ_IN_MS, RECV_SERVICE)?;
+                // Execute websocket action orders
+                for ws_action_order in ws_action_orders {
+                    self.exec_ws_action(ws_action_order)?;
                 }
+                Ok(())
             }
             Err(e) => self.exec_ws_action(WebsocketActionOrder {
                 ws_action: WebsocketAction::CloseConnection {
@@ -159,12 +162,12 @@ impl Handler for Ws2pConnectionHandler {
             .controller
             .process(WebsocketIncomingEvent::OnMessage { msg })
         {
-            Ok(ws_action_order_opt) => {
-                if let Some(ws_action_order) = ws_action_order_opt {
-                    self.exec_ws_action(ws_action_order)
-                } else {
-                    Ok(())
+            Ok(ws_action_orders) => {
+                // Execute websocket action orders
+                for ws_action_order in ws_action_orders {
+                    self.exec_ws_action(ws_action_order)?;
                 }
+                Ok(())
             }
             Err(e) => self.exec_ws_action(WebsocketActionOrder {
                 ws_action: WebsocketAction::CloseConnection {
@@ -176,12 +179,18 @@ impl Handler for Ws2pConnectionHandler {
         }
     }
     fn on_timeout(&mut self, _event: Token) -> ws::Result<()> {
+        // self.ws.0.timeout(1_000, RECV_SERVICE)?;
+        // if let Some(ws_action_order) = self.controller.check_timeouts() {
+        //     self.exec_ws_action(ws_action_order)
+        // } else {
+        //     Ok(())
+        // }
         self.ws.0.timeout(1_000, RECV_SERVICE)?;
-        if let Some(ws_action_order) = self.controller.check_timeouts() {
-            self.exec_ws_action(ws_action_order)
-        } else {
-            Ok(())
+        let ws_action_orders = self.controller.get_pending_ws_actions();
+        for ws_action_order in ws_action_orders {
+            let _ = self.exec_ws_action(ws_action_order);
         }
+        Ok(())
     }
     /*fn on_frame(&mut self, frame: Frame) -> ws::Result<Option<Frame>> {
         Ok(Some(frame))
diff --git a/lib/modules/ws2p/ws2p/src/controllers/incoming_connections.rs b/lib/modules/ws2p/ws2p/src/controllers/incoming_connections.rs
index d3e306d1cc82e071105fe281edf9a09a893f04c8..c21157f8388cc91dd35a7d3f799836fe63393287 100644
--- a/lib/modules/ws2p/ws2p/src/controllers/incoming_connections.rs
+++ b/lib/modules/ws2p/ws2p/src/controllers/incoming_connections.rs
@@ -17,23 +17,21 @@
 
 use crate::controllers::handler::Ws2pConnectionHandler;
 use crate::controllers::*;
-use dubp_currency_params::CurrencyName;
 use durs_common_tools::fatal_error;
 use durs_message::DursMsg;
 use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use durs_ws2p_protocol::controller::meta_datas::WS2PControllerMetaDatas;
-use durs_ws2p_protocol::controller::{WS2PController, WS2PControllerId};
+use durs_ws2p_protocol::controller::WS2PConnectionDirection;
+use durs_ws2p_protocol::controller::WS2PController;
 use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
 use durs_ws2p_protocol::MySelfWs2pNode;
 use std::fmt::Debug;
 use std::net::ToSocketAddrs;
 use std::sync::mpsc;
-use ws::deflate::DeflateBuilder;
 use ws::listen;
 
 /// Listen on WSPv2 host:port
-pub fn listen_on_ws2p_v2_endpoint<A: ToSocketAddrs + Debug>(
-    currency: &CurrencyName,
+pub fn listen_on_ws2p_v2_endpoint<A: ToSocketAddrs + Debug + Copy>(
     orchestrator_sender: &mpsc::Sender<OrchestratorMsg<DursMsg>>,
     self_node: &MySelfWs2pNode,
     addr: A,
@@ -41,20 +39,20 @@ pub fn listen_on_ws2p_v2_endpoint<A: ToSocketAddrs + Debug>(
     // Connect to websocket
     listen(addr, move |ws| {
         match WS2PController::<DursMsg>::try_new(
-            WS2PControllerId::Incoming,
-            WS2PControllerMetaDatas::new(
-                Hash::random(),
-                WS2Pv2ConnectType::Incoming,
-                currency.clone(),
-                self_node.clone(),
-            ),
+            None,
+            WS2Pv2ConnectType::Incoming,
+            WS2PConnectionDirection::Incoming,
+            WS2PControllerMetaDatas::new(Hash::random(), self_node.clone()),
             orchestrator_sender.clone(),
         ) {
-            Ok(controller) => DeflateBuilder::new().build(Ws2pConnectionHandler {
-                ws: WsSender(ws),
-                remote_addr_opt: None,
-                controller,
-            }),
+            Ok(controller) => {
+                info!("WS2P: listening on {:?}", &addr);
+                Ws2pConnectionHandler {
+                    ws: WsSender(ws),
+                    remote_addr_opt: None,
+                    controller,
+                }
+            }
             Err(_e) => fatal_error!("WS2P Orchestrator unreachable"),
         }
     })
diff --git a/lib/modules/ws2p/ws2p/src/controllers/outgoing_connections.rs b/lib/modules/ws2p/ws2p/src/controllers/outgoing_connections.rs
index dcabd55e52affd675e19ed91e847518f3e0ab21c..fe448ac494f48d2255e6f153a1cdf9dbafdaab1d 100644
--- a/lib/modules/ws2p/ws2p/src/controllers/outgoing_connections.rs
+++ b/lib/modules/ws2p/ws2p/src/controllers/outgoing_connections.rs
@@ -15,26 +15,25 @@
 
 //! WS2P outgoing connections controllers.
 
+//use durs_network::*;
+// use std::thread;
 use crate::controllers::handler::Ws2pConnectionHandler;
 use crate::controllers::*;
-use dubp_currency_params::CurrencyName;
 use durs_common_tools::fatal_error;
 use durs_message::DursMsg;
 use durs_network_documents::network_endpoint::EndpointEnum;
 use durs_network_documents::NodeFullId;
+use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use durs_ws2p_protocol::controller::meta_datas::WS2PControllerMetaDatas;
-use durs_ws2p_protocol::controller::{WS2PController, WS2PControllerId};
+use durs_ws2p_protocol::controller::WS2PConnectionDirection;
+use durs_ws2p_protocol::controller::WS2PController;
 use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
 use durs_ws2p_protocol::MySelfWs2pNode;
-use ws::connect;
-use ws::deflate::DeflateBuilder;
-//use durs_network::*;
-use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use std::sync::mpsc;
+use ws::connect;
 
 /// Connect to WSPv2 Endpoint
 pub fn connect_to_ws2p_v2_endpoint(
-    currency: &CurrencyName,
     orchestrator_sender: &mpsc::Sender<OrchestratorMsg<DursMsg>>,
     self_node: &MySelfWs2pNode,
     expected_remote_full_id: Option<NodeFullId>,
@@ -44,27 +43,51 @@ pub fn connect_to_ws2p_v2_endpoint(
     let ws_url = endpoint.get_url(true, false).expect("Endpoint unreachable");
 
     // Log
-    info!("Try connection to {} ...", ws_url);
+    info!("Try connection to endpoint : {} ...", ws_url);
 
     // Connect to websocket
     connect(ws_url, move |ws| {
         match WS2PController::<DursMsg>::try_new(
-            WS2PControllerId::Outgoing {
-                expected_remote_full_id,
+            expected_remote_full_id,
+            WS2Pv2ConnectType::Server,
+            WS2PConnectionDirection::Outgoing,
+            WS2PControllerMetaDatas::new(Hash::random(), self_node.clone()),
+            orchestrator_sender.clone(),
+        ) {
+            Ok(controller) => Ws2pConnectionHandler {
+                ws: WsSender(ws),
+                remote_addr_opt: None,
+                controller,
             },
-            WS2PControllerMetaDatas::new(
-                Hash::random(),
-                WS2Pv2ConnectType::OutgoingServer,
-                currency.clone(),
-                self_node.clone(),
-            ),
+            Err(_e) => fatal_error!("WS2P Service unreachable"),
+        }
+    })
+}
+
+/// Connect to URL
+pub fn connect_to_url(
+    orchestrator_sender: &mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    self_node: &MySelfWs2pNode,
+    ws_url: String,
+    from_blockstamp: Option<Blockstamp>,
+) -> ws::Result<()> {
+    // Log
+    info!("Try connection to url : {} ...", ws_url);
+
+    // Connect to websocket
+    connect(ws_url, move |ws| {
+        match WS2PController::<DursMsg>::try_new(
+            None,
+            WS2Pv2ConnectType::Sync { from_blockstamp },
+            WS2PConnectionDirection::Outgoing,
+            WS2PControllerMetaDatas::new(Hash::random(), self_node.clone()),
             orchestrator_sender.clone(),
         ) {
-            Ok(controller) => DeflateBuilder::new().build(Ws2pConnectionHandler {
+            Ok(controller) => Ws2pConnectionHandler {
                 ws: WsSender(ws),
                 remote_addr_opt: None,
                 controller,
-            }),
+            },
             Err(_e) => fatal_error!("WS2P Service unreachable"),
         }
     })
diff --git a/lib/modules/ws2p/ws2p/src/generate_peer.rs b/lib/modules/ws2p/ws2p/src/generate_peer.rs
index 9eef193c4ea8c74eddc984fd2c0e15eb35befc45..f335ee36845506ea939c30798e972126e8f6f686 100644
--- a/lib/modules/ws2p/ws2p/src/generate_peer.rs
+++ b/lib/modules/ws2p/ws2p/src/generate_peer.rs
@@ -25,6 +25,7 @@ use durs_network_documents::network_endpoint::*;
 use durs_network_documents::network_peer::*;
 use durs_network_documents::*;
 
+/// self peer update endpoints
 pub fn _self_peer_update_endpoints(
     self_peer: PeerCardV11,
     issuer_signator: &SignatorEnum,
@@ -89,6 +90,7 @@ pub fn _self_peer_update_endpoints(
     Ok(new_self_peer)
 }
 
+/// generate peer card
 pub fn _generate_self_peer(
     currency_name: CurrencyName,
     issuer_signator: &SignatorEnum,
@@ -120,6 +122,7 @@ pub fn _generate_self_peer(
 
     let mut self_peer = PeerCardV11 {
         currency_name,
+        certifiers: vec![],
         issuer: issuer_signator.public_key(),
         node_id,
         created_on,
diff --git a/lib/modules/ws2p/ws2p/src/lib.rs b/lib/modules/ws2p/ws2p/src/lib.rs
index 13e4c09fb747e5bb29d2ef2844e96e81b9f37688..40de5d7d148ac7f392bb5831a4424088801a83c3 100644
--- a/lib/modules/ws2p/ws2p/src/lib.rs
+++ b/lib/modules/ws2p/ws2p/src/lib.rs
@@ -29,18 +29,22 @@
 #[macro_use]
 extern crate log;
 #[macro_use]
-extern crate serde_derive;
-#[macro_use]
 extern crate structopt;
+#[macro_use]
+extern crate serde_derive;
 
+mod conf;
 mod constants;
 pub mod controllers;
 mod errors;
-mod generate_peer;
-pub mod services;
+pub mod generate_peer;
+mod websocket;
 
 use crate::errors::WS2PError;
+use crate::websocket::WebsocketConnector;
+use dubp_common_doc::BlockNumber;
 use dubp_currency_params::CurrencyName;
+use dup_crypto::keys::{KeyPair, KeyPairEnum};
 use durs_common_tools::fatal_error;
 use durs_common_tools::traits::merge::Merge;
 use durs_conf::DuRsConf;
@@ -49,8 +53,16 @@ use durs_module::*;
 use durs_network::cli::sync::SyncOpt;
 use durs_network::*;
 use durs_network_documents::network_endpoint::*;
+use durs_network_documents::network_peer::*;
+use durs_network_documents::NodeId;
+use durs_ws2p_messages::v2::api_features::WS2PFeatures;
+use durs_ws2p_protocol::constants as protocol_constants;
+use durs_ws2p_protocol::orchestrator::{WS2PConnectionManager, WS2POrchestrator};
+//use durs_ws2p_protocol::websocket::WebsocketTrait;
+use durs_ws2p_protocol::MySelfWs2pNode;
 use maplit::hashset;
 use std::sync::mpsc;
+use std::thread;
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 /// WS2P Configuration
@@ -82,7 +94,7 @@ impl Merge for WS2PUserConf {
 impl Default for WS2PConf {
     fn default() -> Self {
         WS2PConf {
-            outcoming_quota: *constants::WS2P_DEFAULT_OUTCOMING_QUOTA,
+            outcoming_quota: *protocol_constants::WS2P_DEFAULT_OUTCOMING_QUOTA,
             sync_endpoints: vec![
                 EndpointV2::parse_from_raw("WS2P 2 g1.dunitrust.org 443 ws2p").unwrap(),
                 EndpointV2::parse_from_raw("WS2P 2 rs.g1.librelois.fr 443 ws2p").unwrap(),
@@ -104,8 +116,8 @@ impl Default for WS2PModule {
 #[derive(Debug)]
 /// WS2PFeaturesParseError
 pub enum WS2PFeaturesParseError {
-    /// UnknowApiFeature
-    UnknowApiFeature(String),
+    /// UnknownApiFeature
+    UnknownApiFeature(String),
 }
 
 impl ApiModule<DuRsConf, DursMsg> for WS2PModule {
@@ -116,15 +128,11 @@ impl ApiModule<DuRsConf, DursMsg> for WS2PModule {
         let mut api_features = Vec::with_capacity(0);
         for str_feature in str_features {
             match str_feature {
-                "DEF" => api_features[0] += 1u8,
+                "RBC" => 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(
+                    error!("UnknownApiFeature({})", str_feature);
+                    return Err(WS2PFeaturesParseError::UnknownApiFeature(String::from(
                         str_feature,
                     )));
                 }
@@ -135,19 +143,46 @@ impl ApiModule<DuRsConf, DursMsg> for WS2PModule {
 }
 
 impl NetworkModule<DuRsConf, DursMsg> for WS2PModule {
+    // --- SYNC ---
     fn sync(
-        _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
-        _keys: RequiredKeysContent,
-        _conf: WS2PConf,
-        _main_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
-        _sync_params: SyncOpt,
+        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        keys: RequiredKeysContent,
+        conf: WS2PConf,
+        router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+        sync_params: SyncOpt,
     ) -> Result<(), SyncError> {
-        unimplemented!()
+        if let Ok((mut connection_manager, mut orchestrator)) = init(
+            conf,
+            keys,
+            router_sender,
+            soft_meta_datas,
+            Some(sync_params.clone()),
+        ) {
+            // starts listening module messages
+            thread::spawn(move || orchestrator.main_loop());
+            // starts outgoing in sync mode
+            thread::spawn(move || connection_manager.main_loop(Some(sync_params)));
+
+            info!("WS2P: started orchestrators threads in sync mode");
+
+            // prevents Dunitrust from stopping
+            // FIXME remove
+            loop {
+                thread::sleep(std::time::Duration::from_secs(10));
+                // return Ok if no error occurred
+                // Ok(())
+            }
+        } else {
+            fatal_error!("failed to init orchestrator");
+        }
     }
 }
 
 #[derive(StructOpt, Debug, Copy, Clone)]
-#[structopt(name = "ws2p", setting(structopt::clap::AppSettings::ColoredHelp))]
+#[structopt(
+    name = "ws2pv2",
+    raw(setting = "structopt::clap::AppSettings::ColoredHelp")
+)]
 /// WS2P subcommand options
 pub struct WS2POpt {}
 
@@ -156,6 +191,7 @@ impl DursModule<DuRsConf, DursMsg> for WS2PModule {
     type ModuleConf = WS2PConf;
     type ModuleOpt = WS2POpt;
 
+    #[inline]
     fn name() -> ModuleStaticName {
         ModuleStaticName(constants::MODULE_NAME)
     }
@@ -193,53 +229,137 @@ impl DursModule<DuRsConf, DursMsg> for WS2PModule {
         _module_user_conf: Option<Self::ModuleUserConf>,
         _subcommand_args: WS2POpt,
     ) -> Option<Self::ModuleUserConf> {
-        println!("Succesfully exec ws2p subcommand !");
+        println!("Successfully exec ws2p subcommand !");
         None
     }
+    // --- START ---
     fn start(
-        _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
         keys: RequiredKeysContent,
-        _conf: WS2PConf,
+        conf: WS2PConf,
         router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
     ) -> Result<(), failure::Error> {
-        // Get key_pair
-        let _key_pair = if let RequiredKeysContent::NetworkKeyPair(key_pair) = keys {
-            key_pair
-        } else {
-            return Err(WS2PError::UnexpectedKeys.into());
-        };
-
-        // Create module channel
-        let (module_sender, module_receiver) = mpsc::channel();
-
-        // Registration with the rooter
-        if router_sender
-            .send(RouterThreadMessage::ModuleRegistration {
-                static_name: ModuleStaticName(constants::MODULE_NAME),
-                sender: module_sender,
-                roles: vec![ModuleRole::InterNodesNetwork],
-                events_subscription: vec![
-                    ModuleEvent::NewValidBlock,
-                    ModuleEvent::NewWotDocInPool,
-                    ModuleEvent::NewTxinPool,
-                ],
-                reserved_apis_parts: vec![ApiPart {
-                    name: ApiName(constants::API_NAME.to_owned()),
-                    versions: hashset![ApiVersion(2)],
-                }],
-                endpoints: vec![],
-            })
-            .is_err()
+        if let Ok((mut connection_manager, mut orchestrator)) =
+            init(conf, keys, router_sender, soft_meta_datas, None)
         {
-            fatal_error!("WS2P module fail to send registration to router !")
-        }
+            // starts listening module messages
+            thread::spawn(move || orchestrator.main_loop());
+            // starts outgoing in start mode
+            thread::spawn(move || connection_manager.main_loop(None));
 
-        while let Ok(msg) = module_receiver.recv() {
-            if let DursMsg::Stop = msg {
-                break;
-            }
+            info!("WS2P: started orchestrators threads in start mode");
+
+            // return Ok if no error occurred
+            Ok(())
+        } else {
+            fatal_error!("failed to init orchestrator");
         }
+    }
+}
+
+// initialize WS2Pv2 module orchestrators
+fn init(
+    _conf: WS2PConf,
+    keys: RequiredKeysContent,
+    router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+    soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+    sync_params_opt: Option<SyncOpt>,
+) -> Result<(WS2PConnectionManager<WebsocketConnector>, WS2POrchestrator), failure::Error> {
+    // Get key_pair
+    let key_pair: KeyPairEnum = if let RequiredKeysContent::NetworkKeyPair(key_pair) = keys {
+        key_pair
+    } else {
+        return Err(WS2PError::UnexpectedKeys.into());
+    };
+
+    // Get node id
+    let node_id = NodeId(soft_meta_datas.conf.my_node_id());
+
+    // Create module channel
+    let (module_sender, module_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) =
+        mpsc::channel();
+
+    // Register module to router
+    if router_sender
+        .send(RouterThreadMessage::ModuleRegistration {
+            static_name: WS2PModule::name(),
+            sender: module_sender,
+            roles: vec![ModuleRole::InterNodesNetwork],
+            events_subscription: vec![
+                ModuleEvent::NewValidBlock,
+                ModuleEvent::NewWotDocInPool,
+                ModuleEvent::NewTxinPool,
+            ],
+            reserved_apis_parts: vec![ApiPart {
+                name: ApiName(constants::API_NAME.to_owned()),
+                versions: hashset![ApiVersion(2)],
+            }],
+            endpoints: vec![],
+        })
+        .is_ok()
+    {
+        debug!("Send ws2p sender to main thread.");
+    } else {
+        fatal_error!("Fail to register WS2P module to router");
+    }
 
-        Ok(())
+    // Get currency name
+    let mut sync_mode = false;
+    let mut currency = CurrencyName(durs_conf::constants::DEFAULT_CURRENCY.to_owned());
+    if let Some(sync_params) = sync_params_opt {
+        sync_mode = true;
+        if let Some(currency_str) = sync_params.currency {
+            currency = CurrencyName(currency_str);
+        }
     }
+    // TODO HUGO get currency name
+
+    // generate self signator
+    let self_signator = key_pair.generate_signator().expect("corrupted keypair"); // TODO handle error
+
+    // generate peer card
+    let peer_card = if let Ok(peer_card) = generate_peer::_generate_self_peer(
+        currency.clone(),
+        &self_signator,
+        node_id,
+        BlockNumber(0), // yet unknown FIXME TODO replace when known → remove when db is accessible
+        vec![],
+    ) {
+        peer_card
+    } else {
+        fatal_error!("can not sign self peer");
+    };
+
+    // generate self node
+    let self_node = MySelfWs2pNode::new(
+        node_id,
+        key_pair,
+        WS2PFeatures([0u8; 4]),
+        PeerCard::V11(peer_card),
+        currency,
+    );
+
+    // Create and start ws server
+    /*if sync_params_opt.is_none() {
+        WebsocketConnector::new(
+            self_node.clone(),
+            orchestrator_sender,
+        )
+    }*/
+
+    // initialize the Orchestrator pair
+    let (connection_manager, orchestrator) = WS2POrchestrator::new_pair::<WebsocketConnector>(
+        WS2PModule::name(),
+        router_sender,
+        module_receiver,
+        self_node.clone(),
+        self_signator,
+        soft_meta_datas,
+        sync_mode,
+    );
+
+    info!("WS2P: initialized orchestrators");
+
+    // return the orchestrators in order to start their main loop in separate threads
+    Ok((connection_manager, orchestrator))
 }
diff --git a/lib/modules/ws2p/ws2p/src/services/outgoing.rs b/lib/modules/ws2p/ws2p/src/services/outgoing.rs
deleted file mode 100644
index 3b5546cbbbd29af7a2f8e8041650207e762c15ef..0000000000000000000000000000000000000000
--- a/lib/modules/ws2p/ws2p/src/services/outgoing.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
-//
-// 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 outgoing Services
-
-use crate::services::WsError;
-use crate::*;
-use dubp_currency_params::CurrencyName;
-use durs_network_documents::{NodeFullId, NodeId};
-use durs_ws2p_protocol::connection_state::WS2PConnectionState;
-use durs_ws2p_protocol::controller::WebsocketActionOrder;
-use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
-use durs_ws2p_protocol::MySelfWs2pNode;
-use std::collections::HashMap;
-use std::sync::mpsc;
-
-#[derive(Debug, Clone)]
-/// Data allowing the service to manage an outgoing connection
-pub struct OutgoingConnection {
-    /// Endpoint
-    pub endpoint: EndpointEnum,
-    /// Controller channel
-    pub controller: mpsc::Sender<WebsocketActionOrder>,
-}
-
-#[derive(Debug, Copy, Clone)]
-/// Endpoind whose last connection attempt failed
-pub struct EndpointInError {
-    /// Last attemp time
-    pub last_attempt_time: u64,
-    /// Error status
-    pub error: WS2PConnectionState,
-}
-
-#[derive(Debug)]
-/// Outgoing connection management service
-pub struct WS2POutgoingOrchestrator {
-    /// Currency Name
-    pub currency: CurrencyName,
-    /// Local node datas
-    pub self_node: MySelfWs2pNode,
-    /// Outgoing connections quota
-    pub quota: usize,
-    /// List of established connections
-    pub connections: HashMap<NodeFullId, OutgoingConnection>,
-    /// List of endpoinds whose last connection attempt failed
-    pub endpoints_in_error: HashMap<NodeFullId, EndpointInError>,
-    /// List of endpoints that have never been contacted
-    pub never_try_endpoints: Vec<EndpointEnum>,
-    /// Service receiver
-    pub receiver: mpsc::Receiver<OrchestratorMsg<DursMsg>>,
-    /// Orchestrator sender
-    pub sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
-}
-
-impl WS2POutgoingOrchestrator {
-    /// Instantiate WS2POutgoingOrchestrator
-    pub fn new(
-        currency: CurrencyName,
-        ws2p_conf: &WS2PConf,
-        self_node: MySelfWs2pNode,
-    ) -> WS2POutgoingOrchestrator {
-        // Create service channel
-        let (sender, receiver) = mpsc::channel();
-
-        WS2POutgoingOrchestrator {
-            currency,
-            quota: ws2p_conf.outcoming_quota,
-            connections: HashMap::with_capacity(ws2p_conf.outcoming_quota),
-            endpoints_in_error: HashMap::new(),
-            never_try_endpoints: Vec::new(),
-            self_node,
-            receiver,
-            sender,
-        }
-    }
-
-    /// Connect to WSPv2 Endpoint
-    pub fn connect_to_ws2p_v2_endpoint(
-        &self,
-        endpoint: &EndpointEnum,
-        remote_node_id: Option<NodeId>,
-    ) -> Result<(), WsError> {
-        let expected_remote_full_id = if let Some(remote_node_id) = remote_node_id {
-            Some(NodeFullId(remote_node_id, endpoint.pubkey()))
-        } else {
-            None
-        };
-        match controllers::outgoing_connections::connect_to_ws2p_v2_endpoint(
-            &self.currency,
-            &self.sender,
-            &self.self_node,
-            expected_remote_full_id,
-            endpoint,
-        ) {
-            Ok(_) => Ok(()),
-            Err(_) => Err(WsError::UnknownError),
-        }
-    }
-}
diff --git a/lib/modules/ws2p/ws2p/src/websocket.rs b/lib/modules/ws2p/ws2p/src/websocket.rs
new file mode 100644
index 0000000000000000000000000000000000000000..77e135c2c957153656a88116b8c714f41bbf67ad
--- /dev/null
+++ b/lib/modules/ws2p/ws2p/src/websocket.rs
@@ -0,0 +1,143 @@
+//  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/>.
+
+//! WebsocketConnector structure implementing WebsocketTrait
+
+// use durs_network_documents::network_endpoint::EndpointEnum;
+// use durs_network_documents::url::Url;
+// use durs_network_documents::NodeFullId;
+use crate::controllers::incoming_connections::listen_on_ws2p_v2_endpoint;
+use crate::controllers::outgoing_connections::connect_to_url;
+use crate::controllers::outgoing_connections::connect_to_ws2p_v2_endpoint;
+use core::fmt::Debug;
+use durs_common_tools::fatal_error;
+use durs_message::DursMsg;
+use durs_network_documents::url::Url;
+use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
+use durs_ws2p_protocol::websocket::WsError;
+use durs_ws2p_protocol::websocket::{WebsocketAction, WebsocketTrait};
+use durs_ws2p_protocol::MySelfWs2pNode;
+use std::net::ToSocketAddrs;
+use std::sync::mpsc;
+
+#[derive(Debug, Clone)]
+pub struct WebsocketConnector {
+    my_self_ws2p_node: MySelfWs2pNode,
+    orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+}
+
+impl WebsocketTrait for WebsocketConnector {
+    fn new(
+        my_self_ws2p_node: MySelfWs2pNode,
+        orchestrator_sender: mpsc::Sender<OrchestratorMsg<DursMsg>>,
+    ) -> Self {
+        WebsocketConnector {
+            my_self_ws2p_node,
+            orchestrator_sender,
+        }
+    }
+
+    fn exec(&self, action: WebsocketAction) -> Result<(), WsError> {
+        match action {
+            WebsocketAction::ConnectToUrl {
+                url,
+                from_blockstamp,
+            } => match url {
+                Url::UrlWithoutScheme(url) => {
+                    // add scheme based on port
+                    let url = match url.tls() {
+                        true => url.to_url_with_scheme("wss"),
+                        false => url.to_url_with_scheme("ws"),
+                    };
+                    // connect to it
+                    if let Ok(ws_url) = url {
+                        match connect_to_url(
+                            &self.orchestrator_sender,
+                            &self.my_self_ws2p_node,
+                            ws_url.to_string(),
+                            from_blockstamp,
+                        ) {
+                            Ok(_) => Ok(()),
+                            Err(_) => Err(WsError::ConnectError),
+                        }
+                    } else {
+                        Err(WsError::ConnectError)
+                    }
+                }
+                Url::Url(_) => {
+                    error!("expected url without scheme");
+                    Err(WsError::ConnectError)
+                }
+            },
+            WebsocketAction::ConnectToEndpoint {
+                expected_remote_full_id,
+                endpoint,
+            } => {
+                match connect_to_ws2p_v2_endpoint(
+                    &self.orchestrator_sender,
+                    &self.my_self_ws2p_node,
+                    expected_remote_full_id,
+                    &endpoint,
+                ) {
+                    Ok(_) => Ok(()),
+                    Err(_) => Err(WsError::ConnectError),
+                }
+            }
+            _ => fatal_error!("the exec function must not be used for this"),
+        }
+    }
+
+    fn listen<A: ToSocketAddrs + Debug + Copy>(&self, addr: A) -> Result<(), WsError> {
+        match listen_on_ws2p_v2_endpoint(&self.orchestrator_sender, &self.my_self_ws2p_node, addr) {
+            Ok(_) => Ok(()),
+            Err(_) => {
+                error!("listen error");
+                Err(WsError::ListenError)
+            }
+        }
+    }
+}
+
+// fn connect(
+//     &self,
+//     expected_remote_full_id: Option<NodeFullId>,
+//     endpoint: &EndpointEnum,
+// ) -> Result<(), WsError> {
+//     match connect_to_ws2p_v2_endpoint(
+//         &self.currency,
+//         &self.orchestrator_sender,
+//         &self.my_self_ws2p_node,
+//         expected_remote_full_id,
+//         endpoint,
+//     ) {
+//         Ok(_) => Ok(()),
+//         Err(_) => Err(WsError::ConnectError),
+//     }
+// }
+// fn connect_to_url(&self, ws_url: Url) -> Result<(), WsError> {
+//     if let Ok(url) = ws_url.to_url_string("wss") {
+//         match connect_to_url(
+//             &self.currency,
+//             &self.orchestrator_sender,
+//             &self.my_self_ws2p_node,
+//             url,
+//         ) {
+//             Ok(_) => Ok(()),
+//             Err(_) => Err(WsError::ConnectError),
+//         }
+//     } else {
+//         Err(WsError::ConnectError)
+//     }
+// }
diff --git a/lib/modules/ws2p/ws2p/tests/connection_negociation.rs b/lib/modules/ws2p/ws2p/tests/connection_negociation.rs
index 88d9a79db878784ffc0b29c62a62b1287b691abe..ec89a008e45a226c609aaebc72e1d6cd2912eeca 100644
--- a/lib/modules/ws2p/ws2p/tests/connection_negociation.rs
+++ b/lib/modules/ws2p/ws2p/tests/connection_negociation.rs
@@ -16,15 +16,19 @@
 use dubp_currency_params::CurrencyName;
 use dup_crypto::keys::KeyPair;
 use dup_crypto::keys::*;
+//use durs_common_tests_tools::logger::init_logger_stdout;
+use dubp_common_doc::BlockNumber;
 use durs_message::DursMsg;
 use durs_network_documents::network_endpoint::*;
+use durs_network_documents::network_peer::*;
 use durs_network_documents::*;
 use durs_ws2p::controllers::incoming_connections::*;
 use durs_ws2p::controllers::outgoing_connections::*;
+use durs_ws2p::generate_peer;
 use durs_ws2p_messages::v2::api_features::*;
 use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
 use durs_ws2p_protocol::connection_state::WS2PConnectionState;
-use durs_ws2p_protocol::controller::{WS2PControllerEvent, WebsocketActionOrder};
+use durs_ws2p_protocol::controller::WS2PControllerEvent;
 use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
 use durs_ws2p_protocol::MySelfWs2pNode;
 use std::sync::mpsc;
@@ -63,25 +67,55 @@ pub fn keypair2() -> ed25519::Ed25519KeyPair {
 }
 
 fn server_node() -> MySelfWs2pNode {
+    // generate peer card
+    let peer_card = if let Ok(peer_card) = generate_peer::_generate_self_peer(
+        CurrencyName("mok".to_string()),
+        &SignatorEnum::Ed25519(keypair1().generate_signator().expect("corrupted keypair")),
+        NodeId(0),
+        BlockNumber(0),
+        vec![],
+    ) {
+        peer_card
+    } else {
+        panic!("can not sign self peer");
+    };
+
     MySelfWs2pNode {
         my_node_id: NodeId(0),
         my_key_pair: KeyPairEnum::Ed25519(keypair1()),
         my_features: WS2PFeatures([5u8, 0, 0, 0]),
+        my_peer: PeerCard::V11(peer_card),
+        my_currency: CurrencyName("mok".to_string()),
     }
 }
 
 fn client_node() -> MySelfWs2pNode {
+    // generate peer card
+    let peer_card = if let Ok(peer_card) = generate_peer::_generate_self_peer(
+        CurrencyName("mok".to_string()),
+        &SignatorEnum::Ed25519(keypair2().generate_signator().expect("corrupted keypair")),
+        NodeId(0),
+        BlockNumber(0),
+        vec![],
+    ) {
+        peer_card
+    } else {
+        panic!("can not sign self peer");
+    };
+
     MySelfWs2pNode {
         my_node_id: NodeId(1),
         my_key_pair: KeyPairEnum::Ed25519(keypair2()),
         my_features: WS2PFeatures([5u8, 0, 0, 0]),
+        my_peer: PeerCard::V11(peer_card),
+        my_currency: CurrencyName("mok".to_string()),
     }
 }
 
-//#[ignore]
+#[ignore]
 #[test]
 #[cfg(unix)]
-fn test_connection_negociation_denial() {
+fn test_connection_negotiation_denial() {
     setup();
 
     // ===== initialization =====
@@ -98,13 +132,15 @@ fn test_connection_negociation_denial() {
     let server_service_sender = server_service_channel.0.clone();
     thread::spawn(move || {
         listen_on_ws2p_v2_endpoint(
-            &currency(),
             &server_service_sender,
             &server_node_clone,
-            format!("localhost:{}", *PORT + 1),
+            &*format!("localhost:{}", *PORT + 1),
         )
     });
 
+    // Wait server ready...
+    thread::sleep(Duration::from_millis(100));
+
     // Create client service channel
     let client_service_channel = mpsc::channel();
 
@@ -113,7 +149,6 @@ fn test_connection_negociation_denial() {
     let client_service_sender = client_service_channel.0.clone();
     thread::spawn(move || {
         connect_to_ws2p_v2_endpoint(
-            &currency(),
             &client_service_sender,
             &client_node,
             Some(NodeFullId(
@@ -125,12 +160,10 @@ fn test_connection_negociation_denial() {
         )
     });
 
-    // ===== opening connection =====
-    // we must get Ws2pServiceSender::ControllerSender from the client and server threads (but we ignore them)
-    // we also test that the statuses match expected ones
+    println!("successfully started client and server");
 
-    let client_controller = get_controller(&client_service_channel.1);
-    let server_controller = get_controller(&server_service_channel.1);
+    // ===== opening connection =====
+    // we test that the statuses match expected ones
 
     // TryToSendConnectMsg
     let state = get_state(&server_service_channel.1); // server
@@ -149,17 +182,13 @@ fn test_connection_negociation_denial() {
     assert_eq!(WS2PConnectionState::ConnectMessOk, state);
     let state = get_state(&client_service_channel.1); // client
     assert_eq!(WS2PConnectionState::Denial, state);
-
-    // Stop
-    let _ = client_controller.send(WebsocketActionOrder::close());
-    let _ = server_controller.send(WebsocketActionOrder::close());
 }
 
 //#[ignore]
 #[test]
 #[cfg(unix)]
-fn test_connection_negociation_success() {
-    setup();
+fn test_connection_negotiation_success() {
+    //init_logger_stdout();
 
     // ===== initialization =====
     // client and server are initialized and launched in separate threads
@@ -175,15 +204,14 @@ fn test_connection_negociation_success() {
     let server_service_sender = server_service_channel.0.clone();
     thread::spawn(move || {
         listen_on_ws2p_v2_endpoint(
-            &currency(),
             &server_service_sender,
             &server_node_clone,
-            format!("localhost:{}", *PORT),
+            &*format!("localhost:{}", *PORT),
         )
     });
 
     // Wait server ready...
-    //thread::sleep(Duration::from_millis(500));
+    thread::sleep(Duration::from_millis(100));
 
     // Create client service channel
     let client_service_channel = mpsc::channel();
@@ -194,7 +222,6 @@ fn test_connection_negociation_success() {
     let client_service_sender = client_service_channel.0.clone();
     thread::spawn(move || {
         connect_to_ws2p_v2_endpoint(
-            &currency(),
             &client_service_sender,
             &client_node_clone,
             Some(server_node_clone.get_full_id()),
@@ -204,11 +231,7 @@ fn test_connection_negociation_success() {
     });
 
     // ===== opening connection =====
-    // we must get Ws2pServiceSender::ControllerSender from the client and server threads (but we ignore them)
-    // we also test that the statuses match expected ones
-
-    let _client_controller = get_controller(&client_service_channel.1);
-    let _server_controller = get_controller(&server_service_channel.1);
+    // we test that the statuses match expected ones
 
     // TryToSendConnectMsg
     let state = get_state(&client_service_channel.1); // client
@@ -248,35 +271,42 @@ fn test_connection_negociation_success() {
     );
 
     // Established for client
-    expected_event(
+    expected_event_new_conn_established(
         &client_service_channel.1,
-        WS2PControllerEvent::NewConnEstablished {
-            conn_type: WS2Pv2ConnectType::OutgoingServer,
-            remote_full_id: server_node.get_full_id(),
-        },
+        WS2Pv2ConnectType::Server,
+        server_node.get_full_id(),
     );
     // Established for server
-    expected_event(
+    expected_event_new_conn_established(
         &server_service_channel.1,
-        WS2PControllerEvent::NewConnEstablished {
-            conn_type: WS2Pv2ConnectType::OutgoingServer,
-            remote_full_id: client_node.get_full_id(),
-        },
+        WS2Pv2ConnectType::Server,
+        client_node.get_full_id(),
     );
 }
 
 // === functions used in above test ===
 
 // Get established event in a receiver
-fn expected_event(
+fn expected_event_new_conn_established(
     orchestrator_receiver: &mpsc::Receiver<OrchestratorMsg<DursMsg>>,
-    expected_event: WS2PControllerEvent,
+    expected_conn_type: WS2Pv2ConnectType,
+    expected_remote_full_id: NodeFullId,
 ) {
     match orchestrator_receiver
         .recv_timeout(Duration::from_millis(*TIMEOUT_IN_MS))
         .expect("Receive nothing from controller :")
     {
-        OrchestratorMsg::ControllerEvent { event, .. } => assert_eq!(expected_event, event),
+        OrchestratorMsg::ControllerEvent {
+            event,
+            controller_id,
+        } => {
+            if let WS2PControllerEvent::NewConnEstablished { .. } = event {
+                assert_eq!(expected_conn_type, controller_id.conn_type);
+                assert_eq!(expected_remote_full_id, controller_id.node_id);
+            } else {
+                panic!("Expect event NewConnEstablished, receive '{:?}' !", event);
+            }
+        }
         other => panic!("Expect signal ControllerEvent, receive '{:?}' !", other),
     }
 }
@@ -292,6 +322,10 @@ fn get_state(
         OrchestratorMsg::ControllerEvent {
             event: WS2PControllerEvent::StateChange { new_state },
             ..
+        }
+        | OrchestratorMsg::AnonControllerEvent {
+            event: WS2PControllerEvent::StateChange { new_state },
+            ..
         } => new_state,
         other => panic!(
             "Expect signal ChangeConnectionState, receive '{:?}' !",
@@ -299,17 +333,3 @@ fn get_state(
         ),
     }
 }
-
-// get the controller from the thread
-fn get_controller(
-    orchestrator_receiver: &mpsc::Receiver<OrchestratorMsg<DursMsg>>,
-) -> mpsc::Sender<WebsocketActionOrder> {
-    // we must receive controller sender
-    if let Ok(OrchestratorMsg::ControllerSender(controller_sender)) =
-        orchestrator_receiver.recv_timeout(Duration::from_millis(*TIMEOUT_IN_MS))
-    {
-        return controller_sender;
-    } else {
-        panic!("Not receive client controller sender");
-    }
-}
diff --git a/lib/modules/ws2p/ws2p/tests/syncronisation.rs b/lib/modules/ws2p/ws2p/tests/syncronisation.rs
new file mode 100644
index 0000000000000000000000000000000000000000..48e1bc8a9aaa7b11031e3d67582c617a6a937247
--- /dev/null
+++ b/lib/modules/ws2p/ws2p/tests/syncronisation.rs
@@ -0,0 +1,180 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// 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 durs_common_tests_tools::logger::init_logger_stdout;
+// use dubp_common_doc::BlockNumber;
+// use dubp_currency_params::CurrencyName;
+// use dup_crypto::keys::KeyPair;
+use dup_crypto::keys::*;
+use durs_conf::*;
+// use durs_message::*;
+// use durs_network_documents::network_endpoint::*;
+// use durs_network_documents::network_peer::*;
+// use durs_network_documents::*;
+// use durs_ws2p::controllers::incoming_connections::*;
+// use durs_ws2p::controllers::outgoing_connections::*;
+// use durs_ws2p::generate_peer;
+// use durs_ws2p_messages::v2::api_features::*;
+// use durs_ws2p_messages::v2::connect::WS2Pv2ConnectType;
+// use durs_ws2p_protocol::connection_state::WS2PConnectionState;
+// use durs_ws2p_protocol::controller::WS2PControllerEvent;
+// use durs_ws2p_protocol::orchestrator::OrchestratorMsg;
+// use durs_ws2p_protocol::MySelfWs2pNode;
+use std::sync::mpsc;
+// use std::sync::Once;
+// use std::thread;
+// use std::time::Duration;
+use durs_common_tools::fatal_error;
+use durs_module::SoftwareMetaDatas;
+use durs_module::*;
+use durs_network::cli::sync::SyncOpt;
+use durs_network::NetworkModule;
+use durs_network_documents::url::*;
+use durs_ws2p::*;
+use std::fs;
+use std::path::PathBuf;
+use std::str::FromStr;
+use tempfile::TempDir;
+
+#[macro_use]
+extern crate log;
+
+// pas de framework de test :'(
+fn mock_soft_metadata(profile_path: PathBuf) -> SoftwareMetaDatas<DuRsConf> {
+    SoftwareMetaDatas {
+        conf: DuRsConf::default(),
+        profile_path,
+        soft_name: "dunitruuuuust",
+        soft_version: "test",
+    }
+}
+
+// generate key
+fn keypair1() -> ed25519::Ed25519KeyPair {
+    let seed = Seed32::new([
+        228, 125, 124, 120, 57, 212, 246, 250, 139, 246, 62, 26, 56, 241, 175, 123, 151, 209, 5,
+        106, 2, 148, 43, 101, 118, 160, 233, 7, 112, 222, 0, 169,
+    ]);
+    ed25519::KeyPairFromSeed32Generator::generate(seed)
+}
+fn keypair2() -> ed25519::Ed25519KeyPair {
+    let seed = Seed32::new([
+        222, 125, 124, 120, 57, 212, 246, 250, 139, 246, 62, 26, 56, 241, 175, 123, 151, 209, 5,
+        106, 2, 148, 43, 101, 118, 160, 233, 7, 112, 222, 0, 169,
+    ]);
+    ed25519::KeyPairFromSeed32Generator::generate(seed)
+}
+
+// generate the blockchain data in the temporary directory
+// FIXME now it's juste copying existing data
+fn generate_blockchain_data(tmp_profile_path: &PathBuf, source_path: &str) {
+    let source_path = PathBuf::from(source_path.clone());
+    let data_dir: PathBuf = [tmp_profile_path.clone(), PathBuf::from("datas")]
+        .iter()
+        .collect();
+    let blockchain_dir: PathBuf = [data_dir.clone(), PathBuf::from("blockchain")]
+        .iter()
+        .collect();
+    fs::create_dir(&data_dir).expect("can not create directory");
+    fs::create_dir(&blockchain_dir).expect("can not create directory");
+    // copy files
+    let file_list = vec![
+        "conf.json",
+        "dunitrust.log",
+        "keypairs.json",
+        "datas/currency_params.db",
+        "datas/blockchain/data.mdb",
+        "datas/blockchain/lock.mdb",
+        "datas/blockchain/wot.db",
+    ];
+    for file in file_list {
+        let mut source = source_path.clone();
+        source.push(file.clone());
+        let mut dest = tmp_profile_path.clone();
+        dest.push(file);
+        // we do not mind if file does not exist
+        if fs::copy(source, dest).is_ok() {
+        } else {
+        };
+    }
+}
+
+// generate sync options
+fn generate_sync_opt() -> SyncOpt {
+    SyncOpt {
+        cautious_mode: false,
+        currency: Some("currency_name".to_string()),
+        end: None,
+        local_path: None,
+        source: Some(Url::from_str("localhost:8000").expect("can not parse url")),
+        start: false,
+        sync_module_name: None,
+        unsafe_mode: false,
+    }
+}
+
+#[test]
+fn test_synchronisation() {
+    // create and fill in temporary folders
+    let server_tmp_profile_path = TempDir::new().expect("Fail to create tmp dir.").into_path();
+    let client_tmp_profile_path = TempDir::new().expect("Fail to create tmp dir.").into_path();
+    generate_blockchain_data(
+        &server_tmp_profile_path,
+        "/home/hugo/.config/durs-dev/server/",
+    );
+    // generate_blockchain_data(
+    //     &client_tmp_profile_path,
+    //     "/home/hugo/.config/durs-dev/client/",
+    // );
+
+    // créer les channels du fake router serveur et client mpsc::Sender<RouterThreadMessage<DursMsg>>
+    let (server_router_sender, server_router_recv) = mpsc::channel();
+    let (client_router_sender, client_router_recv) = mpsc::channel();
+
+    // instancier le module ws2p serveur et client
+    let server_ws2p_module = WS2PModule {};
+    let client_ws2p_module = WS2PModule {};
+
+    // lancer le module ws2p serveur
+    WS2PModule::start(
+        &mock_soft_metadata(server_tmp_profile_path.clone()),
+        RequiredKeysContent::NetworkKeyPair(KeyPairEnum::Ed25519(keypair1())),
+        WS2PConf {
+            outcoming_quota: 5,
+            sync_endpoints: Vec::new(),
+        },
+        server_router_sender,
+    )
+    .unwrap_or_else(|_| fatal_error!("can not start server"));
+
+    // lancer le module ws2p client avec les bonnes syncOpt localhost:8000
+    WS2PModule::sync(
+        &mock_soft_metadata(server_tmp_profile_path.clone()),
+        RequiredKeysContent::NetworkKeyPair(KeyPairEnum::Ed25519(keypair2())),
+        WS2PConf {
+            outcoming_quota: 5,
+            sync_endpoints: Vec::new(),
+        },
+        client_router_sender,
+        generate_sync_opt(),
+    )
+    .unwrap_or_else(|_| fatal_error!("can not start client"));
+
+    // écouter les échanges et répondre
+
+    // Clear
+    std::fs::remove_dir_all(server_tmp_profile_path).expect("Fail to remove tmp dir.");
+    std::fs::remove_dir_all(client_tmp_profile_path).expect("Fail to remove tmp dir.");
+}