diff --git a/src/account.h b/src/account.h
index 949c6dffdccb7a2740dded87edb2e1e64263c8fc..bd7a99bfc2010b3813c5aa221bbffcacaebd6966 100644
--- a/src/account.h
+++ b/src/account.h
@@ -112,12 +112,16 @@ public:
      */
     virtual void loadConfig();
 
-    const AccountConfig& config() const {
-        if (config_) return *config_;
-        else throw std::runtime_error("Account doesn't have a configuration");
+    const AccountConfig& config() const
+    {
+        if (config_)
+            return *config_;
+        else
+            throw std::runtime_error("Account doesn't have a configuration");
     }
 
-    inline void editConfig(std::function<void(AccountConfig& config)>&& edit) {
+    inline void editConfig(std::function<void(AccountConfig& config)>&& edit)
+    {
         std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
         edit(*config_);
         saveConfig();
@@ -125,7 +129,8 @@ public:
 
     virtual void saveConfig() const;
 
-    void setAccountDetails(const std::map<std::string, std::string>& details) {
+    void setAccountDetails(const std::map<std::string, std::string>& details)
+    {
         std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
         if (not config_)
             config_ = buildConfig();
@@ -134,7 +139,8 @@ public:
         saveConfig();
     }
 
-    std::map<std::string, std::string> getAccountDetails() const {
+    std::map<std::string, std::string> getAccountDetails() const
+    {
         std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
         return config().toMap();
     }
@@ -186,6 +192,7 @@ public:
      * @return a token to query the message status
      */
     virtual uint64_t sendTextMessage(const std::string& /*to*/,
+                                     const std::string& /*deviceId*/,
                                      const std::map<std::string, std::string>& /*payloads*/,
                                      uint64_t /*refreshToken*/ = 0,
                                      bool /*onlyConnected*/ = false)
diff --git a/src/connectivity/connectionmanager.cpp b/src/connectivity/connectionmanager.cpp
index 9457e404caa6e1fd8d002d09a1df5680eec46bf5..61f507f75218f478dbdd4999ba0249327120a97c 100644
--- a/src/connectivity/connectionmanager.cpp
+++ b/src/connectivity/connectionmanager.cpp
@@ -60,7 +60,6 @@ struct ConnectionInfo
 
     std::function<void(bool)> onConnected_;
     std::unique_ptr<asio::steady_timer> waitForAnswer_ {};
-
 };
 
 class ConnectionManager::Impl : public std::enable_shared_from_this<ConnectionManager::Impl>
@@ -344,12 +343,17 @@ ConnectionManager::Impl::connectDeviceStartIce(
     }
 
     info->onConnected_ = std::move(onConnected);
-    info->waitForAnswer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(), std::chrono::steady_clock::now() + DHT_MSG_TIMEOUT);
-    info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, deviceId, vid));
+    info->waitForAnswer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(),
+                                                                std::chrono::steady_clock::now()
+                                                                    + DHT_MSG_TIMEOUT);
+    info->waitForAnswer_->async_wait(
+        std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, deviceId, vid));
 }
 
 void
-ConnectionManager::Impl::onResponse(const asio::error_code& ec, const DeviceId& deviceId, const dht::Value::Id& vid)
+ConnectionManager::Impl::onResponse(const asio::error_code& ec,
+                                    const DeviceId& deviceId,
+                                    const dht::Value::Id& vid)
 {
     if (ec == asio::error::operation_aborted)
         return;
@@ -384,7 +388,6 @@ ConnectionManager::Impl::onResponse(const asio::error_code& ec, const DeviceId&
     info->onConnected_(true);
 }
 
-
 bool
 ConnectionManager::Impl::connectDeviceOnNegoDone(
     const DeviceId& deviceId,
@@ -706,7 +709,11 @@ ConnectionManager::Impl::onPeerResponse(const PeerConnectionRequest& req)
         info->responseReceived_ = true;
         info->response_ = std::move(req);
         info->waitForAnswer_->expires_at(std::chrono::steady_clock::now());
-        info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, device, req.id));
+        info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse,
+                                                   this,
+                                                   std::placeholders::_1,
+                                                   device,
+                                                   req.id));
     } else {
         JAMI_WARN() << account << " respond received, but cannot find request";
     }
diff --git a/src/connectivity/multiplexed_socket.cpp b/src/connectivity/multiplexed_socket.cpp
index 2670f716264fb4571f18468c9f31e8dfb62b927f..3c48f3db2c6bf4392adb7fbf7d1003f25d674d46 100644
--- a/src/connectivity/multiplexed_socket.cpp
+++ b/src/connectivity/multiplexed_socket.cpp
@@ -127,19 +127,15 @@ public:
     {
         auto& channelSocket = sockets[channel];
         if (not channelSocket)
-            channelSocket = std::make_shared<ChannelSocket>(parent_.weak(),
-                                                            name,
-                                                            channel,
-                                                            isInitiator,
-                                                            [w = parent_.weak(), channel]() {
-                                                                // Remove socket in another thread to avoid any lock
-                                                                dht::ThreadPool::io().run([w, channel]() {
-                                                                    if (auto shared = w.lock()) {
-                                                                        shared->eraseChannel(channel);
-                                                                    }
-                                                                });
-
-                                                            });
+            channelSocket = std::make_shared<ChannelSocket>(
+                parent_.weak(), name, channel, isInitiator, [w = parent_.weak(), channel]() {
+                    // Remove socket in another thread to avoid any lock
+                    dht::ThreadPool::io().run([w, channel]() {
+                        if (auto shared = w.lock()) {
+                            shared->eraseChannel(channel);
+                        }
+                    });
+                });
         else {
             JAMI_WARN("A channel is already present on that socket, accepting "
                       "the request will close the previous one %s",
@@ -669,7 +665,11 @@ MultiplexedSocket::monitor() const
     std::lock_guard<std::mutex> lk(pimpl_->socketsMutex);
     for (const auto& [_, channel] : pimpl_->sockets) {
         if (channel)
-            JAMI_DEBUG("\t\t- Channel {} (count: {}) with name {:s} Initiator: {}", fmt::ptr(channel.get()), channel.use_count(), channel->name(), channel->isInitiator());
+            JAMI_DEBUG("\t\t- Channel {} (count: {}) with name {:s} Initiator: {}",
+                       fmt::ptr(channel.get()),
+                       channel.use_count(),
+                       channel->name(),
+                       channel->isInitiator());
     }
 }
 
@@ -791,8 +791,7 @@ ChannelSocketTest::ChannelSocketTest(const DeviceId& deviceId,
     , ioCtx_(*Manager::instance().ioContext())
 {}
 
-ChannelSocketTest::~ChannelSocketTest()
-{}
+ChannelSocketTest::~ChannelSocketTest() {}
 
 void
 ChannelSocketTest::link(const std::shared_ptr<ChannelSocketTest>& socket1,
@@ -925,7 +924,8 @@ ChannelSocket::ChannelSocket(std::weak_ptr<MultiplexedSocket> endpoint,
                              const uint16_t& channel,
                              bool isInitiator,
                              std::function<void()> rmFromMxSockCb)
-    : pimpl_ {std::make_unique<Impl>(endpoint, name, channel, isInitiator, std::move(rmFromMxSockCb))}
+    : pimpl_ {
+        std::make_unique<Impl>(endpoint, name, channel, isInitiator, std::move(rmFromMxSockCb))}
 {}
 
 ChannelSocket::~ChannelSocket() {}
diff --git a/src/connectivity/multiplexed_socket.h b/src/connectivity/multiplexed_socket.h
index 1b6858b38671cf47cf55a2ddcb447bffcd1d4ce1..a414adf26cf5b9febe2c2aef5df98e3349241907 100644
--- a/src/connectivity/multiplexed_socket.h
+++ b/src/connectivity/multiplexed_socket.h
@@ -115,10 +115,12 @@ public:
      * This will close all channels and send a TLS EOF on the main socket.
      */
     void shutdown();
+
     /**
      * This will wait that eventLoop is stopped and stop it if necessary
      */
     void join();
+
     /**
      * Will trigger that callback when shutdown() is called
      */
diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp
index 1f49da6e3b0258ac53569f89cc345f8b9b770be6..d75bcd77b25793e04e33d0dbb7c3fec5a4b24883 100644
--- a/src/im/message_engine.cpp
+++ b/src/im/message_engine.cpp
@@ -44,6 +44,7 @@ MessageEngine::MessageEngine(SIPAccountBase& acc, const std::string& path)
 
 MessageToken
 MessageEngine::sendMessage(const std::string& to,
+                           const std::string& deviceId,
                            const std::map<std::string, std::string>& payloads,
                            uint64_t refreshToken)
 {
@@ -52,7 +53,8 @@ MessageEngine::sendMessage(const std::string& to,
     MessageToken token;
     {
         std::lock_guard<std::mutex> lock(messagesMutex_);
-        auto& peerMessages = messages_[to];
+
+        auto& peerMessages = deviceId.empty() ? messages_[to] : messagesDevices_[deviceId];
         auto previousIt = peerMessages.find(refreshToken);
         if (previousIt != peerMessages.end() && previousIt->second.status != MessageStatus::SENT) {
             JAMI_DEBUG("[message {:d}] Replace content", refreshToken);
@@ -70,18 +72,20 @@ MessageEngine::sendMessage(const std::string& to,
         }
         save_();
     }
-    runOnMainThread([this, to]() { retrySend(to); });
+    runOnMainThread([this, to, deviceId]() { retrySend(to, true, deviceId); });
     return token;
 }
 
 void
-MessageEngine::onPeerOnline(const std::string& peer, bool retryOnTimeout)
+MessageEngine::onPeerOnline(const std::string& peer,
+                            bool retryOnTimeout,
+                            const std::string& deviceId)
 {
-    retrySend(peer, retryOnTimeout);
+    retrySend(peer, retryOnTimeout, deviceId);
 }
 
 void
-MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout)
+MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout, const std::string& deviceId)
 {
     if (account_.getRegistrationState() != RegistrationState::REGISTERED)
         return;
@@ -94,10 +98,13 @@ MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout)
     std::vector<PendingMsg> pending {};
     {
         std::lock_guard<std::mutex> lock(messagesMutex_);
-        auto p = messages_.find(peer);
-        if (p == messages_.end())
+
+        auto& m = deviceId.empty() ? messages_ : messagesDevices_;
+        auto p = m.find(deviceId.empty() ? peer : deviceId);
+        if (p == m.end())
             return;
         auto& messages = p->second;
+
         for (auto m = messages.begin(); m != messages.end(); ++m) {
             if (m->second.status == MessageStatus::UNKNOWN
                 || m->second.status == MessageStatus::IDLE) {
@@ -118,7 +125,7 @@ MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout)
                 p.to,
                 std::to_string(p.token),
                 (int) libjami::Account::MessageStates::SENDING);
-        account_.sendMessage(p.to, p.payloads, p.token, retryOnTimeout);
+        account_.sendMessage(p.to, deviceId, p.payloads, p.token, retryOnTimeout, false);
     }
 }
 
@@ -159,15 +166,19 @@ MessageEngine::cancel(MessageToken t)
 }
 
 void
-MessageEngine::onMessageSent(const std::string& peer, MessageToken token, bool ok)
+MessageEngine::onMessageSent(const std::string& peer,
+                             MessageToken token,
+                             bool ok,
+                             const std::string& deviceId)
 {
     JAMI_DEBUG("[message {:d}] Message sent: {:s}", token, ok ? "success"sv : "failure"sv);
     std::lock_guard<std::mutex> lock(messagesMutex_);
-    auto p = messages_.find(peer);
-    if (p == messages_.end()) {
-        JAMI_DEBUG("[message {:d}] Can't find peer", token);
+    auto& m = deviceId.empty() ? messages_ : messagesDevices_;
+
+    auto p = m.find(deviceId.empty() ? peer : deviceId);
+    if (p == m.end())
         return;
-    }
+
     auto f = p->second.find(token);
     if (f != p->second.end()) {
         auto emit = f->second.payloads.find("application/im-gitmessage-id")
@@ -304,7 +315,8 @@ MessageEngine::save_() const
                     payloads[p.first] = p.second;
                 peerRoot[to_hex_string(m.first)] = std::move(msg);
             }
-            if (peerRoot.size() == 0) continue;
+            if (peerRoot.size() == 0)
+                continue;
             root[c.first] = std::move(peerRoot);
         }
 
@@ -325,14 +337,11 @@ MessageEngine::save_() const
                     writer->write(root, &file);
             } catch (const std::exception& e) {
                 JAMI_ERROR("[Account {:s}] Couldn't save messages to {:s}: {:s}",
-                         accountID,
-                         path,
-                         e.what());
+                           accountID,
+                           path,
+                           e.what());
             }
-            JAMI_DEBUG("[Account {:s}] saved {:d} messages to {:s}",
-                     accountID,
-                     root.size(),
-                     path);
+            JAMI_DEBUG("[Account {:s}] saved {:d} messages to {:s}", accountID, root.size(), path);
         });
     } catch (const std::exception& e) {
         JAMI_ERR("[Account %s] couldn't save messages to %s: %s",
diff --git a/src/im/message_engine.h b/src/im/message_engine.h
index 9f93fc80d6e8a7a2e459dd9796daeb49f49d9aa9..80de0d3eab0f92e7994e2d41b9c11720edcc4332 100644
--- a/src/im/message_engine.h
+++ b/src/im/message_engine.h
@@ -41,7 +41,15 @@ class MessageEngine
 public:
     MessageEngine(SIPAccountBase&, const std::string& path);
 
+    /**
+     * Add a message to the engine and try to send it
+     * @param to            Uri of the peer
+     * @param deviceId      (Optional) if we want to send to a specific device
+     * @param payloads      The message
+     * @param refreshToken  The token of the message
+     */
     MessageToken sendMessage(const std::string& to,
+                             const std::string& deviceId,
                              const std::map<std::string, std::string>& payloads,
                              uint64_t refreshToken);
 
@@ -51,14 +59,20 @@ public:
 
     bool isSent(MessageToken t) const { return getStatus(t) == MessageStatus::SENT; }
 
-    void onMessageSent(const std::string& peer, MessageToken t, bool success);
+    void onMessageSent(const std::string& peer,
+                       MessageToken t,
+                       bool success,
+                       const std::string& deviceId = {});
+
     void onMessageDisplayed(const std::string& peer, MessageToken t, bool displayed);
 
     /**
      * @TODO change MessageEngine by a queue,
      * @NOTE retryOnTimeout is used for failing SIP messages (jamiAccount::sendTextMessage)
      */
-    void onPeerOnline(const std::string& peer, bool retryOnTimeout = true);
+    void onPeerOnline(const std::string& peer,
+                      bool retryOnTimeout = true,
+                      const std::string& deviceId = {});
 
     /**
      * Load persisted messages
@@ -75,7 +89,10 @@ private:
     static const std::chrono::minutes RETRY_PERIOD;
     using clock = std::chrono::steady_clock;
 
-    void retrySend(const std::string& peer, bool retryOnTimeout = true);
+    void retrySend(const std::string& peer,
+                   bool retryOnTimeout = true,
+                   const std::string& deviceId = {});
+
     void save_() const;
 
     struct Message
@@ -91,6 +108,8 @@ private:
     const std::string savePath_;
 
     std::map<std::string, std::map<MessageToken, Message>> messages_;
+    std::map<std::string, std::map<MessageToken, Message>> messagesDevices_;
+
     std::set<MessageToken> sentMessages_;
 
     mutable std::mutex messagesMutex_ {};
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index f62fc51f418e74a4946dbda215abae2fd0e0f557..dbb736bf93e097270aef763b255ac0b69c4bd2af 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -39,7 +39,7 @@
 namespace jami {
 
 static const char* const LAST_MODIFIED = "lastModified";
-static const auto jsonBuilder =  []{
+static const auto jsonBuilder = [] {
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
@@ -122,7 +122,7 @@ ConversationRequest::toMap() const
 class Conversation::Impl
 {
 public:
-    Impl(const std::weak_ptr<JamiAccount>& account,
+    Impl(const std::shared_ptr<JamiAccount>& account,
          ConversationMode mode,
          const std::string& otherMember = "")
         : repository_(ConversationRepository::createConversation(account, mode, otherMember))
@@ -134,7 +134,7 @@ public:
         init();
     }
 
-    Impl(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId)
+    Impl(const std::shared_ptr<JamiAccount>& account, const std::string& conversationId)
         : account_(account)
     {
         repository_ = std::make_unique<ConversationRepository>(account, conversationId);
@@ -144,7 +144,7 @@ public:
         init();
     }
 
-    Impl(const std::weak_ptr<JamiAccount>& account,
+    Impl(const std::shared_ptr<JamiAccount>& account,
          const std::string& remoteDevice,
          const std::string& conversationId)
         : account_(account)
@@ -153,10 +153,8 @@ public:
                                                                 remoteDevice,
                                                                 conversationId);
         if (!repository_) {
-            if (auto shared = account.lock()) {
-                emitSignal<libjami::ConversationSignal::OnConversationError>(
-                    shared->getAccountID(), conversationId, EFETCH, "Couldn't clone repository");
-            }
+            emitSignal<libjami::ConversationSignal::OnConversationError>(
+                account->getAccountID(), conversationId, EFETCH, "Couldn't clone repository");
             throw std::logic_error("Couldn't clone repository");
         }
         init();
@@ -171,7 +169,10 @@ public:
     void init()
     {
         if (auto shared = account_.lock()) {
+            ioContext_ = Manager::instance().ioContext();
+            fallbackTimer_ = std::make_unique<asio::steady_timer>(*ioContext_);
             swarmManager_ = std::make_shared<SwarmManager>(NodeId(shared->currentDeviceId()));
+            swarmManager_->setMobility(shared->isMobile());
             accountId_ = shared->getAccountID();
             transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(),
                                                                  repository_->id());
@@ -194,6 +195,27 @@ public:
         }
     }
 
+    const std::string& toString() const
+    {
+        if (fmtStr_.empty()) {
+            if (repository_->mode() == ConversationMode::ONE_TO_ONE) {
+                if (auto acc = account_.lock()) {
+                    auto peer = acc->getUsername();
+                    for (const auto& member : repository_->getInitialMembers()) {
+                        if (member != acc->getUsername()) {
+                            peer = member;
+                        }
+                    }
+                    fmtStr_ = fmt::format("[Conversation (1:1) {}]", peer);
+                }
+            } else {
+                fmtStr_ = fmt::format("[Conversation {}]", repository_->id());
+            }
+        }
+        return fmtStr_;
+    }
+    mutable std::string fmtStr_;
+
     /**
      * If, for whatever reason, the daemon is stopped while hosting a conference,
      * we need to announce the end of this call when restarting.
@@ -201,7 +223,15 @@ public:
      */
     std::vector<std::string> announceEndedCalls();
 
-    ~Impl() = default;
+    ~Impl()
+    {
+        try {
+            if (fallbackTimer_)
+                fallbackTimer_->cancel();
+        } catch (const std::exception& e) {
+            JAMI_ERROR("[Conversation {:s}] {:s}", toString(), e.what());
+        }
+    }
 
     std::vector<std::string> refreshActiveCalls();
     bool isAdmin() const;
@@ -272,11 +302,12 @@ public:
             auto device = commit.at("device");
             std::lock_guard<std::mutex> lk(activeCallsMtx_);
             auto itActive = std::find_if(activeCalls_.begin(),
-                                            activeCalls_.end(),
-                                                [&](const auto& value) {
-                                                    return value.at("id") == confId && value.at("uri") == uri
-                                                        && value.at("device") == device;
-                                                });
+                                         activeCalls_.end(),
+                                         [&](const auto& value) {
+                                             return value.at("id") == confId
+                                                    && value.at("uri") == uri
+                                                    && value.at("device") == device;
+                                         });
             if (commit.find("duration") == commit.end()) {
                 if (itActive == activeCalls_.end() && !eraseOnly) {
                     JAMI_DEBUG(
@@ -300,18 +331,16 @@ public:
                     itActive = activeCalls_.erase(itActive);
                     // Unlikely, but we must ensure that no duplicate exists
                     while (itActive != activeCalls_.end()) {
-                        itActive = std::find_if(itActive, activeCalls_.end(),
-                                                [&](const auto& value) {
-                                                    return value.at("id") == confId && value.at("uri") == uri
-                                                        && value.at("device") == device;
-                                                });
+                        itActive = std::find_if(itActive, activeCalls_.end(), [&](const auto& value) {
+                            return value.at("id") == confId && value.at("uri") == uri
+                                   && value.at("device") == device;
+                        });
                         if (itActive != activeCalls_.end()) {
                             JAMI_ERROR("Duplicate call found. (This is a bug)");
                             itActive = activeCalls_.erase(itActive);
                         }
                     }
 
-
                     if (eraseOnly) {
                         JAMI_WARNING("previous swarm:{:s} call finished detected: {:s} on device "
                                      "{:s}, account {:s}",
@@ -523,7 +552,8 @@ public:
     void voteUnban(const std::string& contactUri, const std::string_view type, const OnDoneCb& cb);
 
     std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited,
-                                                               bool includeLeft, bool includeBanned) const;
+                                                               bool includeLeft,
+                                                               bool includeBanned) const;
 
     std::string_view bannedType(const std::string& uri) const
     {
@@ -562,12 +592,15 @@ public:
     std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited,
                                                                bool includeLeft) const;
 
-    std::shared_ptr<SwarmManager> swarmManager_;
     std::set<std::string> checkedMembers_; // Store members we tried
     std::function<void()> bootstrapCb_;
+#ifdef LIBJAMI_TESTABLE
+    std::function<void(std::string, BootstrapStatus)> bootstrapCbTest_;
+#endif
 
     std::mutex writeMtx_ {};
     std::unique_ptr<ConversationRepository> repository_;
+    std::shared_ptr<SwarmManager> swarmManager_;
     std::weak_ptr<JamiAccount> account_;
     std::atomic_bool isRemoving_ {false};
     std::vector<std::map<std::string, std::string>> loadMessages(const LogOptions& options);
@@ -604,6 +637,12 @@ public:
     mutable std::vector<std::map<std::string, std::string>> activeCalls_ {};
 
     GitSocketList gitSocketList_ {};
+
+    // Bootstrap
+    std::shared_ptr<asio::io_context> ioContext_;
+    std::unique_ptr<asio::steady_timer> fallbackTimer_;
+
+    bool isMobile {false};
 };
 
 bool
@@ -719,18 +758,18 @@ Conversation::Impl::loadMessages(const LogOptions& options)
     return repository_->convCommitToMap(convCommits);
 }
 
-Conversation::Conversation(const std::weak_ptr<JamiAccount>& account,
+Conversation::Conversation(const std::shared_ptr<JamiAccount>& account,
                            ConversationMode mode,
                            const std::string& otherMember)
     : pimpl_ {new Impl {account, mode, otherMember}}
 {}
 
-Conversation::Conversation(const std::weak_ptr<JamiAccount>& account,
+Conversation::Conversation(const std::shared_ptr<JamiAccount>& account,
                            const std::string& conversationId)
     : pimpl_ {new Impl {account, conversationId}}
 {}
 
-Conversation::Conversation(const std::weak_ptr<JamiAccount>& account,
+Conversation::Conversation(const std::shared_ptr<JamiAccount>& account,
                            const std::string& remoteDevice,
                            const std::string& conversationId)
     : pimpl_ {new Impl {account, remoteDevice, conversationId}}
@@ -807,11 +846,7 @@ Conversation::gitSocket(const DeviceId& deviceId) const
 {
     return pimpl_->gitSocket(deviceId);
 }
-/*bool
-Conversation::hasGitSocket(const DeviceId& deviceId) const
-{
-    return pimpl_->hasGitSocket(deviceId);
-}*/
+
 void
 Conversation::addGitSocket(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket)
 {
@@ -828,6 +863,20 @@ void
 Conversation::removeGitSockets()
 {
     pimpl_->gitSocketList_.clear();
+    pimpl_->swarmManager_->shutdown();
+    pimpl_->checkedMembers_.clear();
+}
+
+void
+Conversation::connectivityChanged()
+{
+    pimpl_->swarmManager_->maintainBuckets();
+}
+
+bool
+Conversation::hasSwarmChannel(const std::string& deviceId)
+{
+    return pimpl_->swarmManager_->isConnectedWith(DeviceId(deviceId));
 }
 
 void
@@ -928,7 +977,23 @@ Conversation::removeMember(const std::string& contactUri, bool isDevice, const O
                 JAMI_WARN("Vote solved for %s. %s banned",
                           contactUri.c_str(),
                           isDevice ? "Device" : "Member");
+
+                const auto nodes = sthis->pimpl_->swarmManager_->getRoutingTable().getAllNodes();
+                // Remove nodes from swarmManager
+                std::vector<NodeId> toRemove;
+                for (const auto node : nodes)
+                    if (contactUri == sthis->uriFromDevice(node.toString()))
+                        toRemove.emplace_back(node);
+                sthis->pimpl_->swarmManager_->deleteNode(toRemove);
+                // Remove git sockets with this member
+                std::vector<DeviceId> gitToRm;
+                for (const auto& [deviceId, _] : sthis->pimpl_->gitSocketList_)
+                    if (contactUri == sthis->uriFromDevice(deviceId.toString()))
+                        gitToRm.emplace_back(deviceId);
+                for (const auto& did: gitToRm)
+                    sthis->removeGitSocket(did);
             }
+
             sthis->pimpl_->announce(commits);
             lk.unlock();
             cb(!lastId.empty(), lastId);
@@ -958,9 +1023,31 @@ Conversation::peersToSyncWith() const
     s.reserve(nodes.size() + mobiles.size());
     s.insert(s.end(), nodes.begin(), nodes.end());
     s.insert(s.end(), mobiles.begin(), mobiles.end());
+    for (const auto& [deviceId, _] : pimpl_->gitSocketList_)
+        if (std::find(s.cbegin(), s.cend(), deviceId) == s.cend())
+            s.emplace_back(deviceId);
     return s;
 }
 
+bool
+Conversation::isBoostraped() const
+{
+    const auto& routingTable = pimpl_->swarmManager_->getRoutingTable();
+    return !routingTable.getNodes().empty();
+}
+
+std::string
+Conversation::uriFromDevice(const std::string& deviceId) const
+{
+    return pimpl_->repository_->uriFromDevice(deviceId);
+}
+
+void
+Conversation::monitor()
+{
+    pimpl_->swarmManager_->getRoutingTable().printRoutingTable();
+}
+
 std::string
 Conversation::join()
 {
@@ -1280,11 +1367,9 @@ Conversation::sync(const std::string& member,
     if (auto account = pimpl_->account_.lock()) {
         // For waiting request, downloadFile
         for (const auto& wr : dataTransfer()->waitingRequests()) {
-            auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR
-                        + account->getAccountID() + DIR_SEPARATOR_STR
-                        + "conversation_data" + DIR_SEPARATOR_STR
-                        + id() + DIR_SEPARATOR_STR
-                        + wr.fileId;
+            auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
+                        + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + id()
+                        + DIR_SEPARATOR_STR + wr.fileId;
             auto start = fileutils::size(path);
             if (start < 0)
                 start = 0;
@@ -1595,7 +1680,9 @@ Conversation::setMessageDisplayed(const std::string& uri, const std::string& int
 void
 Conversation::updateLastDisplayed(const std::map<std::string, std::string>& map)
 {
-    auto filePath = fmt::format("{}/{}", pimpl_->conversationDataPath_, ConversationMapKeys::LAST_DISPLAYED);
+    auto filePath = fmt::format("{}/{}",
+                                pimpl_->conversationDataPath_,
+                                ConversationMapKeys::LAST_DISPLAYED);
     auto prefs = map;
     auto itLast = prefs.find(LAST_MODIFIED);
     if (itLast != prefs.end()) {
@@ -1611,7 +1698,7 @@ Conversation::updateLastDisplayed(const std::map<std::string, std::string>& map)
         prefs.erase(itLast);
     }
 
-    for (const auto& [uri, id]: prefs)
+    for (const auto& [uri, id] : prefs)
         setMessageDisplayed(uri, id);
 }
 
@@ -1670,7 +1757,9 @@ Conversation::displayed() const
 {
     try {
         std::map<std::string, std::string> lastDisplayed;
-        auto filePath = fmt::format("{}/{}", pimpl_->conversationDataPath_, ConversationMapKeys::LAST_DISPLAYED);
+        auto filePath = fmt::format("{}/{}",
+                                    pimpl_->conversationDataPath_,
+                                    ConversationMapKeys::LAST_DISPLAYED);
         auto file = fileutils::loadFile(filePath);
         msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
         oh.get().convert(lastDisplayed);
@@ -1681,19 +1770,145 @@ Conversation::displayed() const
     return {};
 }
 
+#ifdef LIBJAMI_TESTABLE
+void
+Conversation::onBootstrapStatus(const std::function<void(std::string, BootstrapStatus)>& cb)
+{
+    pimpl_->bootstrapCbTest_ = cb;
+}
+#endif
+
+void
+Conversation::checkBootstrapMember(const asio::error_code& ec,
+                                   std::vector<std::map<std::string, std::string>> members)
+{
+    if (ec == asio::error::operation_aborted
+        or pimpl_->swarmManager_->getRoutingTable().getNodes().size() > 0)
+        return;
+    // We bootstrap the DRT with devices who already wrote in the repository.
+    // However, in a conversation, a large number of devices may just watch
+    // the conversation, but never write any message.
+    auto acc = pimpl_->account_.lock();
+    std::string uri;
+    while (!members.empty()) {
+        auto member = members.back();
+        members.pop_back();
+        auto& uri = member.at("uri");
+        if (uri != acc->getUsername()
+            && pimpl_->checkedMembers_.find(uri) == pimpl_->checkedMembers_.end())
+            break;
+    }
+    // If members is empty, we finished the fallback un-successfully
+    if (members.empty() && uri.empty()) {
+        JAMI_WARNING("{}[SwarmManager {}] Bootstrap: Fallback failed. Wait for remote connections.",
+                     pimpl_->toString(),
+                     fmt::ptr(pimpl_->swarmManager_.get()));
+#ifdef LIBJAMI_TESTABLE
+        if (pimpl_->bootstrapCbTest_)
+            pimpl_->bootstrapCbTest_(id(), BootstrapStatus::FAILED);
+#endif
+        return;
+    }
+
+    // Fallback, check devices of a member (we didn't check yet) in the conversation
+    pimpl_->checkedMembers_.emplace(uri);
+    auto devices = std::make_shared<std::vector<NodeId>>();
+    acc->forEachDevice(
+        dht::InfoHash(uri),
+        [w = weak(), devices](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
+            // Test if already sent
+            if (auto sthis = w.lock()) {
+                if (!sthis->pimpl_->swarmManager_->getRoutingTable().hasKnownNode(dev->getLongId()))
+                    devices->emplace_back(dev->getLongId());
+            }
+        },
+        [w = weak(), devices, members = std::move(members), uri](bool ok) {
+            auto sthis = w.lock();
+            if (!sthis)
+                return;
+            if (ok && devices->size() != 0) {
+#ifdef LIBJAMI_TESTABLE
+                if (sthis->pimpl_->bootstrapCbTest_)
+                    sthis->pimpl_->bootstrapCbTest_(sthis->id(), BootstrapStatus::FALLBACK);
+#endif
+                JAMI_WARNING("{}[SwarmManager {}] Bootstrap: Fallback with member: {}",
+                             sthis->pimpl_->toString(),
+                             fmt::ptr(sthis->pimpl_->swarmManager_.get()),
+                             uri);
+                sthis->pimpl_->swarmManager_->setKnownNodes(*devices);
+            } else {
+                // Check next member
+                sthis->pimpl_->fallbackTimer_->expires_at(std::chrono::steady_clock::now());
+                sthis->pimpl_->fallbackTimer_->async_wait(
+                    std::bind(&Conversation::checkBootstrapMember,
+                              sthis.get(),
+                              std::placeholders::_1,
+                              std::move(members)));
+            }
+        });
+}
+
 void
 Conversation::bootstrap(std::function<void()> onBootstraped)
 {
     if (!pimpl_ || !pimpl_->repository_ || !pimpl_->swarmManager_)
         return;
+    // Here, we bootstrap the DRT with devices who already wrote in the conversation
+    // If this doesn't work, it will try to fallback with checkBootstrapMember
+    // If it works, the callback onConnectionChanged will be called with ok=true
     pimpl_->bootstrapCb_ = std::move(onBootstraped);
     std::vector<DeviceId> devices;
     for (const auto& m : pimpl_->repository_->devices())
         devices.insert(devices.end(), m.second.begin(), m.second.end());
     // Add known devices
-    JAMI_DEBUG("[SwarmManager {}] Bootstrap with {} devices",
+    if (auto acc = pimpl_->account_.lock()) {
+        for (const auto& [id, _] : acc->getKnownDevices()) {
+            devices.emplace_back(id);
+        }
+    }
+    JAMI_DEBUG("{}[SwarmManager {}] Bootstrap with {} devices",
+               pimpl_->toString(),
                fmt::ptr(pimpl_->swarmManager_.get()),
                devices.size());
+    // set callback
+    pimpl_->swarmManager_->onConnectionChanged([w = weak()](bool ok) {
+        // This will call methods from accounts, so trigger on another thread.
+        dht::ThreadPool::io().run([w, ok] {
+            auto sthis = w.lock();
+            if (!sthis)
+                return;
+            if (ok) {
+                // Bootstrap succeeded!
+                sthis->pimpl_->checkedMembers_.clear();
+                if (sthis->pimpl_->bootstrapCb_)
+                    sthis->pimpl_->bootstrapCb_();
+#ifdef LIBJAMI_TESTABLE
+                if (sthis->pimpl_->bootstrapCbTest_)
+                    sthis->pimpl_->bootstrapCbTest_(sthis->id(), BootstrapStatus::SUCCESS);
+#endif
+                return;
+            }
+            // Fallback
+            auto acc = sthis->pimpl_->account_.lock();
+            if (!acc)
+                return;
+            auto members = sthis->getMembers(false, false);
+            std::shuffle(members.begin(), members.end(), acc->rand);
+            // TODO decide a formula
+            auto timeForBootstrap = std::min(static_cast<size_t>(8), members.size());
+            JAMI_DEBUG("{}[SwarmManager {}] Fallback in {} seconds",
+                       sthis->pimpl_->toString(),
+                       fmt::ptr(sthis->pimpl_->swarmManager_.get()),
+                       (20 - timeForBootstrap));
+            sthis->pimpl_->fallbackTimer_->expires_at(std::chrono::steady_clock::now() + 20s
+                                                      - std::chrono::seconds(timeForBootstrap));
+            sthis->pimpl_->fallbackTimer_->async_wait(std::bind(&Conversation::checkBootstrapMember,
+                                                                sthis.get(),
+                                                                std::placeholders::_1,
+                                                                std::move(members)));
+        });
+    });
+    pimpl_->checkedMembers_.clear();
     pimpl_->swarmManager_->setKnownNodes(devices);
 }
 
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 10dd08b451db7ee6a1bdd02d769030e581e11c53..2f32a3b444f95efa2b26e047eece3aa4c7f6537e 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -33,6 +33,8 @@
 #include <memory>
 #include <set>
 
+#include <asio.hpp>
+
 namespace jami {
 
 namespace ConversationMapKeys {
@@ -126,18 +128,32 @@ using NeedSocketCb
 class Conversation : public std::enable_shared_from_this<Conversation>
 {
 public:
-    Conversation(const std::weak_ptr<JamiAccount>& account,
+    Conversation(const std::shared_ptr<JamiAccount>& account,
                  ConversationMode mode,
                  const std::string& otherMember = "");
-    Conversation(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId = "");
-    Conversation(const std::weak_ptr<JamiAccount>& account,
+    Conversation(const std::shared_ptr<JamiAccount>& account,
+                 const std::string& conversationId = "");
+    Conversation(const std::shared_ptr<JamiAccount>& account,
                  const std::string& remoteDevice,
                  const std::string& conversationId);
     ~Conversation();
 
+    /**
+     * Print the state of the DRT linked to the conversation
+     */
+    void monitor();
+
+#ifdef LIBJAMI_TESTABLE
+    enum class BootstrapStatus { FAILED, FALLBACK, SUCCESS };
+    /**
+     * Used by the tests to get whenever the DRT is connected/disconnected
+     */
+    void onBootstrapStatus(const std::function<void(std::string, BootstrapStatus)>& cb);
+#endif
+
     /**
      * Bootstrap swarm manager to other peers
-     * @param onBootstrapped    Callback called when connection is successfully established
+     * @param onBootstraped     Callback called when connection is successfully established
      */
     void bootstrap(std::function<void()> onBootstraped);
 
@@ -486,10 +502,26 @@ public:
      */
     std::vector<std::map<std::string, std::string>> currentCalls() const;
 
+    /**
+     * Git operations will need a ChannelSocket for cloning/fetching commits
+     * Because libgit2 is a C library, we store the pointer in the corresponding conversation
+     * and the GitTransport will inject to libgit2 whenever needed
+     */
     std::shared_ptr<ChannelSocket> gitSocket(const DeviceId& deviceId) const;
     void addGitSocket(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket);
     void removeGitSocket(const DeviceId& deviceId);
     void removeGitSockets();
+    /**
+     * Used to avoid multiple connections, we just check if we got a swarm channel with a specific
+     * device
+     * @param deviceId
+     */
+    bool hasSwarmChannel(const std::string& deviceId);
+
+    /**
+     * If we change from one network to one another, we will need to update the state of the connections
+     */
+    void connectivityChanged();
 
 private:
     std::shared_ptr<Conversation> shared()
@@ -509,6 +541,15 @@ private:
         return std::static_pointer_cast<Conversation const>(shared_from_this());
     }
 
+    // Private because of weak()
+    /**
+     * Used by bootstrap() to launch the fallback
+     * @param ec
+     * @param members       Members to try to connect
+     */
+    void checkBootstrapMember(const asio::error_code& ec,
+                              std::vector<std::map<std::string, std::string>> members);
+
     class Impl;
     std::unique_ptr<Impl> pimpl_;
 };
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index d1d8736aca664e08abe53301674ff00eca514a91..84f0e06cd51634fb0eddd14563a653b4c37cf7ec 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -898,8 +898,9 @@ ConversationModule::Impl::sendMessageNotification(Conversation& conversation,
     {
         std::lock_guard<std::mutex> lk(notSyncedNotificationMtx_);
         devices = conversation.peersToSyncWith();
-        auto members = conversation.memberUris(username_, {});
+        auto members = conversation.memberUris(username_, {MemberRole::BANNED});
         std::vector<std::string> connectedMembers;
+        // print all members
         for (const auto& device : devices) {
             auto cert = tls::CertificateStore::instance().getCertificate(device.toString());
             if (cert && cert->issuer)
@@ -1142,10 +1143,10 @@ ConversationModule::loadConversations()
                 auto convFromDetails = getOneToOneConversation(otherUri);
                 if (convFromDetails != repository) {
                     if (convFromDetails.empty()) {
-                        JAMI_ERROR(
-                            "No conversation detected for {} but one exists ({}). Update details",
-                            otherUri,
-                            repository);
+                        JAMI_ERROR("No conversation detected for {} but one exists ({}). "
+                                   "Update details",
+                                   otherUri,
+                                   repository);
                         acc->updateConvForContact(otherUri, convFromDetails, repository);
                     } else {
                         JAMI_ERROR("Multiple conversation detected for {} but ({} & {})",
@@ -1292,12 +1293,12 @@ ConversationModule::clearPendingFetch()
 {
     if (!pimpl_->pendingConversationsFetch_.empty()) {
         // Note: This is a fallback. convModule() is kept if account is disabled/re-enabled.
-        // iOS uses setAccountActive() a lot, and if for some reason the previous pending fetch is
-        // not erased (callback not called), it will block the new messages as it will not sync. The
-        // best way to debug this is to get logs from the last ICE connection for syncing the
-        // conversation. It may have been killed in some un-expected way avoiding to call the
-        // callbacks. This should never happen, but if it's the case, this will allow new messages
-        // to be synced correctly.
+        // iOS uses setAccountActive() a lot, and if for some reason the previous pending fetch
+        // is not erased (callback not called), it will block the new messages as it will not
+        // sync. The best way to debug this is to get logs from the last ICE connection for
+        // syncing the conversation. It may have been killed in some un-expected way avoiding to
+        // call the callbacks. This should never happen, but if it's the case, this will allow
+        // new messages to be synced correctly.
         JAMI_ERR("This is a bug, seems to still fetch to some device on initializing");
         pimpl_->pendingConversationsFetch_.clear();
     }
@@ -1839,7 +1840,8 @@ ConversationModule::onSyncData(const SyncMsg& msg,
             auto itConv = pimpl_->convInfos_.find(convId);
             if (itConv != pimpl_->convInfos_.end() && itConv->second.removed) {
                 if (itConv->second.removed > convInfo.created) {
-                    // Only reclone if re-added, else the peer is not synced yet (could be offline before)
+                    // Only reclone if re-added, else the peer is not synced yet (could be
+                    // offline before)
                     continue;
                 }
                 JAMI_DEBUG("Re-add previously removed conversation {:s}", convId);
@@ -2020,8 +2022,8 @@ ConversationModule::addConversationMember(const std::string& conversationId,
 
     if (it->second->isMember(contactUri, true)) {
         JAMI_DEBUG("{:s} is already a member of {:s}, resend invite", contactUri, conversationId);
-        // Note: This should not be necessary, but if for whatever reason the other side didn't join
-        // we should not forbid new invites
+        // Note: This should not be necessary, but if for whatever reason the other side didn't
+        // join we should not forbid new invites
         auto invite = it->second->generateInvitation();
         lk.unlock();
         pimpl_->sendMsgCb_(contactUri, {}, std::move(invite), 0);
@@ -2524,8 +2526,8 @@ ConversationModule::hostConference(const std::string& conversationId,
                          });
 
     // When conf finished = remove host & commit
-    // Master call, so when it's stopped, the conference will be stopped (as we use the hold state
-    // for detaching the call)
+    // Master call, so when it's stopped, the conference will be stopped (as we use the hold
+    // state for detaching the call)
     conf->onShutdown(
         [w = pimpl_->weak(), accountUri = pimpl_->username_, confId, conversationId, call](
             int duration) {
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index e02fb46d0096c8fd2563cfc9fbf446b49b559900..9b7d68fa586aa2cf1d7af7307ff006ded5ba7953 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -370,8 +370,8 @@ public:
      */
     void setConversationPreferences(const std::string& conversationId,
                                     const std::map<std::string, std::string>& prefs);
-    std::map<std::string, std::string> getConversationPreferences(
-        const std::string& conversationId, bool includeCreated = false) const;
+    std::map<std::string, std::string> getConversationPreferences(const std::string& conversationId,
+                                                                  bool includeCreated = false) const;
     /**
      * Retrieve all conversation preferences to sync with other devices
      */
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 7f6174de061d70b542157714fef868844526b732..79909c13d8e55dc23330b7e50ef83dcc76743b8b 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -93,7 +93,8 @@ public:
         return {std::move(repo), git_repository_free};
     }
 
-    std::string getDisplayName() const {
+    std::string getDisplayName() const
+    {
         auto shared = account_.lock();
         if (!shared)
             return {};
@@ -2060,7 +2061,6 @@ ConversationRepository::Impl::log(const LogOptions& options) const
             if (options.skipMerge && git_commit_parentcount(commit.get()) > 1) {
                 return CallbackResult::Skip;
             }
-
             if ((options.nbOfCommits != 0 && commits.size() == options.nbOfCommits))
                 return CallbackResult::Break; // Stop logging
             if (breakLogging)
@@ -2455,17 +2455,14 @@ ConversationRepository::Impl::diffStats(const GitDiff& diff) const
 //////////////////////////////////
 
 std::unique_ptr<ConversationRepository>
-ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& account,
+ConversationRepository::createConversation(const std::shared_ptr<JamiAccount>& account,
                                            ConversationMode mode,
                                            const std::string& otherMember)
 {
-    auto shared = account.lock();
-    if (!shared)
-        return {};
     // Create temporary directory because we can't know the first hash for now
     std::uniform_int_distribution<uint64_t> dist {0, std::numeric_limits<uint64_t>::max()};
     random_device rdev;
-    auto conversationsPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID()
+    auto conversationsPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
                              + DIR_SEPARATOR_STR + "conversations";
     fileutils::check_dir(conversationsPath.c_str());
     auto tmpPath = conversationsPath + DIR_SEPARATOR_STR + std::to_string(dist(rdev));
@@ -2483,14 +2480,14 @@ ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& acc
     }
 
     // Add initial files
-    if (!add_initial_files(repo, shared, mode, otherMember)) {
+    if (!add_initial_files(repo, account, mode, otherMember)) {
         JAMI_ERR("Error when adding initial files");
         fileutils::removeAll(tmpPath, true);
         return {};
     }
 
     // Commit changes
-    auto id = initial_commit(repo, shared, mode, otherMember);
+    auto id = initial_commit(repo, account, mode, otherMember);
     if (id.empty()) {
         JAMI_ERR("Couldn't create initial commit in %s", tmpPath.c_str());
         fileutils::removeAll(tmpPath, true);
@@ -2511,20 +2508,15 @@ ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& acc
 }
 
 std::unique_ptr<ConversationRepository>
-ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& account,
+ConversationRepository::cloneConversation(const std::shared_ptr<JamiAccount>& account,
                                           const std::string& deviceId,
                                           const std::string& conversationId)
 {
-    auto shared = account.lock();
-    if (!shared)
-        return {};
-    auto conversationsPath = fileutils::get_data_dir() + "/" + shared->getAccountID() + "/"
+    auto conversationsPath = fileutils::get_data_dir() + "/" + account->getAccountID() + "/"
                              + "conversations";
     fileutils::check_dir(conversationsPath.c_str());
     auto path = conversationsPath + "/" + conversationId;
-    git_repository* rep = nullptr;
-    std::stringstream url;
-    url << "git://" << deviceId << '/' << conversationId;
+    auto url = fmt::format("git://{}/{}", deviceId, conversationId);
 
     git_clone_options clone_options;
     git_clone_options_init(&clone_options, GIT_CLONE_OPTIONS_VERSION);
@@ -2533,11 +2525,14 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco
                                                               void*) {
         // Uncomment to get advancment
         // if (stats->received_objects % 500 == 0 || stats->received_objects == stats->total_objects)
-        //     JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, stats->received_bytes/1024);
+        //     JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects,
+        //     stats->received_bytes/1024);
         // If a pack is more than 256Mb, it's anormal.
         if (stats->received_bytes > MAX_FETCH_SIZE) {
             JAMI_ERROR("Abort fetching repository, the fetch is too big: {} bytes ({}/{})",
-                stats->received_bytes, stats->received_objects, stats->total_objects);
+                       stats->received_bytes,
+                       stats->received_objects,
+                       stats->total_objects);
             return -1;
         }
         return 0;
@@ -2549,11 +2544,13 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco
         fileutils::removeAll(path, true);
     }
 
-    JAMI_INFO("Start clone in %s", path.c_str());
-    if (git_clone(&rep, url.str().c_str(), path.c_str(), nullptr) < 0) {
-        const git_error* err = giterr_last();
-        if (err)
-            JAMI_ERR("Error when retrieving remote conversation: %s %s", err->message, path.c_str());
+    JAMI_DEBUG("Start clone of {:s} to {:s}", url, path);
+    git_repository* rep = nullptr;
+    if (auto err = git_clone(&rep, url.c_str(), path.c_str(), nullptr)) {
+        if (const git_error* gerr = giterr_last())
+            JAMI_ERROR("Error when retrieving remote conversation: {:s} {:s}", gerr->message, path);
+        else
+            JAMI_ERROR("Unknown error {:d} when retrieving remote conversation", err);
         return nullptr;
     }
     git_repository_free(rep);
@@ -2930,11 +2927,14 @@ ConversationRepository::fetch(const std::string& remoteDeviceId)
     fetch_opts.callbacks.transfer_progress = [](const git_indexer_progress* stats, void*) {
         // Uncomment to get advancment
         // if (stats->received_objects % 500 == 0 || stats->received_objects == stats->total_objects)
-        //     JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, stats->received_bytes/1024);
+        //     JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects,
+        //     stats->received_bytes/1024);
         // If a pack is more than 256Mb, it's anormal.
         if (stats->received_bytes > MAX_FETCH_SIZE) {
             JAMI_ERROR("Abort fetching repository, the fetch is too big: {} bytes ({}/{})",
-                stats->received_bytes, stats->received_objects, stats->total_objects);
+                       stats->received_bytes,
+                       stats->received_objects,
+                       stats->total_objects);
             return -1;
         }
         return 0;
@@ -3702,6 +3702,12 @@ ConversationRepository::pinCertificates(bool blocking)
     }
 }
 
+std::string
+ConversationRepository::uriFromDevice(const std::string& deviceId) const
+{
+    return pimpl_->uriFromDevice(deviceId);
+}
+
 std::string
 ConversationRepository::updateInfos(const std::map<std::string, std::string>& profile)
 {
@@ -3799,7 +3805,8 @@ ConversationRepository::infos() const
             std::map<std::string, std::string> result;
             if (fileutils::isFile(profilePath)) {
                 auto content = fileutils::loadFile(profilePath);
-                result = ConversationRepository::infosFromVCard(vCard::utils::toMap(std::string_view {(const char*)content.data(), content.size()}));
+                result = ConversationRepository::infosFromVCard(vCard::utils::toMap(
+                    std::string_view {(const char*) content.data(), content.size()}));
             }
             result["mode"] = std::to_string(static_cast<int>(mode()));
             return result;
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index 0178c7f7ab045213698fa327d31a6bbbd52eb97c..d49777711098eefeb98fd1587ca4c151d06cb728 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -59,8 +59,9 @@ struct LogOptions
 {
     std::string from {};
     std::string to {};
-    uint64_t nbOfCommits {0}; // maximum number of commits wanted
-    bool skipMerge {false};    // Do not include merge commits in the log. Used by the module to get last interaction without potential merges
+    uint64_t nbOfCommits {0};  // maximum number of commits wanted
+    bool skipMerge {false};    // Do not include merge commits in the log. Used by the module to get
+                               // last interaction without potential merges
     bool includeTo {false};    // If we want or not the "to" commit [from-to] or [from-to)
     bool fastLog {false};      // Do not parse content, used mostly to count
     bool logIfNotFound {true}; // Add a warning in the log if commit is not found
@@ -140,7 +141,7 @@ public:
      * @return  the conversation repository object
      */
     static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> createConversation(
-        const std::weak_ptr<JamiAccount>& account,
+        const std::shared_ptr<JamiAccount>& account,
         ConversationMode mode = ConversationMode::INVITES_ONLY,
         const std::string& otherMember = "");
 
@@ -153,7 +154,7 @@ public:
      * @param socket            Socket used to clone
      */
     static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> cloneConversation(
-        const std::weak_ptr<JamiAccount>& account,
+        const std::shared_ptr<JamiAccount>& account,
         const std::string& deviceId,
         const std::string& conversationId);
 
@@ -372,6 +373,13 @@ public:
      * @param blocking      if we need to wait that certificates are pinned
      */
     void pinCertificates(bool blocking = false);
+    /**
+     * Retrieve the uri from a deviceId
+     * @note used by swarm manager (peersToSyncWith)
+     * @param deviceId
+     * @return corresponding issuer
+     */
+    std::string uriFromDevice(const std::string& deviceId) const;
 
     /**
      * Change repository's infos
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 58446e3e746a5a50a4468225e0c40bfe658f2090..ad6e8501273f53d54a8270ffe1db7e16d87ea611 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -1356,7 +1356,6 @@ JamiAccount::getVolatileAccountDetails() const
     return a;
 }
 
-
 #if HAVE_RINGNS
 void
 JamiAccount::lookupName(const std::string& name)
@@ -1496,8 +1495,8 @@ JamiAccount::registerAsyncOps()
                 const auto& accId = accPtr->getAccountID();
 
                 JAMI_LOG("[Account {:s}] DHT UPNP mapping changed to {:s}",
-                          accId,
-                          mapRes->toString(true));
+                         accId,
+                         mapRes->toString(true));
 
                 if (*update) {
                     // Check if we need to update the mapping and the registration.
@@ -1512,10 +1511,11 @@ JamiAccount::registerAsyncOps()
                             // Update the mapping and restart the registration.
                             dhtMap.updateFrom(mapRes);
 
-                            JAMI_WARNING("[Account {:s}] Allocated port changed to {}. Restarting the "
-                                      "registration",
-                                      accId,
-                                      accPtr->dhtPortUsed());
+                            JAMI_WARNING(
+                                "[Account {:s}] Allocated port changed to {}. Restarting the "
+                                "registration",
+                                accId,
+                                accPtr->dhtPortUsed());
 
                             accPtr->dht_->connectivityChanged();
 
@@ -1529,14 +1529,15 @@ JamiAccount::registerAsyncOps()
                     // Set connection info and load the account.
                     if (mapRes->getState() == upnp::MappingState::OPEN) {
                         dhtMap.updateFrom(mapRes);
-                        JAMI_LOG("[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
-                                 accId,
-                                 dhtMap.toString());
-                    } else {
-                        JAMI_WARNING(
-                            "[Account {:s}] Mapping request is in {:s} state: starting the DHT anyway",
+                        JAMI_LOG(
+                            "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
                             accId,
-                            mapRes->getStateStr());
+                            dhtMap.toString());
+                    } else {
+                        JAMI_WARNING("[Account {:s}] Mapping request is in {:s} state: starting "
+                                     "the DHT anyway",
+                                     accId,
+                                     mapRes->getStateStr());
                     }
 
                     // Load the account and start the DHT.
@@ -1564,7 +1565,8 @@ JamiAccount::doRegister()
 {
     std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
     if (not isUsable()) {
-        JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID());
+        JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring",
+                     getAccountID());
         return;
     }
 
@@ -1816,11 +1818,14 @@ JamiAccount::doRegister_()
             config.proxy_server = proxyServerCached_;
 
         if (not config.proxy_server.empty()) {
-            JAMI_LOG("[Account {}] using proxy server {}",
-                      getAccountID(),
-                      config.proxy_server);
+            JAMI_LOG("[Account {}] using proxy server {}", getAccountID(), config.proxy_server);
             if (not config.push_token.empty()) {
-                JAMI_LOG("[Account %s] using push notifications with platform: {}, topic: {}, token: {}", getAccountID(), config.push_platform, config.push_topic, config.push_token);
+                JAMI_LOG(
+                    "[Account %s] using push notifications with platform: {}, topic: {}, token: {}",
+                    getAccountID(),
+                    config.push_platform,
+                    config.push_topic,
+                    config.push_token);
             }
         }
 
@@ -1939,6 +1944,14 @@ JamiAccount::doRegister_()
                 },
                 [this] {
                     deviceAnnounced_ = true;
+
+                    // Bootstrap at the end to avoid to be long to load.
+                    dht::ThreadPool::io().run([w = weak()] {
+                        if (auto shared = w.lock()) {
+                            std::lock_guard<std::recursive_mutex> lock(shared->configurationMutex_);
+                            shared->convModule()->bootstrap();
+                        }
+                    });
                     emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                         accountID_, getVolatileAccountDetails());
                 });
@@ -2023,12 +2036,13 @@ JamiAccount::doRegister_()
 
                     // Check if pull from banned device
                     if (convModule()->isBannedDevice(conversationId, remoteDevice)) {
-                        JAMI_WARNING("[Account {:s}] Git server requested for conversation {:s}, but the "
-                                  "device is "
-                                  "unauthorized ({:s}) ",
-                                  getAccountID(),
-                                  conversationId,
-                                  remoteDevice);
+                        JAMI_WARNING(
+                            "[Account {:s}] Git server requested for conversation {:s}, but the "
+                            "device is "
+                            "unauthorized ({:s}) ",
+                            getAccountID(),
+                            conversationId,
+                            remoteDevice);
                         channel->shutdown();
                         return;
                     }
@@ -2039,12 +2053,13 @@ JamiAccount::doRegister_()
                         // So it's not the server socket
                         return;
                     }
-                    JAMI_WARNING("[Account {:s}] Git server requested for conversation {:s}, device {:s}, "
-                              "channel {}",
-                              accountID_,
-                              conversationId,
-                              deviceId.toString(),
-                              channel->channel());
+                    JAMI_WARNING(
+                        "[Account {:s}] Git server requested for conversation {:s}, device {:s}, "
+                        "channel {}",
+                        accountID_,
+                        conversationId,
+                        deviceId.toString(),
+                        channel->channel());
                     auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
                     gs->setOnFetched(
                         [w = weak(), conversationId, deviceId](const std::string& commit) {
@@ -2142,11 +2157,12 @@ JamiAccount::convModule()
                         shared->syncModule()->syncWithConnected(syncMsg);
                 });
             },
-            [this](auto&& uri, auto&& msg, auto token = 0) {
+            [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
                 // No need to retrigger, sendTextMessage will call
                 // messageEngine_.sendMessage, already retriggering on
                 // main thread.
-                return sendTextMessage(uri, msg, token);
+                auto deviceId = device ? device.toString() : "";
+                return sendTextMessage(uri, deviceId, msg, token);
             },
             [this](const auto& convId, const auto& deviceId, auto cb, const auto& type) {
                 runOnMainThread([w = weak(), convId, deviceId, cb = std::move(cb), type] {
@@ -2186,16 +2202,41 @@ JamiAccount::convModule()
                 });
             },
             [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
-                std::lock_guard<std::mutex> lkCM(connManagerMtx_);
-                if (!connectionManager_) {
-                    Manager::instance().ioContext()->post([cb = std::move(cb)] { cb({}); });
-                    return;
-                }
-                connectionManager_->connectDevice(DeviceId(deviceId),
-                                                  fmt::format("swarm://{}", convId),
-                                                  [cb = std::move(
-                                                       cb)](std::shared_ptr<ChannelSocket> socket,
-                                                            const DeviceId&) { cb(socket); });
+                runOnMainThread([w = weak(), convId, deviceId, cb, connectionType] {
+                    auto shared = w.lock();
+                    if (!shared)
+                        return;
+                    std::lock_guard<std::mutex> lkCM(shared->connManagerMtx_);
+                    if (!shared->connectionManager_) {
+                        Manager::instance().ioContext()->post([cb] { cb({}); });
+                        return;
+                    }
+                    if (!shared->connectionManager_->isConnecting(DeviceId(deviceId), fmt::format("swarm://{}", convId))) {
+                        shared->connectionManager_->connectDevice(
+                            DeviceId(deviceId),
+                            fmt::format("swarm://{}", convId),
+                            [w, cb](std::shared_ptr<ChannelSocket> socket,
+                                    const DeviceId& deviceId) {
+                                if (socket) {
+                                    auto shared = w.lock();
+                                    if (!shared)
+                                        return;
+                                    auto remoteCert = socket->peerCertificate();
+                                    auto uri = remoteCert->issuer->getId().toString();
+                                    std::unique_lock<std::mutex> lk(shared->sipConnsMtx_);
+                                    // Verify that the connection is not already cached
+                                    SipConnectionKey key(uri, deviceId.toString());
+                                    auto it = shared->sipConns_.find(key);
+                                    if (it == shared->sipConns_.end()) {
+                                        lk.unlock();
+                                        shared->requestSIPConnection(uri, deviceId, "");
+                                    }
+                                }
+
+                                cb(socket);
+                            });
+                    }
+                });
             },
             [this](auto&& convId, auto&& contactUri, bool accept) {
                 // NOTE: do not reschedule as the conversation's requests
@@ -2328,6 +2369,7 @@ JamiAccount::connectivityChanged()
         // nothing to do
         return;
     }
+    convModule()->connectivityChanged();
     dht_->connectivityChanged();
     {
         std::lock_guard<std::mutex> lkCM(connManagerMtx_);
@@ -2911,7 +2953,7 @@ JamiAccount::declineConversationRequest(const std::string& conversationId)
 {
     auto peerId = convModule()->peerFromConversationRequest(conversationId);
     convModule()->declineConversationRequest(conversationId);
-    if (!peerId.empty())  {
+    if (!peerId.empty()) {
         std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
         if (auto info = accountManager_->getInfo()) {
             // Verify if we have a trust request with this peer + convId
@@ -2919,7 +2961,9 @@ JamiAccount::declineConversationRequest(const std::string& conversationId)
             if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
                 && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
                 accountManager_->discardTrustRequest(peerId);
-                JAMI_DEBUG("[Account {:s}] declined trust request with {:s}", getAccountID(), peerId);
+                JAMI_DEBUG("[Account {:s}] declined trust request with {:s}",
+                           getAccountID(),
+                           peerId);
             }
         }
     }
@@ -2969,6 +3013,7 @@ JamiAccount::forEachDevice(const dht::InfoHash& to,
 
 uint64_t
 JamiAccount::sendTextMessage(const std::string& to,
+                             const std::string& deviceId,
                              const std::map<std::string, std::string>& payloads,
                              uint64_t refreshToken,
                              bool onlyConnected)
@@ -2983,18 +3028,19 @@ JamiAccount::sendTextMessage(const std::string& to,
     try {
         toUri = parseJamiUri(to);
     } catch (...) {
-        JAMI_ERR("Failed to send a text message due to an invalid URI %s", to.c_str());
+        JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
         return 0;
     }
     if (payloads.size() != 1) {
-        JAMI_ERR("Multi-part im is not supported yet by JamiAccount");
+        JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
         return 0;
     }
-    return SIPAccountBase::sendTextMessage(toUri, payloads, refreshToken, onlyConnected);
+    return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
 }
 
 void
 JamiAccount::sendMessage(const std::string& to,
+                         const std::string& deviceId,
                          const std::map<std::string, std::string>& payloads,
                          uint64_t token,
                          bool retryOnTimeout,
@@ -3006,7 +3052,7 @@ JamiAccount::sendMessage(const std::string& to,
     } catch (...) {
         JAMI_ERR("Failed to send a text message due to an invalid URI %s", to.c_str());
         if (!onlyConnected)
-            messageEngine_.onMessageSent(to, token, false);
+            messageEngine_.onMessageSent(to, token, false, deviceId);
         return;
     }
     if (payloads.size() != 1) {
@@ -3018,9 +3064,6 @@ JamiAccount::sendMessage(const std::string& to,
         return;
     }
 
-    auto toH = dht::InfoHash(toUri);
-    auto now = clock::to_time_t(clock::now());
-
     auto confirm = std::make_shared<PendingConfirmation>();
     if (onlyConnected) {
         confirm->replied = true;
@@ -3035,6 +3078,10 @@ JamiAccount::sendMessage(const std::string& to,
             ++it;
             continue;
         }
+        if (!deviceId.empty() && key.second.toString() != deviceId) {
+            ++it;
+            continue;
+        }
         auto& conn = value.back();
         auto& channel = conn.channel;
 
@@ -3042,7 +3089,7 @@ JamiAccount::sendMessage(const std::string& to,
         auto ctx = std::make_unique<TextMessageCtx>();
         ctx->acc = weak();
         ctx->to = to;
-        ctx->deviceId = key.second;
+        ctx->deviceId = DeviceId(deviceId);
         ctx->id = token;
         ctx->onlyConnected = onlyConnected;
         ctx->retryOnTimeout = retryOnTimeout;
@@ -3070,14 +3117,14 @@ JamiAccount::sendMessage(const std::string& to,
                                       });
             if (!res) {
                 if (!onlyConnected)
-                    messageEngine_.onMessageSent(to, token, false);
+                    messageEngine_.onMessageSent(to, token, false, deviceId);
                 ++it;
                 continue;
             }
         } catch (const std::runtime_error& ex) {
             JAMI_WARN("%s", ex.what());
             if (!onlyConnected)
-                messageEngine_.onMessageSent(to, token, false);
+                messageEngine_.onMessageSent(to, token, false, deviceId);
             ++it;
             // Remove connection in incorrect state
             shutdownSIPConnection(channel, to, key.second);
@@ -3086,137 +3133,153 @@ JamiAccount::sendMessage(const std::string& to,
 
         devices->emplace(key.second);
         ++it;
+        if (key.second.toString() == deviceId) {
+            return;
+        }
     }
     lk.unlock();
 
     if (onlyConnected)
         return;
 
-    // Find listening devices for this account
-    accountManager_->forEachDevice(
-        toH,
-        [this, confirm, to, token, payloads, now, devices](
-            const std::shared_ptr<dht::crypto::PublicKey>& dev) {
-            // Test if already sent
-            auto deviceId = dev->getLongId();
-            if (devices->find(deviceId) != devices->end()) {
-                return;
-            }
-            if (deviceId.toString() == currentDeviceId()) {
-                devices->emplace(deviceId);
-                return;
-            }
+    if (deviceId.empty()) {
+        auto toH = dht::InfoHash(toUri);
+        // Find listening devices for this account
+        accountManager_->forEachDevice(
+            toH,
+            [this, confirm, to, token, payloads, devices](
+                const std::shared_ptr<dht::crypto::PublicKey>& dev) {
+                // Test if already sent
+                auto deviceId = dev->getLongId();
+                if (devices->find(deviceId) != devices->end()) {
+                    return;
+                }
+                if (deviceId.toString() == currentDeviceId()) {
+                    devices->emplace(deviceId);
+                    return;
+                }
 
-            // Else, ask for a channel and send a DHT message
-            auto payload_type = payloads.cbegin()->first;
-            requestSIPConnection(to, deviceId, payload_type);
-            {
-                std::lock_guard<std::mutex> lock(messageMutex_);
-                sentMessages_[token].to.emplace(deviceId);
-            }
+                // Else, ask for a channel and send a DHT message
+                auto payload_type = payloads.cbegin()->first;
+                requestSIPConnection(to, deviceId, payload_type);
+                {
+                    std::lock_guard<std::mutex> lock(messageMutex_);
+                    sentMessages_[token].to.emplace(deviceId);
+                }
 
-            auto h = dht::InfoHash::get("inbox:" + dev->getId().toString());
-            std::lock_guard<std::mutex> l(confirm->lock);
-            auto list_token
-                = dht_->listen<dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) {
-                      // check expected message confirmation
-                      if (msg.id != token)
-                          return true;
-
-                      {
-                          std::lock_guard<std::mutex> lock(messageMutex_);
-                          auto e = sentMessages_.find(msg.id);
-                          if (e == sentMessages_.end()
-                              or e->second.to.find(msg.owner->getLongId()) == e->second.to.end()) {
-                              JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
-                                         << "] Message not found";
-                              return true;
-                          }
-                          sentMessages_.erase(e);
-                          JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
-                                     << "] Received text message reply";
-
-                          // add treated message
-                          auto res = treatedMessages_.emplace(to_hex_string(msg.id));
-                          if (!res.second)
-                              return true;
-                      }
-                      saveTreatedMessages();
-
-                      // report message as confirmed received
-                      {
-                          std::lock_guard<std::mutex> l(confirm->lock);
-                          for (auto& t : confirm->listenTokens)
-                              dht_->cancelListen(t.first, std::move(t.second));
-                          confirm->listenTokens.clear();
-                          confirm->replied = true;
-                      }
-                      messageEngine_.onMessageSent(to, token, true);
-                      return false;
-                  });
-            confirm->listenTokens.emplace(h, std::move(list_token));
-            dht_->putEncrypted(h,
-                               dev,
-                               dht::ImMessage(token,
-                                              std::string(payloads.begin()->first),
-                                              std::string(payloads.begin()->second),
-                                              now),
-                               [this, to, token, confirm, h](bool ok) {
-                                   JAMI_DBG()
-                                       << "[Account " << getAccountID() << "] [message " << token
-                                       << "] Put encrypted " << (ok ? "ok" : "failed");
-                                   if (not ok && connectionManager_ /* Check if not joining */) {
-                                       std::unique_lock<std::mutex> l(confirm->lock);
-                                       auto lt = confirm->listenTokens.find(h);
-                                       if (lt != confirm->listenTokens.end()) {
-                                           std::shared_future<size_t> tok = std::move(lt->second);
-                                           confirm->listenTokens.erase(lt);
-                                           dht_->cancelListen(h, tok);
-                                       }
-                                       if (confirm->listenTokens.empty() and not confirm->replied) {
-                                           l.unlock();
-                                           messageEngine_.onMessageSent(to, token, false);
+                auto h = dht::InfoHash::get("inbox:" + dev->getId().toString());
+                std::lock_guard<std::mutex> l(confirm->lock);
+                auto list_token = dht_->listen<
+                    dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) {
+                    // check expected message confirmation
+                    if (msg.id != token)
+                        return true;
+
+                    {
+                        std::lock_guard<std::mutex> lock(messageMutex_);
+                        auto e = sentMessages_.find(msg.id);
+                        if (e == sentMessages_.end()
+                            or e->second.to.find(msg.owner->getLongId()) == e->second.to.end()) {
+                            JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
+                                       << "] Message not found";
+                            return true;
+                        }
+                        sentMessages_.erase(e);
+                        JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
+                                   << "] Received text message reply";
+
+                        // add treated message
+                        auto res = treatedMessages_.emplace(to_hex_string(msg.id));
+                        if (!res.second)
+                            return true;
+                    }
+                    saveTreatedMessages();
+
+                    // report message as confirmed received
+                    {
+                        std::lock_guard<std::mutex> l(confirm->lock);
+                        for (auto& t : confirm->listenTokens)
+                            dht_->cancelListen(t.first, std::move(t.second));
+                        confirm->listenTokens.clear();
+                        confirm->replied = true;
+                    }
+                    messageEngine_.onMessageSent(to, token, true);
+                    return false;
+                });
+                confirm->listenTokens.emplace(h, std::move(list_token));
+                auto now = clock::to_time_t(clock::now());
+                dht_->putEncrypted(h,
+                                   dev,
+                                   dht::ImMessage(token,
+                                                  std::string(payloads.begin()->first),
+                                                  std::string(payloads.begin()->second),
+                                                  now),
+                                   [this, to, token, confirm, h](bool ok) {
+                                       JAMI_DBG()
+                                           << "[Account " << getAccountID() << "] [message "
+                                           << token << "] Put encrypted " << (ok ? "ok" : "failed");
+                                       if (not ok
+                                           && connectionManager_ /* Check if not joining */) {
+                                           std::unique_lock<std::mutex> l(confirm->lock);
+                                           auto lt = confirm->listenTokens.find(h);
+                                           if (lt != confirm->listenTokens.end()) {
+                                               std::shared_future<size_t> tok = std::move(
+                                                   lt->second);
+                                               confirm->listenTokens.erase(lt);
+                                               dht_->cancelListen(h, tok);
+                                           }
+                                           if (confirm->listenTokens.empty()
+                                               and not confirm->replied) {
+                                               l.unlock();
+                                               messageEngine_.onMessageSent(to, token, false);
+                                           }
                                        }
-                                   }
-                               });
+                                   });
 
-            JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
-                       << "] Sending message for device " << deviceId.toString();
-        },
-        [this, to, token, devices, confirm](bool ok) {
-            if (devices->size() == 1 && devices->begin()->toString() == currentDeviceId()) {
-                // Current user only have devices, so no message are sent
-                {
-                    std::lock_guard<std::mutex> l(confirm->lock);
-                    for (auto& t : confirm->listenTokens)
-                        dht_->cancelListen(t.first, std::move(t.second));
-                    confirm->listenTokens.clear();
-                    confirm->replied = true;
+                JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token
+                           << "] Sending message for device " << deviceId.toString();
+            },
+            [this, to, token, devices, confirm](bool ok) {
+                if (devices->size() == 1 && devices->begin()->toString() == currentDeviceId()) {
+                    // Current user only have devices, so no message are sent
+                    {
+                        std::lock_guard<std::mutex> l(confirm->lock);
+                        for (auto& t : confirm->listenTokens)
+                            dht_->cancelListen(t.first, std::move(t.second));
+                        confirm->listenTokens.clear();
+                        confirm->replied = true;
+                    }
+                    messageEngine_.onMessageSent(to, token, true);
+                } else if (not ok) {
+                    messageEngine_.onMessageSent(to, token, false);
                 }
-                messageEngine_.onMessageSent(to, token, true);
-            } else if (not ok) {
-                messageEngine_.onMessageSent(to, token, false);
-            }
-        });
+            });
 
-    // Timeout cleanup
-    Manager::instance().scheduleTaskIn(
-        [w = weak(), confirm, to, token]() {
-            std::unique_lock<std::mutex> l(confirm->lock);
-            if (not confirm->replied) {
-                if (auto this_ = w.lock()) {
-                    JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token
-                               << "] Timeout";
-                    for (auto& t : confirm->listenTokens)
-                        this_->dht_->cancelListen(t.first, std::move(t.second));
-                    confirm->listenTokens.clear();
-                    confirm->replied = true;
-                    l.unlock();
-                    this_->messageEngine_.onMessageSent(to, token, false);
+        // Timeout cleanup
+        Manager::instance().scheduleTaskIn(
+            [w = weak(), confirm, to, token]() {
+                std::unique_lock<std::mutex> l(confirm->lock);
+                if (not confirm->replied) {
+                    if (auto this_ = w.lock()) {
+                        JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token
+                                   << "] Timeout";
+                        for (auto& t : confirm->listenTokens)
+                            this_->dht_->cancelListen(t.first, std::move(t.second));
+                        confirm->listenTokens.clear();
+                        confirm->replied = true;
+                        l.unlock();
+                        this_->messageEngine_.onMessageSent(to, token, false);
+                    }
                 }
-            }
-        },
-        std::chrono::minutes(1));
+            },
+            std::chrono::minutes(1));
+
+    } else {
+        // Set message as not sent in order to be re-triggered
+        messageEngine_.onMessageSent(to, token, false, deviceId);
+        auto payload_type = payloads.cbegin()->first;
+        requestSIPConnection(to, DeviceId(deviceId), payload_type);
+    }
 }
 
 void
@@ -3227,7 +3290,7 @@ JamiAccount::onSIPMessageSent(const std::shared_ptr<TextMessageCtx>& ctx, int co
         ctx->confirmation->replied = true;
         l.unlock();
         if (!ctx->onlyConnected)
-            messageEngine_.onMessageSent(ctx->to, ctx->id, true);
+            messageEngine_.onMessageSent(ctx->to, ctx->id, true, ctx->deviceId.toString());
     } else {
         // Note: This can be called from pjsip's eventloop while
         // sipConnsMtx_ is locked. So we should retrigger the shutdown.
@@ -3245,7 +3308,7 @@ JamiAccount::onSIPMessageSent(const std::shared_ptr<TextMessageCtx>& ctx, int co
         // After closing sockets with that peer, we try to re-connect to
         // that peer one time.
         if (ctx->retryOnTimeout)
-            messageEngine_.onPeerOnline(ctx->to, false);
+            messageEngine_.onPeerOnline(ctx->to, false, ctx->deviceId.toString());
     }
 }
 
@@ -3344,7 +3407,8 @@ JamiAccount::setPushNotificationTopic(const std::string& topic)
 }
 
 bool
-JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) {
+JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
+{
     if (SIPAccountBase::setPushNotificationConfig(data)) {
         if (dht_) {
             dht_->setPushNotificationPlatform(config_->platform);
@@ -3356,7 +3420,6 @@ JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>&
     return false;
 }
 
-
 /**
  * To be called by clients with relevant data when a push notification is received.
  */
@@ -3471,16 +3534,16 @@ JamiAccount::sendInstantMessage(const std::string& convId,
                                 const std::map<std::string, std::string>& msg)
 {
     auto members = convModule()->getConversationMembers(convId);
-    if (members.empty()) {
+    if (convId.empty() && members.empty()) {
         // TODO remove, it's for old API for contacts
-        sendTextMessage(convId, msg);
+        sendTextMessage(convId, "", msg);
         return;
     }
     for (const auto& m : members) {
         const auto& uri = m.at("uri");
         auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
         // Announce to all members that a new message is sent
-        sendMessage(uri, msg, token, false, true);
+        sendMessage(uri, "", msg, token, false, true);
     }
 }
 
@@ -3497,13 +3560,16 @@ JamiAccount::handleMessage(const std::string& from, const std::pair<std::string,
             return false;
         }
 
-        JAMI_WARN("Received indication for new commit available in conversation %s",
-                  json["id"].asString().c_str());
-
-        convModule()->onNewCommit(from,
-                                  json["deviceId"].asString(),
-                                  json["id"].asString(),
-                                  json["commit"].asString());
+        std::string deviceId = json["deviceId"].asString();
+        std::string id = json["id"].asString();
+        std::string commit = json["commit"].asString();
+        // onNewCommit will do heavy stuff like fetching, avoid to block SIP socket
+        dht::ThreadPool::io().run([w = weak(), from, deviceId, id, commit] {
+            if (auto shared = w.lock()) {
+                if (auto cm = shared->convModule())
+                    cm->onNewCommit(from, deviceId, id, commit);
+            }
+        });
         return true;
     } else if (m.first == MIME_TYPE_INVITE) {
         convModule()->onNeedConversationRequest(from, m.second);
@@ -3682,7 +3748,9 @@ JamiAccount::requestSIPConnection(const std::string& peerId,
 }
 
 void
-JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId)
+JamiAccount::sendProfile(const std::string& convId,
+                         const std::string& peerUri,
+                         const std::string& deviceId)
 {
     if (not fileutils::isFile(profilePath()))
         return;
@@ -3709,7 +3777,9 @@ JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri,
 }
 
 bool
-JamiAccount::needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum)
+JamiAccount::needToSendProfile(const std::string& peerUri,
+                               const std::string& deviceId,
+                               const std::string& sha3Sum)
 {
     std::string previousSha3 {};
     auto vCardPath = fmt::format("{}/vcard", cachePath_);
@@ -3878,12 +3948,15 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket,
     // Store the connection
     connections.emplace_back(SipConnection {sip_tr, socket});
     JAMI_WARNING("[Account {:s}] New SIP channel opened with {:s}",
-              getAccountID(), deviceId.to_c_str());
+                 getAccountID(),
+                 deviceId.to_c_str());
     lk.unlock();
 
     convModule()->syncConversations(peerId, deviceId.toString());
     // Retry messages
+
     messageEngine_.onPeerOnline(peerId);
+    messageEngine_.onPeerOnline(peerId, true, deviceId.toString());
 
     // Connect pending calls
     forEachPendingCall(deviceId, [&](const auto& pc) {
@@ -3946,14 +4019,15 @@ JamiAccount::dataTransfer(const std::string& id)
 }
 
 void
-JamiAccount::monitor() const
+JamiAccount::monitor()
 {
     JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
     JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
 
-    std::lock_guard<std::mutex> lkCM(connManagerMtx_);
+    convModule()->monitor();
+    /*std::lock_guard<std::mutex> lkCM(connManagerMtx_);
     if (connectionManager_)
-        connectionManager_->monitor();
+        connectionManager_->monitor();*/
 }
 
 void
@@ -4032,27 +4106,40 @@ JamiAccount::transferFile(const std::string& conversationId,
     std::lock_guard<std::mutex> lkCM(connManagerMtx_);
     if (!connectionManager_)
         return;
-    connectionManager_->connectDevice(
-        DeviceId(deviceId),
-        channelName,
-        [this, conversationId, path = std::move(path), fileId, interactionId, start, end, onFinished = std::move(onFinished)](
-            std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
-            if (!socket)
-                return;
-            dht::ThreadPool::io().run([w = weak(),
-                                       path = std::move(path),
-                                       socket = std::move(socket),
-                                       conversationId = std::move(conversationId),
-                                       fileId,
-                                       interactionId,
-                                       start,
-                                       end,
-                                       onFinished = std::move(onFinished)] {
-                if (auto shared = w.lock())
-                    if (auto dt = shared->dataTransfer(conversationId))
-                        dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished));
-            });
-        });
+    connectionManager_
+        ->connectDevice(DeviceId(deviceId),
+                        channelName,
+                        [this,
+                         conversationId,
+                         path = std::move(path),
+                         fileId,
+                         interactionId,
+                         start,
+                         end,
+                         onFinished = std::move(onFinished)](std::shared_ptr<ChannelSocket> socket,
+                                                             const DeviceId&) {
+                            if (!socket)
+                                return;
+                            dht::ThreadPool::io().run([w = weak(),
+                                                       path = std::move(path),
+                                                       socket = std::move(socket),
+                                                       conversationId = std::move(conversationId),
+                                                       fileId,
+                                                       interactionId,
+                                                       start,
+                                                       end,
+                                                       onFinished = std::move(onFinished)] {
+                                if (auto shared = w.lock())
+                                    if (auto dt = shared->dataTransfer(conversationId))
+                                        dt->transferFile(socket,
+                                                         fileId,
+                                                         interactionId,
+                                                         path,
+                                                         start,
+                                                         end,
+                                                         std::move(onFinished));
+                            });
+                        });
 }
 
 void
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index a2b62d3656b6ba09c297b934218a4204e4342131..021697ebbc544ca524d358d858f1c155d08d57a7 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -311,11 +311,14 @@ public:
 
     void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
     void sendMessage(const std::string& to,
+                     const std::string& deviceId,
                      const std::map<std::string, std::string>& payloads,
                      uint64_t id,
                      bool retryOnTimeout = true,
                      bool onlyConnected = false) override;
+
     uint64_t sendTextMessage(const std::string& to,
+                             const std::string& deviceId,
                              const std::map<std::string, std::string>& payloads,
                              uint64_t refreshToken = 0,
                              bool onlyConnected = false) override;
@@ -460,7 +463,7 @@ public:
     bool handleMessage(const std::string& from,
                        const std::pair<std::string, std::string>& message) override;
 
-    void monitor() const;
+    void monitor();
 
     // File transfer
     void sendFile(const std::string& conversationId,
@@ -511,14 +514,18 @@ public:
      * @param sha3Sum       SHA3 hash of the profile
      */
     // Note: when swarm will be merged, this can be moved in transferManager
-    bool needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum);
+    bool needToSendProfile(const std::string& peerUri,
+                           const std::string& deviceId,
+                           const std::string& sha3Sum);
     /**
      * Send Profile via cached SIP connection
      * @param convId        Conversation's identifier (can be empty for self profile on sync)
      * @param peerUri       Uri that will receive the profile
      * @param deviceId      Device that will receive the profile
      */
-    void sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId);
+    void sendProfile(const std::string& convId,
+                     const std::string& peerUri,
+                     const std::string& deviceId);
     /**
      * Send profile via cached SIP connection
      * @param peerUri       Uri that will receive the profile
@@ -572,14 +579,22 @@ public:
     void handleIncomingConversationCall(const std::string& callId, const std::string& destination);
 
     /**
-     * The DRT component is composed on some special nodes, that are usually present but not connected.
-     * This kind of node corresponds to devices with push notifications & proxy and are
+     * The DRT component is composed on some special nodes, that are usually present but not
+     * connected. This kind of node corresponds to devices with push notifications & proxy and are
      * stored in the mobile nodes
      */
-    bool isMobile() const {
+    bool isMobile() const
+    {
         return config().proxyEnabled and not config().deviceKey.empty();
     }
 
+#ifdef LIBJAMI_TESTABLE
+    std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>>& channelHandlers()
+    {
+        return channelHandlers_;
+    };
+#endif
+
 private:
     NON_COPYABLE(JamiAccount);
 
diff --git a/src/jamidht/swarm/routing_table.cpp b/src/jamidht/swarm/routing_table.cpp
index c3dc468511bcb276c92b1d54e881a9cdb42b486f..7f88bd0e368cebff95df19daf2c10ec5b0b73406 100644
--- a/src/jamidht/swarm/routing_table.cpp
+++ b/src/jamidht/swarm/routing_table.cpp
@@ -494,6 +494,35 @@ RoutingTable::contains(const std::list<Bucket>::iterator& bucket, const NodeId&
                || NodeId::cmp(nodeId, std::next(bucket)->getLowerLimit()) < 0);
 }
 
+std::vector<NodeId>
+RoutingTable::getAllNodes() const
+{
+    std::vector<NodeId> ret;
+    for (const auto& b : buckets) {
+        const auto& nodes = b.getNodeIds();
+        const auto& knownNodes = b.getKnownNodes();
+        const auto& mobileNodes = b.getMobileNodes();
+        const auto& connectingNodes = b.getConnectingNodes();
+
+        ret.insert(ret.end(), nodes.begin(), nodes.end());
+        ret.insert(ret.end(), knownNodes.begin(), knownNodes.end());
+        ret.insert(ret.end(), mobileNodes.begin(), mobileNodes.end());
+        ret.insert(ret.end(), connectingNodes.begin(), connectingNodes.end());
+    }
+    return ret;
+}
+
+void
+RoutingTable::deleteNode(const NodeId& nodeId)
+{
+    auto bucket = findBucket(nodeId);
+    shutdownNode(nodeId);
+    bucket->removeNode(nodeId);
+    bucket->removeConnectingNode(nodeId);
+    bucket->removeKnownNode(nodeId);
+    bucket->removeMobileNode(nodeId);
+}
+
 NodeId
 RoutingTable::middle(std::list<Bucket>::iterator& it) const
 {
diff --git a/src/jamidht/swarm/routing_table.h b/src/jamidht/swarm/routing_table.h
index c20d5a9cb54417de9ee40ca5545f9e780ddc0018..3265b050d46b6c93d2f95589bcb6ab388787898c 100644
--- a/src/jamidht/swarm/routing_table.h
+++ b/src/jamidht/swarm/routing_table.h
@@ -60,7 +60,7 @@ class Bucket
 
 {
 public:
-    static constexpr int BUCKET_MAX_SIZE = 4;
+    static constexpr int BUCKET_MAX_SIZE = 2;
 
     Bucket() = delete;
     Bucket(const Bucket&) = delete;
@@ -525,6 +525,16 @@ public:
      */
     bool contains(const std::list<Bucket>::iterator& it, const NodeId& nodeId) const;
 
+    /**
+     * Return every node from each bucket
+     */
+    std::vector<NodeId> getAllNodes() const;
+
+    /**
+     * Delete node from every table in bucket
+     */
+    void deleteNode(const NodeId& nodeId);
+
 private:
     RoutingTable(const RoutingTable&) = delete;
     RoutingTable& operator=(const RoutingTable&) = delete;
diff --git a/src/jamidht/swarm/swarm_manager.cpp b/src/jamidht/swarm/swarm_manager.cpp
index c6d52a5954d771bfb6bcff646d8e997b881e3169..e0e56d05d258917e1496bb1e8aa3df9c7841ee1b 100644
--- a/src/jamidht/swarm/swarm_manager.cpp
+++ b/src/jamidht/swarm/swarm_manager.cpp
@@ -1,5 +1,6 @@
 /*
  *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
  *  Author: Fadi Shehadeh <fadi.shehadeh@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -13,11 +14,10 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
- *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#include <iostream>
-
 #include "swarm_manager.h"
 #include "connectivity/multiplexed_socket.h"
 #include <opendht/thread_pool.h>
@@ -37,48 +37,91 @@ SwarmManager::SwarmManager(const NodeId& id)
 
 SwarmManager::~SwarmManager()
 {
-    shutdown();
+    if (!isShutdown_)
+        shutdown();
 }
 
 void
-SwarmManager::removeNode(const NodeId& nodeId)
+SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes)
 {
+    isShutdown_ = false;
     {
         std::lock_guard<std::mutex> lock(mutex);
-        removeNodeInternal(nodeId);
+        for (const auto& NodeId : known_nodes)
+            addKnownNodes(std::move(NodeId));
     }
     maintainBuckets();
 }
 
 void
-SwarmManager::removeNodeInternal(const NodeId& nodeId)
+SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes)
 {
-    routing_table.removeNode(nodeId);
+    {
+        std::lock_guard<std::mutex> lock(mutex);
+        for (const auto& nodeId : mobile_nodes)
+            addMobileNodes(nodeId);
+    }
 }
 
 void
-SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes)
+SwarmManager::addChannel(const std::shared_ptr<ChannelSocketInterface>& channel)
 {
-    isShutdown_ = false;
-    {
-        std::lock_guard<std::mutex> lock(mutex);
-        for (const auto& NodeId : known_nodes)
-            addKnownNodes(std::move(NodeId));
+    // JAMI_WARNING("[SwarmManager {}] addChannel! with {}", fmt::ptr(this), channel->deviceId().to_view());
+    if (channel) {
+        auto emit = false;
+        {
+            std::lock_guard<std::mutex> lock(mutex);
+            emit = routing_table.findBucket(getId())->getNodeIds().size() == 0;
+            auto bucket = routing_table.findBucket(channel->deviceId());
+            if (routing_table.addNode(channel, bucket)) {
+                std::error_code ec;
+                resetNodeExpiry(ec, channel, id_);
+            }
+        }
+        receiveMessage(channel);
+        if (emit && onConnectionChanged_) {
+            // If it's the first channel we add, we're now connected!
+            JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this));
+            onConnectionChanged_(true);
+        }
     }
-    maintainBuckets();
 }
 
 void
-SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes)
+SwarmManager::removeNode(const NodeId& nodeId)
 {
     {
         std::lock_guard<std::mutex> lock(mutex);
-        for (const auto& nodeId : mobile_nodes)
-            addMobileNodes(nodeId);
+        removeNodeInternal(nodeId);
     }
     maintainBuckets();
 }
 
+void
+SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile)
+{
+    std::lock_guard<std::mutex> lock(mutex);
+    auto bucket = routing_table.findBucket(nodeId);
+    bucket->changeMobility(nodeId, isMobile);
+}
+
+bool
+SwarmManager::isConnectedWith(const NodeId& deviceId)
+{
+    return routing_table.hasNode(deviceId);
+}
+
+void
+SwarmManager::shutdown()
+{
+    if (isShutdown_) {
+        return;
+    }
+    isShutdown_ = true;
+    std::lock_guard<std::mutex> lock(mutex);
+    routing_table.shutdownAllNodes();
+}
+
 void
 SwarmManager::addKnownNodes(const NodeId& nodeId)
 {
@@ -95,6 +138,31 @@ SwarmManager::addMobileNodes(const NodeId& nodeId)
     }
 }
 
+void
+SwarmManager::maintainBuckets()
+{
+    std::set<NodeId> nodes;
+    std::unique_lock<std::mutex> lock(mutex);
+    auto& buckets = routing_table.getBuckets();
+    for (auto it = buckets.begin(); it != buckets.end(); ++it) {
+        auto& bucket = *it;
+        bool myBucket = routing_table.contains(it, id_);
+        auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize()
+                                         : bucket.getConnectingNodesSize() + bucket.getNodesSize();
+        if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) {
+            auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes,
+                                                         rd);
+            for (auto& node : nodesToTry)
+                routing_table.addConnectingNode(node);
+
+            nodes.insert(nodesToTry.begin(), nodesToTry.end());
+        }
+    }
+    lock.unlock();
+    for (auto& node : nodes)
+        tryConnect(node);
+}
+
 void
 SwarmManager::sendRequest(const std::shared_ptr<ChannelSocketInterface>& socket,
                           NodeId& nodeId,
@@ -135,17 +203,21 @@ SwarmManager::sendAnswer(const std::shared_ptr<ChannelSocketInterface>& socket,
         msg.is_mobile = isMobile_;
         msg.response = std::move(toResponse);
 
-        msgpack::sbuffer buffer((size_t) 1024);
+        msgpack::sbuffer buffer((size_t) 60000);
         msgpack::packer<msgpack::sbuffer> pk(&buffer);
         pk.pack(msg);
 
         std::error_code ec;
+
         socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
         if (ec) {
             JAMI_ERROR("{}", ec.message());
             return;
         }
     }
+
+    else {
+    }
 }
 
 void
@@ -170,7 +242,7 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock
             auto shared = w.lock();
             auto socket = wsocket.lock();
             if (!shared || !socket)
-                return 0lu;
+                return size_t {0};
 
             try {
                 Message msg;
@@ -179,9 +251,10 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock
                 if (msg.is_mobile)
                     shared->changeMobility(socket->deviceId(), msg.is_mobile);
 
-                if (msg.request)
+                if (msg.request) {
                     shared->sendAnswer(socket, msg);
-                else if (msg.response) {
+
+                } else if (msg.response) {
                     shared->setKnownNodes(msg.response->nodes);
                     shared->setMobileNodes(msg.response->mobile_nodes);
                 }
@@ -204,33 +277,35 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock
 }
 
 void
-SwarmManager::maintainBuckets()
+SwarmManager::resetNodeExpiry(const asio::error_code& ec,
+                              const std::shared_ptr<ChannelSocketInterface>& socket,
+                              NodeId node)
 {
-    std::set<NodeId> nodes;
-    std::unique_lock<std::mutex> lock(mutex);
-    auto& buckets = routing_table.getBuckets();
-    for (auto it = buckets.begin(); it != buckets.end(); ++it) {
-        auto& bucket = *it;
-        bool myBucket = routing_table.contains(it, id_);
-        auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize()
-                                         : bucket.getConnectingNodesSize() + bucket.getNodesSize();
-        if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) {
-            auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes,
-                                                         rd);
-            for (auto& node : nodesToTry)
-                routing_table.addConnectingNode(node);
+    NodeId idToFind;
+    std::list<Bucket>::iterator bucket;
 
-            nodes.insert(nodesToTry.begin(), nodesToTry.end());
-        }
+    if (ec == asio::error::operation_aborted)
+        return;
+
+    if (!node) {
+        bucket = routing_table.findBucket(socket->deviceId());
+        idToFind = bucket->randomId(rd);
+    } else {
+        bucket = routing_table.findBucket(node);
+        idToFind = node;
+    }
+
+    sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE);
+
+    if (!node) {
+        auto& nodeTimer = bucket->getNodeTimer(socket);
+        nodeTimer.expires_after(FIND_PERIOD);
+        nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry,
+                                       shared_from_this(),
+                                       std::placeholders::_1,
+                                       socket,
+                                       NodeId {}));
     }
-    lock.unlock();
-    for (auto& node : nodes)
-        tryConnect(node);
-}
-bool
-SwarmManager::hasChannel(const NodeId& deviceId)
-{
-    return routing_table.hasNode(deviceId);
 }
 
 void
@@ -263,71 +338,32 @@ SwarmManager::tryConnect(const NodeId& nodeId)
 }
 
 void
-SwarmManager::addChannel(std::shared_ptr<ChannelSocketInterface> channel)
+SwarmManager::removeNodeInternal(const NodeId& nodeId)
 {
-    auto emit = false;
-    {
-        std::lock_guard<std::mutex> lock(mutex);
-        emit = routing_table.findBucket(getId())->getNodeIds().size() == 0;
-        auto bucket = routing_table.findBucket(channel->deviceId());
-        if (routing_table.addNode(channel, bucket)) {
-            std::error_code ec;
-            resetNodeExpiry(ec, channel, id_);
-        }
-    }
-    receiveMessage(channel);
-    if (emit && onConnectionChanged_) {
-        // If it's the first channel we add, we're now connected!
-        JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this));
-        onConnectionChanged_(true);
-    }
+    routing_table.removeNode(nodeId);
 }
 
-void
-SwarmManager::resetNodeExpiry(const asio::error_code& ec,
-                              const std::shared_ptr<ChannelSocketInterface>& socket,
-                              NodeId node)
+std::vector<NodeId>
+SwarmManager::getAllNodes() const
 {
-    NodeId idToFind;
-    std::list<Bucket>::iterator bucket;
-
-    if (ec == asio::error::operation_aborted)
-        return;
-
-    if (!node) {
-        bucket = routing_table.findBucket(socket->deviceId());
-        idToFind = bucket->randomId(rd);
-    } else {
-        bucket = routing_table.findBucket(node);
-        idToFind = node;
-    }
-
-    sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE);
+    std::lock_guard<std::mutex> lock(mutex);
+    std::vector<NodeId> nodes;
+    const auto& rtNodes = routing_table.getAllNodes();
+    nodes.insert(nodes.end(), rtNodes.begin(), rtNodes.end());
 
-    if (!node) {
-        auto& nodeTimer = bucket->getNodeTimer(socket);
-        nodeTimer.expires_after(FIND_PERIOD);
-        nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry,
-                                       shared_from_this(),
-                                       std::placeholders::_1,
-                                       socket,
-                                       NodeId {}));
-    }
+    return nodes;
 }
 
 void
-SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile)
+SwarmManager::deleteNode(std::vector<NodeId> nodes)
 {
-    std::lock_guard<std::mutex> lock(mutex);
-    auto bucket = routing_table.findBucket(nodeId);
-    bucket->changeMobility(nodeId, isMobile);
+    {
+        std::lock_guard<std::mutex> lock(mutex);
+        for (const auto& node : nodes) {
+            routing_table.deleteNode(node);
+        }
+    }
+    maintainBuckets();
 }
 
-void
-SwarmManager::shutdown()
-{
-    isShutdown_ = true;
-    std::lock_guard<std::mutex> lock(mutex);
-    routing_table.shutdownAllNodes();
-}
 } // namespace jami
diff --git a/src/jamidht/swarm/swarm_manager.h b/src/jamidht/swarm/swarm_manager.h
index df1d247226a0b0f209e7bc2a55cf4df9391a5563..50f58cc74c209547492771eb9e76de290ec90264 100644
--- a/src/jamidht/swarm/swarm_manager.h
+++ b/src/jamidht/swarm/swarm_manager.h
@@ -17,6 +17,7 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
+
 #pragma once
 
 #include "routing_table.h"
@@ -41,52 +42,107 @@ public:
 
     NeedSocketCb needSocketCb_;
 
-    std::weak_ptr<SwarmManager> weak()
-    {
-        return std::static_pointer_cast<SwarmManager>(shared_from_this());
-    }
+    std::weak_ptr<SwarmManager> weak() { return weak_from_this(); }
 
+    /**
+     * Get swarm manager id
+     * @return NodeId
+     */
     const NodeId& getId() const { return id_; }
 
     /**
-     * Add list of nodes to the known nodes list
-     * @param vector<NodeId>& known_nodes
+     * Set list of nodes to the routing table known_nodes
+     * @param known_nodes
      */
     void setKnownNodes(const std::vector<NodeId>& known_nodes);
 
     /**
-     * Add list of nodes to the mobile nodes list
-     * @param vector<NodeId>& mobile_nodes
+     * Set list of nodes to the routing table mobile_nodes
+     * @param mobile_nodes
      */
     void setMobileNodes(const std::vector<NodeId>& mobile_nodes);
 
     /**
      * Add channel to routing table
-     * @param shared_ptr<ChannelSocketInterface>& channel
+     * @param channel
      */
-    void addChannel(std::shared_ptr<ChannelSocketInterface> channel);
+    void addChannel(const std::shared_ptr<ChannelSocketInterface>& channel);
 
+    /**
+     * Remove channel from routing table
+     * @param channel
+     */
     void removeNode(const NodeId& nodeId);
+
+    /**
+     * Change mobility of specific node
+     * @param nodeId
+     * @param isMobile
+     */
     void changeMobility(const NodeId& nodeId, bool isMobile);
 
-    /** For testing */
+    /**
+     * get all nodes from the different tables in bucket
+     */
+    std::vector<NodeId> getAllNodes() const;
+
+    /**
+     * Delete nodes from the different tables in bucket
+     */
+    void deleteNode(std::vector<NodeId> nodes);
+
+    // For tests
+
+    /**
+     * Get routing table
+     * @return RoutingTable
+     */
     RoutingTable& getRoutingTable() { return routing_table; };
+
+    /**
+     * Get buckets of routing table
+     * @return buckets list
+     */
     std::list<Bucket>& getBuckets() { return routing_table.getBuckets(); };
 
+    /**
+     * Shutdown swarm manager
+     */
     void shutdown();
 
+    /**
+     * Display swarm manager info
+     */
     void display()
     {
         JAMI_DEBUG("SwarmManager {:s} has {:d} nodes in table [P = {}]",
                    getId().to_c_str(),
                    routing_table.getRoutingTableNodeCount(),
                    isMobile_);
+        // print nodes of routingtable
+        for (auto& bucket : routing_table.getBuckets()) {
+            for (auto& node : bucket.getNodes()) {
+                JAMI_DEBUG("Node {:s}", node.first.toString());
+            }
+        }
     }
 
+    /*
+     * Callback for connection changed
+     * @param cb
+     */
     void onConnectionChanged(OnConnectionChanged cb) { onConnectionChanged_ = std::move(cb); }
 
+    /**
+     * Set mobility of swarm manager
+     * @param isMobile
+     */
     void setMobility(bool isMobile) { isMobile_ = isMobile; }
 
+    /**
+     * Get mobility of swarm manager
+     * @return true if mobile, false if not
+     */
     bool isMobile() const { return isMobile_; }
 
     /**
@@ -94,27 +150,38 @@ public:
      */
     void maintainBuckets();
 
-    bool hasChannel(const NodeId& deviceId);
+    /**
+     * Check if we're connected with a specific device
+     * @param deviceId
+     * @return true if connected, false if not
+     */
+    bool isConnectedWith(const NodeId& deviceId);
+
+    /**
+     * Check if swarm manager is shutdown
+     * @return true if shutdown, false if not
+     */
+    bool isShutdown() { return isShutdown_; };
 
 private:
     /**
      * Add node to the known_nodes list
-     * @param NodeId nodeId
+     * @param nodeId
      */
     void addKnownNodes(const NodeId& nodeId);
 
     /**
      * Add node to the mobile_Nodes list
-     * @param NodeId nodeId
+     * @param nodeId
      */
     void addMobileNodes(const NodeId& nodeId);
 
     /**
      * Send nodes request to fill known_nodes list
-     * @param shared_ptr<ChannelSocketInterface>& socket
-     * @param NodeId& nodeId
-     * @param Query q
-     * @param int numberNodes
+     * @param socket
+     * @param nodeId
+     * @param q
+     * @param numberNodes
      */
     void sendRequest(const std::shared_ptr<ChannelSocketInterface>& socket,
                      NodeId& nodeId,
@@ -123,22 +190,22 @@ private:
 
     /**
      * Send answer to request
-     * @param std::shared_ptr<ChannelSocketInterface>& socket
-     * @param Message msg
+     * @param socket
+     * @param msg
      */
     void sendAnswer(const std::shared_ptr<ChannelSocketInterface>& socket, const Message& msg_);
 
     /**
      * Interpret received message
-     * @param std::shared_ptr<ChannelSocketInterface>& socket
+     * @param socket
      */
     void receiveMessage(const std::shared_ptr<ChannelSocketInterface>& socket);
 
     /**
-     * Add list of nodes to the known nodes list
-     * @param asio::error_code& ec
-     * @param shared_ptr<ChannelSocketInterface>& socket
-     * @param NodeId node
+     * Reset node's timer expiry
+     * @param ec
+     * @param socket
+     * @param node
      */
     void resetNodeExpiry(const asio::error_code& ec,
                          const std::shared_ptr<ChannelSocketInterface>& socket,
@@ -146,10 +213,14 @@ private:
 
     /**
      * Try to establich connexion with specific node
-     * @param NodeId nodeId
+     * @param nodeId
      */
     void tryConnect(const NodeId& nodeId);
 
+    /**
+     * Remove node from routing table
+     * @param nodeId
+     */
     void removeNodeInternal(const NodeId& nodeId);
 
     const NodeId id_;
diff --git a/src/manager.cpp b/src/manager.cpp
index cf9d3efaf2436d9fc513385784a6ae2277ee6ff5..279458fccdd094b8a34fc502acbfaeefe52858d5 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -679,12 +679,7 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf)
     if (call.isConferenceParticipant())
         base_.detachParticipant(callId);
 
-
-
-    JAMI_DEBUG("[call:{}] bind to conference {} (callState={})",
-             callId,
-             confId,
-             state);
+    JAMI_DEBUG("[call:{}] bind to conference {} (callState={})", callId, confId, state);
 
     base_.getRingBufferPool().unBindAll(callId);
 
@@ -702,9 +697,7 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf)
         conf.bindParticipant(callId);
         base_.answerCall(call);
     } else
-        JAMI_WARNING("[call:{}] call state {} not recognized for conference",
-                  callId,
-                  state);
+        JAMI_WARNING("[call:{}] call state {} not recognized for conference", callId, state);
 }
 
 //==============================================================================
@@ -2918,10 +2911,10 @@ Manager::sendTextMessage(const std::string& accountID,
             if (pluginChatManager.hasHandlers()) {
                 auto cm = std::make_shared<JamiMessage>(accountID, to, false, payloads, fromPlugin);
                 pluginChatManager.publishMessage(cm);
-                return acc->sendTextMessage(cm->peerId, cm->data, 0, onlyConnected);
+                return acc->sendTextMessage(cm->peerId, "", cm->data, 0, onlyConnected);
             } else
 #endif // ENABLE_PLUGIN
-                return acc->sendTextMessage(to, payloads, 0, onlyConnected);
+                return acc->sendTextMessage(to, "", payloads, 0, onlyConnected);
         } catch (const std::exception& e) {
             JAMI_ERR("Exception during text message sending: %s", e.what());
         }
@@ -3051,10 +3044,9 @@ Manager::createSinkClients(
         }
         if (participant.w && participant.h && !participant.videoMuted) {
             auto currentSink = getSinkClient(sinkId);
-            if (!accountId.empty() &&
-                currentSink &&
-                string_remove_suffix(participant.uri, '@') == getAccount(accountId)->getUsername() &&
-                participant.device == getAccount<JamiAccount>(accountId)->currentDeviceId()) {
+            if (!accountId.empty() && currentSink
+                && string_remove_suffix(participant.uri, '@') == getAccount(accountId)->getUsername()
+                && participant.device == getAccount<JamiAccount>(accountId)->currentDeviceId()) {
                 // This is a local sink that must already exist
                 continue;
             }
diff --git a/src/manager.h b/src/manager.h
index e75ac960bae6f9b2d9917355ec9f9c392e285238..735273be6b8e49a460ab90cedc2a3ae004298a84 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -818,11 +818,12 @@ public:
      * @param videoStream the the VideoFrameActiveWriter to which the sinks should be attached
      * @param sinksMap A map between sink ids and the respective shared pointer.
      */
-    void createSinkClients(const std::string& callId,
-                           const ConfInfo& infos,
-                           const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams,
-                           std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap,
-                           const std::string& accountId = "");
+    void createSinkClients(
+        const std::string& callId,
+        const ConfInfo& infos,
+        const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams,
+        std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap,
+        const std::string& accountId = "");
 
     /**
      * Return an existing SinkClient instance as a shared_ptr associated to the given identifier.
@@ -839,7 +840,7 @@ public:
     AccountFactory accountFactory;
 
     std::vector<libjami::Message> getLastMessages(const std::string& accountID,
-                                                const uint64_t& base_timestamp);
+                                                  const uint64_t& base_timestamp);
 
     SIPVoIPLink& sipVoIPLink() const;
 #ifdef ENABLE_PLUGIN
diff --git a/src/meson.build b/src/meson.build
index 8cee032ff124d16be62e66759460e9dccebd7476..a1e503aeba8724d3818928d2034066941ee9137b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -55,6 +55,10 @@ libjami_sources = files(
     'jamidht/sync_channel_handler.cpp',
     'jamidht/sync_module.cpp',
     'jamidht/transfer_channel_handler.cpp',
+    'jamidht/swarm/routing_table.cpp',
+    'jamidht/swarm/swarm_manager.cpp',
+    'jamidht/swarm/swarm_protocol.cpp',
+    'jamidht/swarm/swarm_channel_handler.cpp',
     'media/audio/audio-processing/null_audio_processor.cpp',
     'media/audio/sound/audiofile.cpp',
     'media/audio/sound/dtmf.cpp',
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index 38287eaf09602e5895fc8150e46c2aa610c73c04..08c005b892dd82703275ce67648caa9b6845966f 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -268,8 +268,8 @@ SIPAccount::onTransportStateChanged(pjsip_transport_state state,
 {
     pj_status_t currentStatus = transportStatus_;
     JAMI_DEBUG("Transport state changed to {:s} for account {:s}!",
-             SipTransport::stateToStr(state),
-             accountID_);
+               SipTransport::stateToStr(state),
+               accountID_);
     if (!SipTransport::isAlive(state)) {
         if (info) {
             transportStatus_ = info->status;
@@ -301,7 +301,8 @@ SIPAccount::setTransport(const std::shared_ptr<SipTransport>& t)
         return;
     if (transport_) {
         JAMI_DEBUG("Removing old transport [{}] from account", fmt::ptr(transport_.get()));
-        // NOTE: do not call destroyRegistrationInfo() there as we must call the registration callback if needed
+        // NOTE: do not call destroyRegistrationInfo() there as we must call the registration
+        // callback if needed
         if (regc_)
             pjsip_regc_release_transport(regc_);
         transport_->removeStateListener(reinterpret_cast<uintptr_t>(this));
@@ -482,9 +483,10 @@ SIPAccount::mapPortUPnP()
                            or mapRes->getState() == upnp::MappingState::IN_PROGRESS;
             auto newPort = success ? mapRes->getExternalPort() : accPtr->config().publishedPort;
             if (not success and not accPtr->isRegistered()) {
-                JAMI_WARNING("[Account {:s}] Failed to open port {}: registering SIP account anyway",
-                          accPtr->getAccountID(),
-                          oldPort);
+                JAMI_WARNING(
+                    "[Account {:s}] Failed to open port {}: registering SIP account anyway",
+                    accPtr->getAccountID(),
+                    oldPort);
                 accPtr->doRegister1_();
                 return;
             }
@@ -492,12 +494,13 @@ SIPAccount::mapPortUPnP()
                 or (accPtr->getRegistrationState() != RegistrationState::REGISTERED)) {
                 if (not accPtr->isRegistered())
                     JAMI_WARNING("[Account {:s}] SIP port {} opened: registering SIP account",
-                              accPtr->getAccountID(),
-                              newPort);
+                                 accPtr->getAccountID(),
+                                 newPort);
                 else
-                    JAMI_WARNING("[Account {:s}] SIP port changed to {}: re-registering SIP account",
-                              accPtr->getAccountID(),
-                              newPort);
+                    JAMI_WARNING(
+                        "[Account {:s}] SIP port changed to {}: re-registering SIP account",
+                        accPtr->getAccountID(),
+                        newPort);
                 accPtr->publishedPortUsed_ = newPort;
             } else {
                 accPtr->connectivityChanged();
@@ -528,7 +531,8 @@ SIPAccount::setPushNotificationToken(const std::string& pushDeviceToken)
 }
 
 bool
-SIPAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) {
+SIPAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
+{
     if (SIPAccountBase::setPushNotificationConfig(data)) {
         if (config().enabled)
             doUnregister([&](bool /* transport_free */) { doRegister(); });
@@ -1795,6 +1799,7 @@ static pjsip_accept_hdr* im_create_accept(pj_pool_t *pool)
 
 void
 SIPAccount::sendMessage(const std::string& to,
+                        const std::string&,
                         const std::map<std::string, std::string>& payloads,
                         uint64_t id,
                         bool,
diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h
index 4caf32fb181625a11e168f1745f5743f83ed245f..a29f7db724a5801667c303c87e901143fb19d84e 100644
--- a/src/sip/sipaccount.h
+++ b/src/sip/sipaccount.h
@@ -81,17 +81,19 @@ public:
 
     ~SIPAccount() noexcept;
 
-    const SipAccountConfig& config() const {
+    const SipAccountConfig& config() const
+    {
         return *static_cast<const SipAccountConfig*>(&Account::config());
     }
 
-    std::unique_ptr<AccountConfig> buildConfig() const override {
+    std::unique_ptr<AccountConfig> buildConfig() const override
+    {
         return std::make_unique<SipAccountConfig>(getAccountID());
     }
-    inline void editConfig(std::function<void(SipAccountConfig& conf)>&& edit) {
-        Account::editConfig([&](AccountConfig& conf) {
-            edit(*static_cast<SipAccountConfig*>(&conf));
-        });
+    inline void editConfig(std::function<void(SipAccountConfig& conf)>&& edit)
+    {
+        Account::editConfig(
+            [&](AccountConfig& conf) { edit(*static_cast<SipAccountConfig*>(&conf)); });
     }
 
     std::string_view getAccountType() const override { return ACCOUNT_TYPE; }
@@ -396,6 +398,7 @@ public:
     void onRegister(pjsip_regc_cbparam* param);
 
     virtual void sendMessage(const std::string& to,
+                             const std::string& deviceId,
                              const std::map<std::string, std::string>& payloads,
                              uint64_t id,
                              bool retryOnTimeout = true,
@@ -412,7 +415,10 @@ public:
     IpAddr createBindingAddress();
 
     void setActiveCodecs(const std::vector<unsigned>& list) override;
-    bool isSrtpEnabled() const override { return config().srtpKeyExchange != KeyExchangeProtocol::NONE; }
+    bool isSrtpEnabled() const override
+    {
+        return config().srtpKeyExchange != KeyExchangeProtocol::NONE;
+    }
 
     bool setPushNotificationToken(const std::string& pushDeviceToken = "") override;
     bool setPushNotificationConfig(const std::map<std::string, std::string>& data) override;
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index d05de5d886189ce5545a0c88c170f2f1ba41de95..92acd001492fd75e9dbb0c8dc4b0e5c7c374a1be 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -175,6 +175,7 @@ public:
     IceTransportOptions getIceOptions() const noexcept;
 
     virtual void sendMessage(const std::string& to,
+                             const std::string& deviceId,
                              const std::map<std::string, std::string>& payloads,
                              uint64_t id,
                              bool retryOnTimeout = true,
@@ -182,16 +183,17 @@ public:
         = 0;
 
     virtual uint64_t sendTextMessage(const std::string& to,
+                                     const std::string& deviceId,
                                      const std::map<std::string, std::string>& payloads,
                                      uint64_t refreshToken = 0,
                                      bool onlyConnected = false) override
     {
         if (onlyConnected) {
             auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
-            sendMessage(to, payloads, token, false, true);
+            sendMessage(to, deviceId, payloads, token, false, true);
             return token;
         }
-        return messageEngine_.sendMessage(to, payloads, refreshToken);
+        return messageEngine_.sendMessage(to, deviceId, payloads, refreshToken);
     }
 
     im::MessageStatus getMessageStatus(uint64_t id) const override
diff --git a/test/unitTest/Makefile.am b/test/unitTest/Makefile.am
index 03719f78ae3f967d7a062ca8599d6f9f7b9b5b85..03e97dcd6e3cc3dd830a055aeacf70a2339635b5 100644
--- a/test/unitTest/Makefile.am
+++ b/test/unitTest/Makefile.am
@@ -243,4 +243,22 @@ ut_plugins_SOURCES = plugins/plugins.cpp common.cpp
 check_PROGRAMS += ut_routing_table
 ut_routing_table_SOURCES = swarm/routing_table.cpp
 
+#
+# Bootstrap
+#
+check_PROGRAMS += ut_bootstrap
+ut_bootstrap_SOURCES = swarm/bootstrap.cpp common.cpp
+
+#
+# SwarmConversation
+#
+check_PROGRAMS += ut_swarm_conversation
+ut_swarm_conversation_SOURCES = swarm/swarm_conversation.cpp common.cpp
+
+#
+# SwarmSpreadTest
+#
+check_PROGRAMS += ut_swarm_spread
+ut_swarm_spread_SOURCES = swarm/swarm_spread.cpp common.cpp
+
 TESTS = $(check_PROGRAMS)
diff --git a/test/unitTest/actors/account_list.yml b/test/unitTest/actors/account_list.yml
new file mode 100644
index 0000000000000000000000000000000000000000..be6c387f85e50b8bd56e0c6450dc6c0fa968568b
--- /dev/null
+++ b/test/unitTest/actors/account_list.yml
@@ -0,0 +1,112 @@
+default-account:
+      type: RING
+      upnpEnabled: "true"
+      archivePassword: ""
+      archivePIN: ""
+      archivePath: ""
+
+accounts:
+
+  alice:
+    displayName: ALICE
+    alias: ALICE
+
+  bob:
+    displayName: BOB
+    alias: BOB
+
+  charlie:
+    displayName: CHARLIE
+    alias: CHARLIE
+
+  dave:
+    displayName: DAVE
+    alias: DAVE
+
+  emily:
+    displayName: EMILY
+    alias: EMILY
+
+  frank:
+    displayName: FRANK
+    alias: FRANK
+
+  greg:
+    displayName: GREG
+    alias: GREG
+
+  holly:
+    displayName: HOLLY
+    alias: HOLLY
+
+  ian:
+    displayName: IAN
+    alias: IAN
+
+  jenna:
+    displayName: JENNA
+    alias: JENNA
+
+  kevin:
+    displayName: KEVIN
+    alias: KEVIN
+
+  lucy:
+    displayName: LUCY
+    alias: LUCY
+
+  mike:
+    displayName: MIKE
+    alias: MIKE
+
+  nora:
+    displayName: NORA
+    alias: NORA
+
+  olivia:
+    displayName: OLIVIA
+    alias: OLIVIA
+
+  pete:
+    displayName: PETE
+    alias: PETE
+
+  quinn:
+    displayName: QUINN
+    alias: QUINN
+
+  rachel:
+    displayName: RACHEL
+    alias: RACHEL
+
+  sam:
+    displayName: SAM
+    alias: SAM
+
+  tom:
+    displayName: TOM
+    alias: TOM
+
+  uma:
+    displayName: UMA
+    alias: UMA
+
+  victor:
+    displayName: VICTOR
+    alias: VICTOR
+
+  wendy:
+    displayName: WENDY
+    alias: WENDY
+
+  xander:
+    displayName: XANDER
+    alias: XANDER
+
+  yvonne:
+    displayName: YVONNE
+    alias: YVONNE
+
+  zoe:
+    displayName: ZOE
+    alias: ZOE
\ No newline at end of file
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 4bebdf2d0159ec8cc3ccb91989976c2bd4cdccc8..a3fe35c2d85e0ed787065b2d8639db484b219445 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -1409,8 +1409,8 @@ ConversationTest::testSetMessageDisplayedAfterClone()
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false, memberMessageGenerated = false,
-         msgDisplayed = false, aliceRegistered = false;
+    bool requestReceived = false, memberMessageGenerated = false, msgDisplayed = false,
+         aliceRegistered = false;
     confHandlers.insert(
         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
             [&](const std::string& /*accountId*/,
@@ -1504,9 +1504,6 @@ ConversationTest::testSetMessageDisplayedAfterClone()
                                 })
                    != membersInfos.end());
 
-
-
-
     libjami::unregisterSignalHandlers();
 }
 
@@ -2010,7 +2007,7 @@ ConversationTest::testVoteNoBadFile()
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
          voteMessageGenerated = false, messageBobReceived = false, messageCarlaReceived = false,
-         carlaConnected = true;
+         carlaConnected = false;
     ;
     confHandlers.insert(
         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
@@ -3535,15 +3532,12 @@ ConversationTest::testConversationPreferencesBeforeClone()
     aliceAccount->sendTrustRequest(bobUri, {});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; }));
 
     // Set preferences
     Manager::instance().sendRegister(aliceId, false);
     libjami::setConversationPreferences(bobId, convId, {{"foo", "bar"}, {"bar", "foo"}});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return preferencesBob.size() == 2;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return preferencesBob.size() == 2; }));
     CPPUNIT_ASSERT(preferencesBob["foo"] == "bar" && preferencesBob["bar"] == "foo");
 
     // Bob2 should sync preferences
@@ -3556,7 +3550,9 @@ ConversationTest::testConversationPreferencesBeforeClone()
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started && conversationReadyBob2 && !preferencesBob2.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bob2Started && conversationReadyBob2 && !preferencesBob2.empty();
+    }));
     CPPUNIT_ASSERT(preferencesBob2["foo"] == "bar" && preferencesBob2["bar"] == "foo");
 }
 
diff --git a/test/unitTest/conversation/conversationMembersEvent.cpp b/test/unitTest/conversation/conversationMembersEvent.cpp
index 6fe4f3042e6bf92f74ebcd98ecce9ed52e7fa832..418a864042bd554ba2acfa52c2b9a61207b6fa1a 100644
--- a/test/unitTest/conversation/conversationMembersEvent.cpp
+++ b/test/unitTest/conversation/conversationMembersEvent.cpp
@@ -430,6 +430,7 @@ ConversationMembersEventTest::testMemberAddedNoBadFile()
     generateFakeInvite(aliceAccount, convId, bobUri);
     // Generate conv request
     aliceAccount->sendTextMessage(bobUri,
+                                  std::string(bobAccount->currentDeviceId()),
                                   {{"application/invite+json",
                                     "{\"conversationId\":\"" + convId + "\"}"}});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
diff --git a/test/unitTest/conversation/conversationRequest.cpp b/test/unitTest/conversation/conversationRequest.cpp
index 71d853dbf550ba28a996b2871fdbba83417132dc..408914e96540260b7c684ef30d03fb5b538affe1 100644
--- a/test/unitTest/conversation/conversationRequest.cpp
+++ b/test/unitTest/conversation/conversationRequest.cpp
@@ -1401,8 +1401,9 @@ ConversationRequestTest::testRemoveContactRemoveTrustRequest()
     // First, Alice adds Bob
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestB1Received && requestB2Received; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !convId.empty() && requestB1Received && requestB2Received;
+    }));
 
     // Bob1 accepts, both device should get it
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
diff --git a/test/unitTest/conversationRepository/conversationRepository.cpp b/test/unitTest/conversationRepository/conversationRepository.cpp
index 861c2c6e65696db668151b4f3269857a3ebb397c..c181dccb15c32dfe319b57b7fcc4e3345fd053ad 100644
--- a/test/unitTest/conversationRepository/conversationRepository.cpp
+++ b/test/unitTest/conversationRepository/conversationRepository.cpp
@@ -52,7 +52,8 @@ public:
     ConversationRepositoryTest()
     {
         // Init daemon
-        libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
+        libjami::init(
+            libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
         if (not Manager::instance().initialized)
             CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
     }
@@ -66,16 +67,11 @@ public:
 
 private:
     void testCreateRepository();
-    void testCloneViaChannelSocket();
     void testAddSomeMessages();
     void testLogMessages();
-    void testFetch();
     void testMerge();
     void testFFMerge();
     void testDiff();
-    // NOTE: Just for debug. the test is a bit complex to write
-    // due to the clone/fetch verifications (initial commits, size).
-    // void testCloneHugeRepo();
 
     void testMergeProfileWithConflict();
 
@@ -90,15 +86,12 @@ private:
 
     CPPUNIT_TEST_SUITE(ConversationRepositoryTest);
     CPPUNIT_TEST(testCreateRepository);
-    CPPUNIT_TEST(testCloneViaChannelSocket);
     CPPUNIT_TEST(testAddSomeMessages);
     CPPUNIT_TEST(testLogMessages);
-    CPPUNIT_TEST(testFetch);
     CPPUNIT_TEST(testMerge);
     CPPUNIT_TEST(testFFMerge);
     CPPUNIT_TEST(testDiff);
     CPPUNIT_TEST(testMergeProfileWithConflict);
-    // CPPUNIT_TEST(testCloneHugeRepo);
 
     CPPUNIT_TEST_SUITE_END();
 };
@@ -127,7 +120,7 @@ ConversationRepositoryTest::testCreateRepository()
     auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId()));
     auto uri = aliceAccount->getUsername();
 
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     // Assert that repository exists
     CPPUNIT_ASSERT(repository != nullptr);
@@ -179,135 +172,11 @@ ConversationRepositoryTest::testCreateRepository()
     CPPUNIT_ASSERT(deviceCrtStr == deviceCert);
 }
 
-void
-ConversationRepositoryTest::testCloneViaChannelSocket()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId()));
-    auto uri = aliceAccount->getUsername();
-    auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId()));
-
-    bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-    aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
-    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
-                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id();
-    auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
-                      + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id();
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable rcv, scv;
-    bool successfullyConnected = false;
-    bool successfullyReceive = false;
-    bool receiverConnected = false;
-    std::shared_ptr<ChannelSocket> channelSocket = nullptr;
-    std::shared_ptr<ChannelSocket> sendSocket = nullptr;
-
-    bobAccount->connectionManager().onChannelRequest(
-        [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&,
-                               const std::string& name) {
-            successfullyReceive = name == "git://*";
-            return true;
-        });
-
-    aliceAccount->connectionManager().onChannelRequest(
-        [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&,
-                               const std::string&) { return true; });
-
-    bobAccount->connectionManager().onConnectionReady(
-        [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) {
-            receiverConnected = socket && (name == "git://*");
-            channelSocket = socket;
-            rcv.notify_one();
-        });
-
-    aliceAccount->connectionManager().connectDevice(bobDeviceId,
-                                                    "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket,
-                                                        const DeviceId&) {
-                                                        if (socket) {
-                                                            successfullyConnected = true;
-                                                            sendSocket = socket;
-                                                        }
-                                                        scv.notify_one();
-                                                    });
-
-    rcv.wait_for(lk, std::chrono::seconds(10));
-    scv.wait_for(lk, std::chrono::seconds(10));
-    CPPUNIT_ASSERT(successfullyReceive);
-    CPPUNIT_ASSERT(successfullyConnected);
-    CPPUNIT_ASSERT(receiverConnected);
-
-    bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket);
-    GitServer gs(aliceId, repository->id(), sendSocket);
-
-    auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(),
-                                                            aliceDeviceId.toString(),
-                                                            repository->id());
-    gs.stop();
-
-    CPPUNIT_ASSERT(cloned != nullptr);
-    CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
-
-    // Assert that first commit is signed by alice
-    git_repository* repo;
-    CPPUNIT_ASSERT(git_repository_open(&repo, clonedPath.c_str()) == 0);
-
-    // 1. Verify that last commit is correctly signed by alice
-    git_oid commit_id;
-    CPPUNIT_ASSERT(git_reference_name_to_id(&commit_id, repo, "HEAD") == 0);
-
-    git_buf signature = {}, signed_data = {};
-    git_commit_extract_signature(&signature, &signed_data, repo, &commit_id, "signature");
-    auto pk = base64::decode(std::string(signature.ptr, signature.ptr + signature.size));
-    auto data = std::vector<uint8_t>(signed_data.ptr, signed_data.ptr + signed_data.size);
-    git_repository_free(repo);
-
-    CPPUNIT_ASSERT(aliceAccount->identity().second->getPublicKey().checkSignature(data, pk));
-
-    // 2. Check created files
-    auto CRLsPath = clonedPath + DIR_SEPARATOR_STR + "CRLs" + DIR_SEPARATOR_STR
-                    + aliceDeviceId.toString();
-    CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
-
-    auto adminCrt = clonedPath + DIR_SEPARATOR_STR + "admins" + DIR_SEPARATOR_STR + uri + ".crt";
-    CPPUNIT_ASSERT(fileutils::isFile(adminCrt));
-
-    auto crt = std::ifstream(adminCrt);
-    std::string adminCrtStr((std::istreambuf_iterator<char>(crt)), std::istreambuf_iterator<char>());
-
-    auto cert = aliceAccount->identity().second;
-    auto deviceCert = cert->toString(false);
-    auto parentCert = cert->issuer->toString(true);
-
-    CPPUNIT_ASSERT(adminCrtStr == parentCert);
-
-    auto deviceCrt = clonedPath + DIR_SEPARATOR_STR + "devices" + DIR_SEPARATOR_STR
-                     + aliceDeviceId.toString() + ".crt";
-    CPPUNIT_ASSERT(fileutils::isFile(deviceCrt));
-
-    crt = std::ifstream(deviceCrt);
-    std::string deviceCrtStr((std::istreambuf_iterator<char>(crt)),
-                             std::istreambuf_iterator<char>());
-
-    CPPUNIT_ASSERT(deviceCrtStr == deviceCert);
-
-    // Check cloned messages
-    auto messages = cloned->log();
-    CPPUNIT_ASSERT(messages.size() == 1);
-    CPPUNIT_ASSERT(messages[0].id == repository->id());
-    CPPUNIT_ASSERT(
-        aliceAccount->identity().second->getPublicKey().checkSignature(messages[0].signed_content,
-                                                                       messages[0].signature));
-}
-
 void
 ConversationRepositoryTest::testAddSomeMessages()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     auto id1 = repository->commitMessage("Commit 1");
     auto id2 = repository->commitMessage("Commit 2");
@@ -346,7 +215,7 @@ void
 ConversationRepositoryTest::testLogMessages()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     auto id1 = repository->commitMessage("Commit 1");
     auto id2 = repository->commitMessage("Commit 2");
@@ -371,135 +240,6 @@ ConversationRepositoryTest::testLogMessages()
     CPPUNIT_ASSERT(messages[0].id == repository->id());
 }
 
-void
-ConversationRepositoryTest::testFetch()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId()));
-    auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId()));
-
-    bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-    aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
-    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
-                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id();
-    auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
-                      + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id();
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable rcv, scv, ccv;
-    bool successfullyConnected = false;
-    bool successfullyReceive = false;
-    bool receiverConnected = false;
-    std::shared_ptr<ChannelSocket> channelSocket = nullptr;
-    std::shared_ptr<ChannelSocket> sendSocket = nullptr;
-
-    bobAccount->connectionManager().onChannelRequest(
-        [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) {
-            successfullyReceive = name == "git://*";
-            ccv.notify_one();
-            return true;
-        });
-
-    aliceAccount->connectionManager().onChannelRequest(
-        [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string&) { return true; });
-
-    bobAccount->connectionManager().onConnectionReady(
-        [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) {
-            receiverConnected = socket && (name == "git://*");
-            channelSocket = socket;
-            rcv.notify_one();
-        });
-
-    aliceAccount->connectionManager().connectDevice(bobDeviceId,
-                                                    "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket,
-                                                        const DeviceId&) {
-                                                        if (socket) {
-                                                            successfullyConnected = true;
-                                                            sendSocket = socket;
-                                                        }
-                                                        scv.notify_one();
-                                                    });
-
-    rcv.wait_for(lk, std::chrono::seconds(10));
-    scv.wait_for(lk, std::chrono::seconds(10));
-    CPPUNIT_ASSERT(successfullyReceive);
-    CPPUNIT_ASSERT(successfullyConnected);
-    CPPUNIT_ASSERT(receiverConnected);
-    CPPUNIT_ASSERT(repository != nullptr);
-
-    bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket);
-    GitServer gs(aliceId, repository->id(), sendSocket);
-
-    // Clone repository
-    auto id1 = repository->commitMessage("Commit 1");
-
-    auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(),
-                                                            aliceDeviceId.toString(),
-                                                            repository->id());
-    gs.stop();
-    bobAccount->removeGitSocket(aliceDeviceId, repository->id());
-
-    // Add some new messages to fetch
-    auto id2 = repository->commitMessage("Commit 2");
-    auto id3 = repository->commitMessage("Commit 3");
-
-    // Open a new channel to simulate the fact that we are later
-    aliceAccount->connectionManager().connectDevice(bobDeviceId,
-                                                    "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket,
-                                                        const DeviceId&) {
-                                                        if (socket) {
-                                                            successfullyConnected = true;
-                                                            sendSocket = socket;
-                                                        }
-                                                        scv.notify_one();
-                                                    });
-
-    rcv.wait_for(lk, std::chrono::seconds(10));
-    scv.wait_for(lk, std::chrono::seconds(10));
-    ccv.wait_for(lk, std::chrono::seconds(10));
-    bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket);
-    GitServer gs2(aliceId, repository->id(), sendSocket);
-
-    CPPUNIT_ASSERT(cloned->fetch(aliceDeviceId.toString()));
-    CPPUNIT_ASSERT(id3 == cloned->remoteHead(aliceDeviceId.toString()));
-
-    gs2.stop();
-    bobAccount->removeGitSocket(aliceDeviceId, repository->id());
-
-    auto messages = cloned->log({id3});
-    CPPUNIT_ASSERT(messages.size() == 4 /* 3 + initial */);
-    CPPUNIT_ASSERT(messages[0].id == id3);
-    CPPUNIT_ASSERT(messages[0].parents.front() == id2);
-    CPPUNIT_ASSERT(messages[0].commit_msg == "Commit 3");
-    CPPUNIT_ASSERT(messages[0].author.name == messages[3].author.name);
-    CPPUNIT_ASSERT(messages[0].author.email == messages[3].author.email);
-    CPPUNIT_ASSERT(messages[1].id == id2);
-    CPPUNIT_ASSERT(messages[1].parents.front() == id1);
-    CPPUNIT_ASSERT(messages[1].commit_msg == "Commit 2");
-    CPPUNIT_ASSERT(messages[1].author.name == messages[3].author.name);
-    CPPUNIT_ASSERT(messages[1].author.email == messages[3].author.email);
-    CPPUNIT_ASSERT(messages[2].id == id1);
-    CPPUNIT_ASSERT(messages[2].commit_msg == "Commit 1");
-    CPPUNIT_ASSERT(messages[2].author.name == messages[3].author.name);
-    CPPUNIT_ASSERT(messages[2].author.email == messages[3].author.email);
-    CPPUNIT_ASSERT(messages[2].parents.front() == repository->id());
-    // Check sig
-    CPPUNIT_ASSERT(
-        aliceAccount->identity().second->getPublicKey().checkSignature(messages[0].signed_content,
-                                                                       messages[0].signature));
-    CPPUNIT_ASSERT(
-        aliceAccount->identity().second->getPublicKey().checkSignature(messages[1].signed_content,
-                                                                       messages[1].signature));
-    CPPUNIT_ASSERT(
-        aliceAccount->identity().second->getPublicKey().checkSignature(messages[2].signed_content,
-                                                                       messages[2].signature));
-}
-
 std::string
 ConversationRepositoryTest::addCommit(git_repository* repo,
                                       const std::shared_ptr<JamiAccount> account,
@@ -617,7 +357,7 @@ void
 ConversationRepositoryTest::testMerge()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     // Assert that repository exists
     CPPUNIT_ASSERT(repository != nullptr);
@@ -652,7 +392,7 @@ void
 ConversationRepositoryTest::testFFMerge()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     // Assert that repository exists
     CPPUNIT_ASSERT(repository != nullptr);
@@ -689,7 +429,7 @@ ConversationRepositoryTest::testDiff()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId()));
     auto uri = aliceAccount->getUsername();
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     auto id1 = repository->commitMessage("Commit 1");
     auto id2 = repository->commitMessage("Commit 2");
@@ -708,7 +448,7 @@ void
 ConversationRepositoryTest::testMergeProfileWithConflict()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+    auto repository = ConversationRepository::createConversation(aliceAccount);
 
     // Assert that repository exists
     CPPUNIT_ASSERT(repository != nullptr);
@@ -763,89 +503,6 @@ ConversationRepositoryTest::testMergeProfileWithConflict()
     CPPUNIT_ASSERT(repository->log().size() == 5 /* Initial, add, modify 1, modify 2, merge */);
 }
 
-/*
-void
-ConversationRepositoryTest::testCloneHugeRepo()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId()));
-    auto uri = aliceAccount->getUsername();
-    auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId()));
-
-    bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-    aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
-
-    auto convPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
-                    + DIR_SEPARATOR_STR + "conversations";
-    fileutils::recursive_mkdir(convPath);
-    const auto copyOptions = std::filesystem::copy_options::overwrite_existing |
-std::filesystem::copy_options::recursive; auto repoPath = convPath + DIR_SEPARATOR_STR +
-"8d3be095ebff73be1c43f193d02407b946d7895d"; std::filesystem::copy("/home/amarok/daemon/", repoPath,
-copyOptions);
-
-    auto repository = ConversationRepository(aliceAccount->weak(),
-"8d3be095ebff73be1c43f193d02407b946d7895d"); auto clonedPath = fileutils::get_data_dir() +
-DIR_SEPARATOR_STR + bobAccount->getAccountID()
-                      + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository.id();
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable rcv, scv;
-    bool successfullyConnected = false;
-    bool successfullyReceive = false;
-    bool receiverConnected = false;
-    std::shared_ptr<ChannelSocket> channelSocket = nullptr;
-    std::shared_ptr<ChannelSocket> sendSocket = nullptr;
-
-    bobAccount->connectionManager().onChannelRequest(
-        [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, const std::string&
-name) { successfullyReceive = name == "git://*"; return true;
-        });
-
-    aliceAccount->connectionManager().onChannelRequest(
-        [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, const std::string&
-name) { return true; });
-
-    bobAccount->connectionManager().onConnectionReady(
-        [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) {
-            receiverConnected = socket && (name == "git://*");
-            channelSocket = socket;
-            rcv.notify_one();
-        });
-
-    aliceAccount->connectionManager().connectDevice(bobDeviceId,
-                                                    "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket,
-                                                        const DeviceId&) {
-                                                        if (socket) {
-                                                            successfullyConnected = true;
-                                                            sendSocket = socket;
-                                                        }
-                                                        scv.notify_one();
-                                                    });
-
-    ;
-    scv.wait_for(lk, std::chrono::seconds(10));
-    CPPUNIT_ASSERT(rcv.wait_for(lk, std::chrono::seconds(10), [&] { return receiverConnected; }));
-    CPPUNIT_ASSERT(scv.wait_for(lk, std::chrono::seconds(10), [&] { return successfullyConnected;
-})); CPPUNIT_ASSERT(successfullyReceive);
-
-    bobAccount->addGitSocket(aliceDeviceId, repository.id(), channelSocket);
-    GitServer gs(aliceId, repository.id(), sendSocket);
-
-    JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
-    auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(),
-                                                            aliceDeviceId,
-                                                            repository.id());
-    gs.stop();
-    JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ END CLONE");
-
-    CPPUNIT_ASSERT(cloned != nullptr);
-    CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
-}
-*/
-
 } // namespace test
 } // namespace jami
 
diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp
index 2f4b08bd9b55b61c40cb67fdefc96dc0ebd2f657..e8cd6c1500d4e991a5bbb56912b21b0ba0d4fc50 100644
--- a/test/unitTest/fileTransfer/fileTransfer.cpp
+++ b/test/unitTest/fileTransfer/fileTransfer.cpp
@@ -37,6 +37,7 @@
 
 using namespace std::literals::chrono_literals;
 using namespace libjami::Account;
+using namespace std::literals::chrono_literals;
 
 namespace jami {
 namespace test {
@@ -218,9 +219,7 @@ FileTransferTest::testConversationFileTransfer()
 
     libjami::acceptConversationRequest(bobId, convId);
     libjami::acceptConversationRequest(carlaId, convId);
-    cv.wait_for(lk, 30s, [&]() {
-        return conversationReady == 3 && memberJoined == 2;
-    });
+    cv.wait_for(lk, 30s, [&]() { return conversationReady == 3 && memberJoined == 2; });
 
     // Send file
     std::ofstream sendFile(sendPath);
@@ -230,15 +229,12 @@ FileTransferTest::testConversationFileTransfer()
 
     libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() {
-        return !tidBob.empty() && !tidCarla.empty();
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return !tidBob.empty() && !tidCarla.empty(); }));
 
     libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath);
     libjami::downloadFile(carlaId, convId, iidCarla, tidCarla, recv2Path);
 
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 45s, [&]() { return finished.size() == 3; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return finished.size() == 3; }));
 
     libjami::unregisterSignalHandlers();
 }
@@ -313,9 +309,7 @@ FileTransferTest::testFileTransferInConversation()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && bobJoined;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -330,9 +324,7 @@ FileTransferTest::testFileTransferInConversation()
     transferAFinished = false;
     transferBFinished = false;
     libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return transferAFinished && transferBFinished;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; }));
 
     libjami::unregisterSignalHandlers();
     std::this_thread::sleep_for(5s);
@@ -408,9 +400,7 @@ FileTransferTest::testVcfFileTransferInConversation()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && bobJoined;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -425,9 +415,7 @@ FileTransferTest::testVcfFileTransferInConversation()
     transferAFinished = false;
     transferBFinished = false;
     libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return transferAFinished && transferBFinished;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; }));
 
     libjami::unregisterSignalHandlers();
     std::this_thread::sleep_for(5s);
@@ -512,9 +500,7 @@ FileTransferTest::testBadSha3sumOut()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && memberJoin;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && memberJoin; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -537,9 +523,7 @@ FileTransferTest::testBadSha3sumOut()
     libjami::downloadFile(bobId, convId, iid, mid, recvPath);
 
     // The file transfer will not be sent as modified
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() {
-        return transferAFinished || transferBFinished;
-    }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return transferAFinished || transferBFinished; }));
 
     libjami::unregisterSignalHandlers();
 }
@@ -623,9 +607,7 @@ FileTransferTest::testBadSha3sumIn()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
-        return conversationReady && memberJoin;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -738,23 +720,18 @@ FileTransferTest::testAskToMultipleParticipants()
 
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    requestReceived = false;
+    libjami::addConversationMember(aliceId, convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
-        return conversationReady && memberJoin;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; }));
 
-    requestReceived = false;
     conversationReady = false;
     memberJoin = false;
 
-    libjami::addConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
-        return conversationReady && memberJoin;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -764,9 +741,7 @@ FileTransferTest::testAskToMultipleParticipants()
 
     libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return !bobTid.empty() && !carlaTid.empty();
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobTid.empty() && !carlaTid.empty(); }));
 
     transferCFinished = false;
     libjami::downloadFile(carlaId, convId, iidCarla, carlaTid, recv2Path);
@@ -853,9 +828,7 @@ FileTransferTest::testCancelInTransfer()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && bobJoined;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -949,9 +922,7 @@ FileTransferTest::testTransferInfo()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && bobJoined;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; }));
 
     // Create file to send
     std::ofstream sendFile(sendPath);
@@ -973,9 +944,7 @@ FileTransferTest::testTransferInfo()
     transferAFinished = false;
     transferBFinished = false;
     libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return transferAFinished && transferBFinished;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; }));
     CPPUNIT_ASSERT(libjami::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress)
                    == libjami::DataTransferError::success);
 
@@ -1019,17 +988,16 @@ FileTransferTest::testRemoveHardLink()
             conversationReady = true;
         }));
     auto conversationRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-        [&](const std::string& /*accountId*/, const std::string& /* conversationId */) {
-            conversationRemoved = true;
-        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
+            [&](const std::string& /*accountId*/, const std::string& /* conversationId */) {
+                conversationRemoved = true;
+            }));
     libjami::registerSignalHandlers(confHandlers);
 
     auto convId = libjami::startConversation(aliceId);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
 
     // Send file
     std::ofstream sendFile(sendPath);
@@ -1040,15 +1008,11 @@ FileTransferTest::testRemoveHardLink()
     libjami::sendFile(aliceId, convId, sendPath, std::filesystem::absolute("SEND"), "");
 
     messageReceived = false;
-    CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() {
-        return messageReceived;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return messageReceived; }));
 
     CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationRemoved;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
 
     auto content = fileutils::loadTextFile(sendPath);
     CPPUNIT_ASSERT(content.find("AAA") != std::string::npos);
diff --git a/test/unitTest/swarm/bootstrap.cpp b/test/unitTest/swarm/bootstrap.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c5c9b65e8fed236b94f5cd99dde2620aabe23aa
--- /dev/null
+++ b/test/unitTest/swarm/bootstrap.cpp
@@ -0,0 +1,392 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <condition_variable>
+#include <msgpack.hpp>
+#include <filesystem>
+
+#include "../../test_runner.h"
+#include "account_const.h"
+#include "common.h"
+#include "conversation_interface.h"
+#include "fileutils.h"
+#include "jami.h"
+#include "jamidht/conversation.h"
+#include "jamidht/jamiaccount.h"
+#include "jamidht/swarm/swarm_channel_handler.h"
+#include "manager.h"
+
+using namespace std::string_literals;
+using namespace std::literals::chrono_literals;
+using namespace libjami::Account;
+
+namespace jami {
+
+struct ConvInfoTest
+{
+    std::string accountId;
+    std::string convId;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    std::vector<std::map<std::string, std::string>> messages;
+    Conversation::BootstrapStatus bootstrap {Conversation::BootstrapStatus::FAILED};
+};
+
+namespace test {
+
+class BootstrapTest : public CppUnit::TestFixture
+{
+public:
+    ~BootstrapTest() { libjami::fini(); }
+    static std::string name() { return "Bootstrap"; }
+    void setUp();
+    void tearDown();
+
+    ConvInfoTest aliceData;
+    ConvInfoTest bobData;
+    ConvInfoTest bob2Data;
+    ConvInfoTest carlaData;
+
+    std::mutex mtx;
+    std::condition_variable cv;
+
+private:
+    void connectSignals();
+
+    void testBootstrapOk();
+    void testBootstrapFailed();
+    void testBootstrapNeverNewDevice();
+    void testBootstrapCompat();
+
+    CPPUNIT_TEST_SUITE(BootstrapTest);
+    CPPUNIT_TEST(testBootstrapOk);
+    CPPUNIT_TEST(testBootstrapFailed);
+    CPPUNIT_TEST(testBootstrapNeverNewDevice);
+    CPPUNIT_TEST(testBootstrapCompat);
+    CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BootstrapTest, BootstrapTest::name());
+
+void
+BootstrapTest::setUp()
+{
+    aliceData = {};
+    bobData = {};
+    bob2Data = {};
+    carlaData = {};
+
+    // Init daemon
+    libjami::init(
+        libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
+    if (not Manager::instance().initialized)
+        CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
+
+    auto actors = load_actors("actors/alice-bob-carla.yml");
+    aliceData.accountId = actors["alice"];
+    bobData.accountId = actors["bob"];
+    carlaData.accountId = actors["carla"];
+
+    Manager::instance().sendRegister(carlaData.accountId, false);
+    wait_for_announcement_of({aliceData.accountId, bobData.accountId});
+    connectSignals();
+}
+
+void
+BootstrapTest::tearDown()
+{
+    libjami::unregisterSignalHandlers();
+    auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
+    std::remove(bobArchive.c_str());
+
+    if (bob2Data.accountId.empty()) {
+        wait_for_removal_of({aliceData.accountId, bobData.accountId, carlaData.accountId});
+    } else {
+        wait_for_removal_of(
+            {aliceData.accountId, bobData.accountId, carlaData.accountId, bob2Data.accountId});
+    }
+}
+
+void
+BootstrapTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& convId) {
+            if (accountId == aliceData.accountId) {
+                aliceData.convId = convId;
+                aliceData.conversationReady = true;
+            } else if (accountId == bobData.accountId) {
+                bobData.convId = convId;
+                bobData.conversationReady = true;
+            } else if (accountId == bob2Data.accountId) {
+                bob2Data.convId = convId;
+                bob2Data.conversationReady = true;
+            } else if (accountId == carlaData.accountId) {
+                carlaData.convId = convId;
+                carlaData.conversationReady = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                if (accountId == aliceData.accountId) {
+                    aliceData.requestReceived = true;
+                } else if (accountId == bobData.accountId) {
+                    bobData.requestReceived = true;
+                } else if (accountId == bob2Data.accountId) {
+                    bob2Data.requestReceived = true;
+                } else if (accountId == carlaData.accountId) {
+                    carlaData.requestReceived = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /*conversationId*/,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceData.accountId) {
+                aliceData.messages.emplace_back(message);
+            } else if (accountId == bobData.accountId) {
+                bobData.messages.emplace_back(message);
+            } else if (accountId == bob2Data.accountId) {
+                bob2Data.messages.emplace_back(message);
+            } else if (accountId == carlaData.accountId) {
+                carlaData.messages.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    libjami::registerSignalHandlers(confHandlers);
+
+    // Link callback for convModule()
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaData.accountId);
+
+    aliceAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            aliceData.bootstrap = status;
+            cv.notify_one();
+        });
+    bobAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            bobData.bootstrap = status;
+            cv.notify_one();
+        });
+    carlaAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            carlaData.bootstrap = status;
+            cv.notify_one();
+        });
+}
+
+void
+BootstrapTest::testBootstrapOk()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
+    auto bobUri = bobAccount->getUsername();
+
+    aliceAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            aliceData.bootstrap = status;
+            cv.notify_one();
+        });
+
+    std::unique_lock<std::mutex> lk {mtx};
+    auto convId = libjami::startConversation(aliceData.accountId);
+
+    libjami::addConversationMember(aliceData.accountId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobData.accountId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
+               && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
+               && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
+    }));
+}
+
+void
+BootstrapTest::testBootstrapFailed()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
+    auto bobUri = bobAccount->getUsername();
+
+    aliceAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            aliceData.bootstrap = status;
+            cv.notify_one();
+        });
+
+    std::unique_lock<std::mutex> lk {mtx};
+    auto convId = libjami::startConversation(aliceData.accountId);
+
+    libjami::addConversationMember(aliceData.accountId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobData.accountId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
+               && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
+               && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
+    }));
+
+    // Now bob goes offline, it should disconnect alice
+    Manager::instance().sendRegister(bobData.accountId, false);
+    // Alice will try to maintain before failing (so will take 30secs to fail)
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return aliceData.bootstrap == Conversation::BootstrapStatus::FAILED;
+    }));
+}
+
+void
+BootstrapTest::testBootstrapNeverNewDevice()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
+    auto bobUri = bobAccount->getUsername();
+
+    aliceAccount->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            aliceData.bootstrap = status;
+            cv.notify_one();
+        });
+
+    std::unique_lock<std::mutex> lk {mtx};
+    auto convId = libjami::startConversation(aliceData.accountId);
+
+    libjami::addConversationMember(aliceData.accountId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobData.accountId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
+               && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
+               && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
+    }));
+
+    // Alice offline
+    Manager::instance().sendRegister(aliceData.accountId, false);
+    // Bob will try to maintain before failing (so will take 30secs to fail)
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bobData.bootstrap == Conversation::BootstrapStatus::FAILED;
+    }));
+
+    // Create bob2
+    auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
+    std::remove(bobArchive.c_str());
+    bobAccount->exportArchive(bobArchive);
+    std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
+    details[ConfProperties::TYPE] = "RING";
+    details[ConfProperties::DISPLAYNAME] = "BOB2";
+    details[ConfProperties::ALIAS] = "BOB2";
+    details[ConfProperties::UPNP_ENABLED] = "true";
+    details[ConfProperties::ARCHIVE_PASSWORD] = "";
+    details[ConfProperties::ARCHIVE_PIN] = "";
+    details[ConfProperties::ARCHIVE_PATH] = bobArchive;
+    bob2Data.accountId = Manager::instance().addAccount(details);
+    auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Data.accountId);
+
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    bool bob2Connected = false;
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = bob2Account->getVolatileAccountDetails();
+                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus != "UNREGISTERED")
+                    bob2Connected = true;
+                cv.notify_one();
+            }));
+    libjami::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Connected; }));
+    bob2Account->convModule()->onBootstrapStatus(
+        [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
+            bob2Data.bootstrap = status;
+            cv.notify_one();
+        });
+
+    // Disconnect bob2, to create a valid conv betwen Alice and Bob1
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS
+               && bob2Data.bootstrap == Conversation::BootstrapStatus::SUCCESS;
+    }));
+
+    // Bob offline
+    Manager::instance().sendRegister(bobData.accountId, false);
+    // Bob2 will try to maintain before failing (so will take 30secs to fail)
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bob2Data.bootstrap == Conversation::BootstrapStatus::FAILED;
+    }));
+
+    // Alice bootstrap should go to fallback (because bob2 never wrote into the conversation) & Connected
+    Manager::instance().sendRegister(aliceData.accountId, true);
+    // Wait for announcement, ICE fallback + delay
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
+    }));
+}
+
+void
+BootstrapTest::testBootstrapCompat()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
+    auto bobUri = bobAccount->getUsername();
+
+    dynamic_cast<SwarmChannelHandler*>(aliceAccount->channelHandlers()[Uri::Scheme::SWARM].get())
+        ->disableSwarmManager
+        = true;
+
+    std::unique_lock<std::mutex> lk {mtx};
+    auto convId = libjami::startConversation(aliceData.accountId);
+
+    libjami::addConversationMember(aliceData.accountId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobData.accountId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1;
+    }));
+
+    auto bobMsgSize = bobData.messages.size();
+    libjami::sendMessage(aliceData.accountId, convId, "hi"s, "");
+    cv.wait_for(lk, 30s, [&]() {
+        return bobData.messages.size() == bobMsgSize + 1
+               && bobData.bootstrap == Conversation::BootstrapStatus::FAILED;
+    });
+}
+
+} // namespace test
+} // namespace jami
+
+RING_TEST_RUNNER(jami::test::BootstrapTest::name())
diff --git a/test/unitTest/swarm/routing_table.cpp b/test/unitTest/swarm/routing_table.cpp
index f031fe7f1aed1bbff491afd64d76c2c6abd08e98..e67c5c44e1f0ef1d659b421fb1c305d437e03f6b 100644
--- a/test/unitTest/swarm/routing_table.cpp
+++ b/test/unitTest/swarm/routing_table.cpp
@@ -1297,7 +1297,7 @@ RoutingTableTest::testSwarmManagersWMobileModes()
         }
     }
 
-    sleep(5);
+    sleep(10);
 
     {
         if (!swarmManagers.empty()) {
@@ -1306,7 +1306,7 @@ RoutingTableTest::testSwarmManagersWMobileModes()
         }
     }
 
-    sleep(4);
+    sleep(10);
 
     CPPUNIT_ASSERT_EQUAL_MESSAGE("Supposed to be equal",
                                  swarmManagers.size(),
diff --git a/test/unitTest/swarm/swarm_conversation.cpp b/test/unitTest/swarm/swarm_conversation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c94dac82db9a5ff08f5f33782521dcdbf5f86f16
--- /dev/null
+++ b/test/unitTest/swarm/swarm_conversation.cpp
@@ -0,0 +1,223 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  Author: Fadi Shehadeh <fadi.shehadeh@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <condition_variable>
+#include <filesystem>
+#include <string>
+
+#include "jami.h"
+#include "../common.h"
+#include "jamidht/swarm/swarm_manager.h"
+#include "connectivity/multiplexed_socket.h"
+
+#include "connectivity/peer_connection.h"
+#include <opendht/thread_pool.h>
+
+#include "../../test_runner.h"
+#include "account_const.h"
+#include "conversation/conversationcommon.h"
+#include "manager.h"
+
+using namespace dht;
+using NodeId = dht::PkId;
+using namespace std::literals::chrono_literals;
+
+namespace jami {
+namespace test {
+
+struct ConvData
+{
+    std::string id {};
+    bool requestReceived {false};
+    bool needsHost {false};
+    bool conferenceChanged {false};
+    bool conferenceRemoved {false};
+    std::string hostState {};
+    std::vector<std::map<std::string, std::string>> messages {};
+};
+
+class SwarmConversationTest : public CppUnit::TestFixture
+{
+public:
+    ~SwarmConversationTest();
+    static std::string name() { return "SwarmConversationTest"; }
+    void setUp();
+
+    std::map<std::string, ConvData> accountMap;
+    std::vector<std::string> accountIds;
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+
+private:
+    void connectSignals();
+
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+
+    void testSendMessage();
+
+    CPPUNIT_TEST_SUITE(SwarmConversationTest);
+    CPPUNIT_TEST(testSendMessage);
+    CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwarmConversationTest, SwarmConversationTest::name());
+
+void
+SwarmConversationTest::setUp()
+{
+    libjami::init(
+        libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
+    if (not Manager::instance().initialized)
+        CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
+
+    auto actors = load_actors("actors/account_list.yml");
+
+    for (const auto& act : actors) {
+        auto id = act.second;
+        accountIds.emplace_back(id);
+        std::cout << act.second << std::endl;
+        accountMap.insert({id, {}});
+    }
+
+    wait_for_announcement_of(accountIds, 60s);
+}
+
+SwarmConversationTest::~SwarmConversationTest()
+{
+    wait_for_removal_of(accountIds);
+    libjami::fini();
+}
+
+void
+SwarmConversationTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            for (const auto& accId : accountIds) {
+                if (accountId == accId) {
+                    accountMap[accId].id = conversationId;
+                }
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            for (const auto& accId : accountIds) {
+                if (accountId == accId && accountMap[accId].id == conversationId) {
+                    accountMap[accId].messages.emplace_back(message);
+                }
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string&,
+                std::map<std::string, std::string>) {
+                for (const auto& accId : accountIds) {
+                    if (accountId == accId) {
+                        accountMap[accId].requestReceived = true;
+                    }
+                }
+                cv.notify_one();
+            }));
+
+    libjami::registerSignalHandlers(confHandlers);
+}
+
+void
+SwarmConversationTest::testSendMessage()
+{
+    std::map<std::string, std::shared_ptr<JamiAccount>> jamiAccounts;
+
+    for (const auto& accId : accountIds) {
+        jamiAccounts.insert({accId, Manager::instance().getAccount<JamiAccount>(accId)});
+        std::cout << "created account for: " << accId << std::endl;
+    }
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+
+    connectSignals();
+
+    auto aliceId = jamiAccounts.begin()->first;
+    auto convId = libjami::startConversation(aliceId);
+
+    std::cout << "started conversation: " << convId << std::endl;
+
+    for (auto it = std::next(jamiAccounts.begin()); it != jamiAccounts.end(); ++it) {
+        auto userUri = it->second->getUsername();
+        std::cout << "adding member: " << userUri << std::endl;
+        libjami::addConversationMember(aliceId, convId, userUri);
+
+        CPPUNIT_ASSERT_EQUAL_MESSAGE("ERROR", true, cv.wait_for(lk, 40s, [&]() {
+            return accountMap[it->first].requestReceived == true;
+        }));
+
+        libjami::acceptConversationRequest(it->first, convId);
+        std::this_thread::sleep_for(std::chrono::seconds(10));
+    }
+
+    std::cout << "waiting for conversation ready" << std::endl;
+    for (size_t i = 1; i < accountIds.size(); i++) {
+        CPPUNIT_ASSERT(
+            cv.wait_for(lk, 40s, [&]() { return accountMap[accountIds.at(i)].id == convId; }));
+    }
+    std::cout << "messages size " << accountMap[accountIds.at(0)].messages.size() << std::endl;
+
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 70s, [&]() { return accountMap[accountIds.at(0)].messages.size() >= 2; }));
+
+    libjami::sendMessage(aliceId, convId, "hi"s, "");
+
+    for (size_t i = 1; i < accountIds.size(); i++) {
+        std::cout << "COUNTER: " << i << " messages size "
+                  << accountMap[accountIds.at(i)].messages.size() << std::endl;
+
+        if (accountMap[accountIds.at(i)].messages.size() >= 105) {
+            for (const auto& msg : accountMap[accountIds.at(i)].messages) {
+                std::cout << "Message id: " << msg.at("id") << " type: " << msg.at("type")
+                          << std::endl;
+            }
+        }
+
+        CPPUNIT_ASSERT(cv.wait_for(lk, 40s, [&]() {
+            return accountMap[accountIds.at(i)].messages.size() >= 1;
+        }));
+    }
+
+    libjami::unregisterSignalHandlers();
+}
+
+} // namespace test
+} // namespace jami
+
+RING_TEST_RUNNER(jami::test::SwarmConversationTest::name())
diff --git a/test/unitTest/swarm/swarm_spread.cpp b/test/unitTest/swarm/swarm_spread.cpp
index 51c0fc65d8fb8ab8a5bac999b60f87f8bb37978d..c5a0e934d756e628a3a367b683ecdae6d5bef23a 100644
--- a/test/unitTest/swarm/swarm_spread.cpp
+++ b/test/unitTest/swarm/swarm_spread.cpp
@@ -45,10 +45,10 @@ using NodeId = dht::PkId;
 namespace jami {
 namespace test {
 
-constexpr size_t nNodes = 5000;
+constexpr size_t nNodes = 10;
 
 constexpr size_t BOOTSTRAP_SIZE = 2;
-auto time = 300s;
+auto time = 30s;
 
 int TOTAL_HOPS = 0;
 int moyenne = 0;
@@ -200,7 +200,7 @@ SwarmMessageSpread::sendMessage(const std::shared_ptr<ChannelSocketInterface>& s
 
     dht::ThreadPool::io().run([socket, buffer = std::move(buffer)] {
         std::error_code ec;
-        std::this_thread::sleep_for(std::chrono::milliseconds(50));
+        // std::this_thread::sleep_for(std::chrono::milliseconds(50));
 
         socket->write(reinterpret_cast<const unsigned char*>(buffer->data()), buffer->size(), ec);
     });
@@ -302,7 +302,7 @@ SwarmMessageSpread::needSocketCallBack(const std::shared_ptr<SwarmManager>& sm)
                     ChannelSocketTest::link(cstMe.second, cstRemote.second);
                     receiveMessage(myId, cstMe.second);
                     receiveMessage(node, cstRemote.second);
-                    std::this_thread::sleep_for(std::chrono::seconds(5));
+                    // std::this_thread::sleep_for(std::chrono::seconds(5));
                     ChannelSocketTest::link(cstMe.first, cstRemote.first);
                     smRemote->addChannel(cstRemote.first);
                     onSocket(cstMe.first);