diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index fb13b8835c5a614117b302721e690f7e342ac6f9..cdcbadd9b8247bcb6371beb5dcd1600f1818e7e4 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -154,6 +154,44 @@
            </arg>
        </method>
 
+       <method name="exportOnRing" tp:name-for-bindings="exportOnRing">
+           <tp:docstring>
+               Export account on the DHT using the given password and generated PIN (returned through exportOnRingEnded signal).
+           </tp:docstring>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <arg type="s" name="password" direction="in">
+           </arg>
+           <arg type="b" name="success" direction="out">
+               <tp:docstring>
+                   True if the operation was initialized successfully. exportOnRingEnded will be trigered on completion.
+               </tp:docstring>
+           </arg>
+       </method>
+
+       <signal name="exportOnRingEnded" tp:name-for-bindings="exportOnRingEnded">
+           <tp:docstring>
+               Notify clients when the exportOnRing operation ended.
+           </tp:docstring>
+           <arg type="s" name="accountID">
+           </arg>
+           <arg type="i" name="status">
+               <tp:docstring>
+                   Status code: 0 for success
+                  <ul>
+                      <li>SUCCESS = 0         everything went fine. PIN is set.</li>
+                      <li>WRONG_PASSWORD = 1  wrong password provided.</li>
+                      <li>NETWORK_ERROR = 2   can't publish archive on the network.</li>
+                  </ul>
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="PIN">
+               <tp:docstring>
+                   A PIN to show to the user to import the account from somewhere else.
+               </tp:docstring>
+           </arg>
+       </signal>
+
        <method name="testAccountICEInitialization" tp:name-for-bindings="testAccountICEInitialization">
            <tp:docstring>
                Test initializing an ICE transport with the current account configuration.
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 0c9714827302575c014aaa8e2a2d7b2ecb252645..73c1d937e50d871d09829ec954d7d4da9a61a780 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -176,6 +176,7 @@ DBusClient::initLibrary(int flags)
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&DBusConfigurationManager::incomingAccountMessage, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&DBusConfigurationManager::accountMessageStatusChanged, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&DBusConfigurationManager::incomingTrustRequest, confM, _1, _2, _3, _4 )),
+        exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&DBusConfigurationManager::exportOnRingEnded, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::CertificatePinned>(bind(&DBusConfigurationManager::certificatePinned, confM, _1 )),
         exportable_callback<ConfigurationSignal::CertificatePathPinned>(bind(&DBusConfigurationManager::certificatePathPinned, confM, _1, _2 )),
         exportable_callback<ConfigurationSignal::CertificateExpired>(bind(&DBusConfigurationManager::certificateExpired, confM, _1 )),
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 129ba9ffdc55c1ca291d5f8a3f0708fb0c3a65dd..8546e74e0a5e1705082e4bc711f5c1e22a881e82 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -74,6 +74,12 @@ DBusConfigurationManager::addAccount(const std::map<std::string, std::string>& d
     return DRing::addAccount(details);
 }
 
+auto
+DBusConfigurationManager::exportOnRing(const std::string& accountID, const std::string& password) -> decltype(DRing::exportOnRing(accountID, password))
+{
+    return DRing::exportOnRing(accountID, password);
+}
+
 void
 DBusConfigurationManager::removeAccount(const std::string& accountID)
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 743d40952dd0ea8cd7e61de9fa2aae52091620be..e6ecec8c6e59c34601a6b23bbb5dd42fcd4bbb73 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -63,6 +63,7 @@ class DBusConfigurationManager :
         void setAccountActive(const std::string& accountID, const bool& active);
         std::map<std::string, std::string> getAccountTemplate(const std::string& accountType);
         std::string addAccount(const std::map<std::string, std::string>& details);
+        bool exportOnRing(const std::string& accountID, const std::string& password);
         void removeAccount(const std::string& accoundID);
         std::vector<std::string> getAccountList();
         void sendRegister(const std::string& accoundID, const bool& enable);
diff --git a/contrib/src/msgpack/rules.mak b/contrib/src/msgpack/rules.mak
index 18ec422bbe36e1be9990b125fd4414393ea84aca..134bb08b0a9c787080200e53e6a77972f59e7238 100644
--- a/contrib/src/msgpack/rules.mak
+++ b/contrib/src/msgpack/rules.mak
@@ -1,5 +1,5 @@
 # MSGPACK
-MSGPACK_VERSION := 068041f05eb1b8ab2930a7679dfe89ba7d14cb79
+MSGPACK_VERSION := 1df97bc37b363a340c5ad06c5cbcc53310aaff80
 MSGPACK_URL := https://github.com/msgpack/msgpack-c/archive/$(MSGPACK_VERSION).tar.gz
 
 PKGS += msgpack
diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak
index 5003d7a0ba1d40c29c9bead489d2e080808c0be8..d67427311a33b23293bfd673f3969a12ee158c43 100644
--- a/contrib/src/opendht/rules.mak
+++ b/contrib/src/opendht/rules.mak
@@ -1,5 +1,5 @@
 # OPENDHT
-OPENDHT_VERSION := 0.6.3
+OPENDHT_VERSION := aaed64039a40e7d1bc7a8907c53284506484aefb
 OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz
 
 PKGS += opendht
diff --git a/src/Makefile.am b/src/Makefile.am
index 87a4716060e2d6e08b6bb8badea9a59124e5b400..6db024c779d56c628070764ac0137661b3b5f36a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -130,7 +130,9 @@ libring_la_SOURCES = \
 		ring_api.cpp \
 		rational.h \
 		smartools.cpp \
-		smartools.h
+		smartools.h \
+        base64.h \
+        base64.cpp
 
 if HAVE_WIN32
 libring_la_SOURCES += \
diff --git a/src/account.cpp b/src/account.cpp
index 556b9ba50c0a5a94b434cd85b2d959944d71db2f..794a88e7774aee4b769c8a4e37a783dc52083997 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -213,7 +213,6 @@ Account::serialize(YAML::Emitter& out)
     out << YAML::Key << RINGTONE_PATH_KEY << YAML::Value << ringtonePath_;
     out << YAML::Key << HAS_CUSTOM_USER_AGENT_KEY << YAML::Value << hasCustomUserAgent_;
     out << YAML::Key << USER_AGENT_KEY << YAML::Value << userAgent_;
-    out << YAML::Key << USERNAME_KEY << YAML::Value << username_;
     out << YAML::Key << DISPLAY_NAME_KEY << YAML::Value << displayName_;
     out << YAML::Key << HOSTNAME_KEY << YAML::Value << hostname_;
     out << YAML::Key << UPNP_ENABLED_KEY << YAML::Value << upnpEnabled_;
@@ -226,7 +225,6 @@ Account::unserialize(const YAML::Node& node)
 
     parseValue(node, ALIAS_KEY, alias_);
     parseValue(node, ACCOUNT_ENABLE_KEY, enabled_);
-    parseValue(node, USERNAME_KEY, username_);
     parseValue(node, ACCOUNT_AUTOANSWER_KEY, autoAnswerEnabled_);
     parseValue(node, ACCOUNT_ACTIVE_CALL_LIMIT_KEY, activeCallLimit_);
     //parseValue(node, PASSWORD_KEY, password_);
@@ -257,7 +255,6 @@ Account::setAccountDetails(const std::map<std::string, std::string> &details)
     parseString(details, Conf::CONFIG_ACCOUNT_ALIAS, alias_);
     parseString(details, Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName_);
     parseBool(details, Conf::CONFIG_ACCOUNT_ENABLE, enabled_);
-    parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_);
     parseString(details, Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_);
     parseString(details, Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_);
     parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, userAgent_);
@@ -366,6 +363,8 @@ Account::mapStateNumberToString(RegistrationState state)
         CASE_STATE(ERROR_SERVICE_UNAVAILABLE);
         CASE_STATE(ERROR_EXIST_STUN);
         CASE_STATE(ERROR_NOT_ACCEPTABLE);
+        CASE_STATE(ERROR_NEED_MIGRATION);
+        CASE_STATE(INITIALIZING);
         default:
             return DRing::Account::States::ERROR_GENERIC;
     }
diff --git a/src/account.h b/src/account.h
index 1b56003a90932a434979d1c93d27982a1de3de5a..b925752828d21dd22e63a576ee521b66fb6edee1 100644
--- a/src/account.h
+++ b/src/account.h
@@ -134,6 +134,8 @@ class Account : public Serializable, public std::enable_shared_from_this<Account
          */
         virtual void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()) = 0;
 
+        RegistrationState getRegistrationState() const { return registrationState_; }
+
         /**
          * Create a new outgoing call.
          *
diff --git a/src/archiver.cpp b/src/archiver.cpp
index 8f520728eeff551e666b3ba416ca2687c5873e51..ac00041386f87a2360b19e0420e299e1b5b87f98 100644
--- a/src/archiver.cpp
+++ b/src/archiver.cpp
@@ -20,8 +20,6 @@
 
 #include "archiver.h"
 
-#include <json/json.h>
-
 #include "client/ring_signal.h"
 #include "account_const.h"
 #include "configurationmanager_interface.h"
@@ -31,27 +29,67 @@
 #include "logger.h"
 
 #include <opendht/crypto.h>
+#include <json/json.h>
+#include <zlib.h>
 
-#include <fstream>
 #include <sys/stat.h>
+#include <fstream>
 
 namespace ring {
+namespace archiver {
 
-Archiver&
-Archiver::instance()
-{
-    // Meyers singleton
-    static Archiver instance_;
-    return instance_;
+std::map<std::string, std::string>
+jsonValueToAccount(Json::Value& value, const std::string& accountId) {
+    auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
+    fileutils::check_dir(idPath_.c_str(), 0700);
+    auto detailsMap = DRing::getAccountTemplate(value[DRing::Account::ConfProperties::TYPE].asString());
+
+    for( Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++ ) {
+        if (itr->asString().empty())
+            continue;
+        if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) {
+            std::string fileContent(itr->asString());
+            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", {fileContent.begin(), fileContent.end()}, 0600);
+
+        } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
+            std::string fileContent(itr->asString());
+            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key", {fileContent.begin(), fileContent.end()}, 0600);
+
+        } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) {
+            std::string fileContent(itr->asString());
+            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt", {fileContent.begin(), fileContent.end()}, 0600);
+        } else
+            detailsMap[itr.key().asString()] = itr->asString();
+    }
+
+    return detailsMap;
 }
 
-Archiver::Archiver()
-{
+Json::Value
+accountToJsonValue(std::map<std::string, std::string> details) {
+    Json::Value root;
+    std::map<std::string, std::string>::iterator iter;
+    for (iter = details.begin(); iter != details.end(); ++iter) {
 
+        if (iter->first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) {
+            // Ringtone path is not exportable
+        } else if (iter->first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 ||
+                iter->first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 ||
+                iter->first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
+            // replace paths by the files content
+            std::ifstream ifs(iter->second);
+            std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
+            root[iter->first] = fileContent;
+
+        } else
+            root[iter->first] = iter->second;
+    }
+
+    return root;
 }
 
 int
-Archiver::exportAccounts(std::vector<std::string> accountIDs,
+exportAccounts(std::vector<std::string> accountIDs,
                         std::string filepath,
                         std::string password)
 {
@@ -109,31 +147,8 @@ Archiver::exportAccounts(std::vector<std::string> accountIDs,
     return 0;
 }
 
-Json::Value
-Archiver::accountToJsonValue(std::map<std::string, std::string> details) {
-    Json::Value root;
-    std::map<std::string, std::string>::iterator iter;
-    for (iter = details.begin(); iter != details.end(); ++iter) {
-
-        if (iter->first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) {
-            // Ringtone path is not exportable
-        } else if (iter->first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 ||
-                iter->first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 ||
-                iter->first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
-            // replace paths by the files content
-            std::ifstream ifs(iter->second);
-            std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
-            root[iter->first] = fileContent;
-
-        } else
-            root[iter->first] = iter->second;
-    }
-
-    return root;
-}
-
 int
-Archiver::importAccounts(std::string archivePath, std::string password)
+importAccounts(std::string archivePath, std::string password)
 {
     if (archivePath.empty()) {
         RING_ERR("Missing arguments");
@@ -191,35 +206,8 @@ Archiver::importAccounts(std::string archivePath, std::string password)
     return 0;
 }
 
-std::map<std::string, std::string>
-Archiver::jsonValueToAccount(Json::Value& value, const std::string& accountId) {
-    auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
-    fileutils::check_dir(idPath_.c_str(), 0700);
-    auto detailsMap = DRing::getAccountTemplate(value[DRing::Account::ConfProperties::TYPE].asString());
-
-    for( Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++ ) {
-        if (itr->asString().empty())
-            continue;
-        if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) {
-            std::string fileContent(itr->asString());
-            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", {fileContent.begin(), fileContent.end()}, 0600);
-
-        } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
-            std::string fileContent(itr->asString());
-            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key", {fileContent.begin(), fileContent.end()}, 0600);
-
-        } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) {
-            std::string fileContent(itr->asString());
-            fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt", {fileContent.begin(), fileContent.end()}, 0600);
-        } else
-            detailsMap[itr.key().asString()] = itr->asString();
-    }
-
-    return detailsMap;
-}
-
 std::vector<uint8_t>
-Archiver::compress(const std::string& str, int compressionlevel)
+compress(const std::string& str)
 {
     auto destSize = compressBound(str.size());
     std::vector<uint8_t> outbuffer(destSize);
@@ -229,20 +217,20 @@ Archiver::compress(const std::string& str, int compressionlevel)
     if (ret != Z_OK) {
         std::ostringstream oss;
         oss << "Exception during zlib compression: (" << ret << ") ";
-        throw(std::runtime_error(oss.str()));
+        throw std::runtime_error(oss.str());
     }
 
     return outbuffer;
 }
 
 std::vector<uint8_t>
-Archiver::decompress(const std::vector<uint8_t>& str)
+decompress(const std::vector<uint8_t>& str)
 {
     z_stream zs; // z_stream is zlib's control structure
     memset(&zs, 0, sizeof(zs));
 
     if (inflateInit(&zs) != Z_OK)
-        throw(std::runtime_error("inflateInit failed while decompressing."));
+        throw std::runtime_error("inflateInit failed while decompressing.");
 
     zs.next_in = (Bytef*)str.data();
     zs.avail_in = str.size();
@@ -278,4 +266,4 @@ Archiver::decompress(const std::vector<uint8_t>& str)
     return out;
 }
 
-} // namespace ring
+}} // namespace ring::archiver
diff --git a/src/archiver.h b/src/archiver.h
index 6513e37cc5833614db402456bb7e1c09172054b8..3ef500f5608d32856786c5a86f7dd232f4ee0720 100644
--- a/src/archiver.h
+++ b/src/archiver.h
@@ -25,59 +25,45 @@
 #include <string>
 #include <vector>
 #include <map>
-#include <zlib.h>
-
-namespace Json {
-class Value;
-};
 
 namespace ring {
 
 /**
  * Archiver is used to generate/read encrypted archives
  */
-class Archiver {
-public:
-    static Archiver& instance();
-
-    Archiver();
-
-    /**
-     * Create a protected archive containing a list of accounts
-     * @param accountIDs The accounts to exports
-     * @param filepath The filepath where to put the resulting archive
-     * @param password The mandatory password to set on the archive
-     * @returns 0 for OK, error code otherwise
-     */
-    int exportAccounts(std::vector<std::string> accountIDs,
-                        std::string filepath,
-                        std::string password);
+namespace archiver {
 
-    /**
-     * Read a protected archive and add accounts found in it
-     * Warning: this function must be called from a registered pjsip thread
-     * @param archivePath The path to the archive file
-     * @param password The password to read the archive
-     * @returns 0 for OK, error code otherwise
-     */
-    int importAccounts(std::string archivePath, std::string password);
+/**
+ * Create a protected archive containing a list of accounts
+ * @param accountIDs The accounts to exports
+ * @param filepath The filepath where to put the resulting archive
+ * @param password The mandatory password to set on the archive
+ * @returns 0 for OK, error code otherwise
+ */
+int exportAccounts(std::vector<std::string> accountIDs,
+                    std::string filepath,
+                    std::string password);
 
-    /**
-     * Compress a STL string using zlib with given compression level and return
-     * the binary data.
-     */
-    std::vector<uint8_t> compress(const std::string& str, int compressionlevel = Z_BEST_COMPRESSION);
+/**
+ * Read a protected archive and add accounts found in it
+ * Warning: this function must be called from a registered pjsip thread
+ * @param archivePath The path to the archive file
+ * @param password The password to read the archive
+ * @returns 0 for OK, error code otherwise
+ */
+int importAccounts(std::string archivePath, std::string password);
 
-    /**
-     * Decompress an STL string using zlib and return the original data.
-     */
-    std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat);
+/**
+ * Compress a STL string using zlib with given compression level and return
+ * the binary data.
+ */
+std::vector<uint8_t> compress(const std::string& str);
 
-private:
-    NON_COPYABLE(Archiver);
+/**
+ * Decompress an STL string using zlib and return the original data.
+ */
+std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat);
 
-    static Json::Value accountToJsonValue(std::map<std::string, std::string> details);
-    static std::map<std::string, std::string> jsonValueToAccount(Json::Value& value, const std::string& accountId);
-};
+}
 
 } // namespace ring
diff --git a/src/sip/base64.c b/src/base64.cpp
similarity index 81%
rename from src/sip/base64.c
rename to src/base64.cpp
index 707352ad1d789f79b41d7d542db622dec299ba16..d14812afd3f41e64717eb2a443c680e4ce8b4b43 100644
--- a/src/sip/base64.c
+++ b/src/base64.cpp
@@ -16,6 +16,8 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
+#include "base64.h"
+
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -109,3 +111,38 @@ uint8_t *ring_base64_decode(const char *input, size_t input_length,
 
     return output;
 }
+
+namespace ring {
+namespace base64 {
+
+std::string
+encode(const std::vector<uint8_t>::const_iterator begin,
+       const std::vector<uint8_t>::const_iterator end)
+{
+    size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3);
+    std::string out;
+    out.resize(output_length);
+    ring_base64_encode(&(*begin), std::distance(begin, end),
+                       &(*out.begin()), &output_length);
+    out.resize(output_length);
+    return out;
+}
+
+std::string
+encode(const std::vector<uint8_t>& dat)
+{
+    return encode(dat.cbegin(), dat.cend());
+}
+
+std::vector<uint8_t>
+decode(const std::string& str)
+{
+    size_t output_length = str.length() / 4 * 3 + 2;
+    std::vector<uint8_t> output;
+    output.resize(output_length);
+    ring_base64_decode(str.data(), str.size(), output.data(), &output_length);
+    output.resize(output_length);
+    return output;
+}
+
+}} // namespace ring::base64
diff --git a/src/sip/base64.h b/src/base64.h
similarity index 63%
rename from src/sip/base64.h
rename to src/base64.h
index 2e2069114e509621a3214e160c72887ed2d5c492..80b53c3521cc34f8cb96111a2544646046ebf3c7 100644
--- a/src/sip/base64.h
+++ b/src/base64.h
@@ -16,14 +16,10 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#ifndef H_BASE64
-#define H_BASE64
+#pragma once
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "stdint.h"
+#include <stdint.h>
+#include <stddef.h>
 
 /**
  * Encode a buffer in base64.
@@ -51,46 +47,14 @@ char *ring_base64_encode(const uint8_t *input, size_t input_length,
 uint8_t *ring_base64_decode(const char *input, size_t input_length,
                             uint8_t *output, size_t *output_length);
 
-#ifdef __cplusplus
-}
-#endif
-
 #include <string>
 #include <vector>
 
 namespace ring {
 namespace base64 {
 
-std::string
-encode(const std::vector<uint8_t>::const_iterator begin,
-       const std::vector<uint8_t>::const_iterator end)
-{
-    size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3);
-    std::string out;
-    out.resize(output_length);
-    ring_base64_encode(&(*begin), std::distance(begin, end),
-                       &(*out.begin()), &output_length);
-    out.resize(output_length);
-    return out;
-}
-
-std::string
-encode(const std::vector<uint8_t>& dat)
-{
-    return encode(dat.cbegin(), dat.cend());
-}
-
-std::vector<uint8_t>
-decode(const std::string& str)
-{
-    size_t output_length = str.length() / 4 * 3 + 2;
-    std::vector<uint8_t> output;
-    output.resize(output_length);
-    ring_base64_decode(str.data(), str.size(), output.data(), &output_length);
-    output.resize(output_length);
-    return output;
-}
+std::string encode(const std::vector<uint8_t>::const_iterator begin, const std::vector<uint8_t>::const_iterator end);
+std::string encode(const std::vector<uint8_t>& dat);
+std::vector<uint8_t> decode(const std::string& str);
 
 }} // namespace ring::base64
-
-#endif // H_BASE64
diff --git a/src/call.cpp b/src/call.cpp
index a17fc9fb1cc834e1c9f4395a32826f602a86898a..f028a31fc75c94ed2cd21ff1a12e87ab900a87f8 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -158,10 +158,24 @@ Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
     connectionState_ = cnx_state;
     auto new_client_state = getStateStr();
 
+    if (call_state == CallState::OVER) {
+        RING_DBG("[call:%s] %lu subcalls", id_.c_str(), subcalls.size());
+        if (not subcalls.empty()) {
+            auto subs = std::move(subcalls);
+            for (auto c : subs)
+                c->hangup(0);
+        }
+    }
+
+    for (auto& l : stateChangedListeners_)
+        l(callState_, connectionState_, code);
+
     if (old_client_state != new_client_state) {
-        RING_DBG("[call:%s] emit client call state change %s, code %d",
-                 id_.c_str(), new_client_state.c_str(), code);
-        emitSignal<DRing::CallSignal::StateChange>(id_, new_client_state, code);
+        if (not quiet) {
+            RING_DBG("[call:%s] emit client call state change %s, code %d",
+                     id_.c_str(), new_client_state.c_str(), code);
+            emitSignal<DRing::CallSignal::StateChange>(id_, new_client_state, code);
+        }
     }
 
     return true;
@@ -354,4 +368,87 @@ Call::peerHungup()
              aborted ? ECONNABORTED : ECONNREFUSED);
 }
 
+void
+Call::addSubCall(const std::shared_ptr<Call>& call)
+{
+    if (connectionState_ == ConnectionState::CONNECTED
+           || callState_ == CallState::ACTIVE
+           || callState_ == CallState::OVER) {
+        call->removeCall();
+    } else {
+        std::lock_guard<std::recursive_mutex> lk (callMutex_);
+        if (not subcalls.emplace(call).second)
+            return;
+        call->quiet = true;
+        std::weak_ptr<Call> wthis = shared_from_this();
+        std::weak_ptr<Call> wcall = call;
+        call->addStateListener([wcall,wthis](Call::CallState new_state, Call::ConnectionState new_cstate, int code) {
+            if (auto call = wcall.lock()) {
+                if (auto sthis = wthis.lock()) {
+                    auto& this_ = *sthis;
+                    auto sit = this_.subcalls.find(call);
+                    if (sit == this_.subcalls.end())
+                        return;
+                    RING_WARN("[call %s] DeviceCall call %s state changed %d %d", this_.getCallId().c_str(), call->getCallId().c_str(), new_state, new_cstate);
+                    if (new_state == CallState::OVER) {
+                        std::lock_guard<std::recursive_mutex> lk (this_.callMutex_);
+                        this_.subcalls.erase(call);
+                    } else if (new_state == CallState::ACTIVE && this_.callState_ == CallState::INACTIVE) {
+                        this_.setState(new_state);
+                    }
+                    if ((unsigned)this_.connectionState_ < (unsigned)new_cstate && (unsigned)new_cstate < (unsigned)ConnectionState::RINGING) {
+                        this_.setState(new_cstate);
+                    } else if (new_cstate == ConnectionState::DISCONNECTED && new_state == CallState::ACTIVE) {
+                        std::lock_guard<std::recursive_mutex> lk (this_.callMutex_);
+                        RING_WARN("[call %s] peer hangup", this_.getCallId().c_str());
+                        auto subcalls = std::move(this_.subcalls);
+                        for (auto& sub : subcalls) {
+                            if (sub != call)
+                                try {
+                                    sub->hangup(0);
+                                } catch(const std::exception& e) {
+                                    RING_WARN("[call %s] error hanging up: %s", this_.getCallId().c_str());
+                                }
+                        }
+                        this_.peerHungup();
+                    }
+                    if (new_state == CallState::ACTIVE && new_cstate == ConnectionState::CONNECTED) {
+                        std::lock_guard<std::recursive_mutex> lk (this_.callMutex_);
+                        RING_WARN("[call %s] peer answer", this_.getCallId().c_str());
+                        auto subcalls = std::move(this_.subcalls);
+                        for (auto& sub : subcalls) {
+                            if (sub != call)
+                                sub->hangup(0);
+                        }
+                        this_.merge(call);
+                    }
+                    RING_WARN("[call %s] Remaining %d subcalls", this_.getCallId().c_str(), this_.subcalls.size());
+                } else {
+                    RING_WARN("DeviceCall IGNORED call %s state changed %d %d", call->getCallId().c_str(), new_state, new_cstate);
+                }
+            }
+        });
+        setState(ConnectionState::TRYING);
+    }
+}
+
+void
+Call::merge(std::shared_ptr<Call> scall)
+{
+    RING_WARN("[call %s] merge to -> [call %s]", scall->getCallId().c_str(), getCallId().c_str());
+    auto& call = *scall;
+    std::lock(callMutex_, call.callMutex_);
+    std::lock_guard<std::recursive_mutex> lk1 (callMutex_, std::adopt_lock);
+    std::lock_guard<std::recursive_mutex> lk2 (call.callMutex_, std::adopt_lock);
+    iceTransport_ = std::move(call.iceTransport_);
+    peerDisplayName_ = std::move(call.peerDisplayName_);
+    localAddr_ = call.localAddr_;
+    localAudioPort_ = call.localAudioPort_;
+    localVideoPort_ = call.localVideoPort_;
+    setState(call.getState());
+    setState(call.getConnectionState());
+    scall->removeCall();
+}
+
+
 } // namespace ring
diff --git a/src/call.h b/src/call.h
index 5eb618fcf38a84b0da3b204875d1dc75025ef04d..f8f428ff864a550b14744da56695d76be8fbc377 100644
--- a/src/call.h
+++ b/src/call.h
@@ -21,8 +21,7 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#ifndef __CALL_H__
-#define __CALL_H__
+#pragma once
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -40,6 +39,7 @@
 #include <memory>
 #include <vector>
 #include <condition_variable>
+#include <set>
 
 namespace ring {
 
@@ -330,9 +330,18 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> {
         }
 
         virtual void restartMediaSender() = 0;
-
         virtual void restartMediaReceiver() = 0;
 
+        using StateListener = std::function<void(CallState, int)>;
+
+        template<class T>
+        void addStateListener(T&& list) {
+            stateChangedListeners_.emplace_back(std::forward<T>(list));
+        }
+        void addSubCall(const std::shared_ptr<Call>& call);
+
+        virtual void merge(std::shared_ptr<Call> scall);
+
     protected:
         /**
          * Constructor of a call
@@ -345,13 +354,18 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> {
 
         bool isAudioMuted_{false};
         bool isVideoMuted_{false};
+        bool quiet {false};
+        std::set<std::shared_ptr<Call>> subcalls {};
 
     private:
+
         bool validStateTransition(CallState newState);
 
         /** Protect every attribute that can be changed by two threads */
         mutable std::recursive_mutex callMutex_ {};
 
+        std::vector<std::function<void(CallState, ConnectionState, int)>> stateChangedListeners_ {};
+
         // Informations about call socket / audio
 
         /** My IP address */
@@ -394,5 +408,3 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> {
 };
 
 } // namespace ring
-
-#endif // __CALL_H__
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 44d4a2bd771883866aae1ae4b17f84d5d97727e9..4a9adc1359a9e5c37f4cf817103f56a0739d7c8f 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -276,6 +276,16 @@ getMessageStatus(uint64_t id)
     return ring::Manager::instance().getMessageStatus(id);
 }
 
+bool
+exportOnRing(const std::string& accountID, const std::string& password)
+{
+    if (const auto account = ring::Manager::instance().getAccount<ring::RingAccount>(accountID)) {
+        account->addDevice(password);
+        return true;
+    }
+    return false;
+}
+
 /* contact requests */
 std::map<std::string, std::string>
 getTrustRequests(const std::string& accountId)
@@ -314,13 +324,13 @@ sendTrustRequest(const std::string& accountId, const std::string& to, const std:
 int
 exportAccounts(std::vector<std::string> accountIDs, std::string filepath, std::string password)
 {
-    return ring::Archiver::instance().exportAccounts(accountIDs, filepath, password);
+    return ring::archiver::exportAccounts(accountIDs, filepath, password);
 }
 
 int
 importAccounts(std::string archivePath, std::string password)
 {
-    return ring::Archiver::instance().importAccounts(archivePath, password);
+    return ring::archiver::importAccounts(archivePath, password);
 }
 
 ///This function is used as a base for new accounts for clients that support it
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 0d15295b38ad5b043014d21c09e93cfeb6b83be8..57833b76259030cca1c6762b6a3cc90fe0db8db2 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -63,6 +63,7 @@ getSignalHandlers()
         exported_callback<DRing::ConfigurationSignal::IncomingAccountMessage>(),
         exported_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>(),
         exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(),
+        exported_callback<DRing::ConfigurationSignal::ExportOnRingEnded>(),
         exported_callback<DRing::ConfigurationSignal::MediaParametersChanged>(),
         exported_callback<DRing::ConfigurationSignal::Error>(),
 #ifdef __ANDROID__
diff --git a/src/dring/account_const.h b/src/dring/account_const.h
index b169f4e7e58ac921aa9a7e343717bff808a29047..5ac7dfd56dd781a6913eee88667b491cb38fa8fd 100644
--- a/src/dring/account_const.h
+++ b/src/dring/account_const.h
@@ -53,7 +53,10 @@ constexpr static const char ERROR_CONF_STUN           [] = "ERROR_CONF_STUN";
 constexpr static const char ERROR_EXIST_STUN          [] = "ERROR_EXIST_STUN";
 constexpr static const char ERROR_SERVICE_UNAVAILABLE [] = "ERROR_SERVICE_UNAVAILABLE";
 constexpr static const char ERROR_NOT_ACCEPTABLE      [] = "ERROR_NOT_ACCEPTABLE";
+constexpr static const char ERROR_MISSING_ID          [] = "ERROR_MISSING_ID";
+constexpr static const char ERROR_NEED_MIGRATION      [] = "ERROR_NEED_MIGRATION";
 constexpr static const char REQUEST_TIMEOUT           [] = "Request Timeout";
+constexpr static const char INITIALIZING              [] = "INITIALIZING";
 
 } //namespace DRing::Account
 
@@ -125,7 +128,10 @@ constexpr static const char HAS_CUSTOM_USER_AGENT   [] = "Account.hasCustomUserA
 constexpr static const char ALLOW_CERT_FROM_HISTORY [] = "Account.allowCertFromHistory";
 constexpr static const char ALLOW_CERT_FROM_CONTACT [] = "Account.allowCertFromContact";
 constexpr static const char ALLOW_CERT_FROM_TRUSTED [] = "Account.allowCertFromTrusted";
-
+constexpr static const char ARCHIVE_PASSWORD        [] = "Account.archivePassword";
+constexpr static const char ARCHIVE_PATH            [] = "Account.archivePath";
+constexpr static const char ARCHIVE_PIN             [] = "Account.archivePIN";
+constexpr static const char RING_DEVICE_ID          [] = "Account.deviceID";
 
 namespace Audio {
 
@@ -216,6 +222,13 @@ constexpr static const char ALLOW_FROM_TRUSTED [] = "DHT.AllowFromTrusted";
 
 } //namespace DRing::Account::DHT
 
+namespace ETH {
+
+constexpr static const char KEY_FILE           [] = "ETH.keyFile";
+constexpr static const char ACCOUNT            [] = "ETH.account";
+
+} //namespace DRing::Account::ETH
+
 namespace CodecInfo {
 
 constexpr static const char NAME               [] = "CodecInfo.name";
diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h
index 46efa792d04a6d0305806070de86d13911cd5da5..c2c67225188060a590bd1ceae19bb0c0ee61f0a5 100644
--- a/src/dring/configurationmanager_interface.h
+++ b/src/dring/configurationmanager_interface.h
@@ -6,6 +6,7 @@
  *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
  *  Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com>
  *  Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@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
@@ -22,8 +23,7 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-#ifndef DRING_CONFIGURATIONMANAGERI_H
-#define DRING_CONFIGURATIONMANAGERI_H
+#pragma once
 
 #include <vector>
 #include <map>
@@ -45,6 +45,8 @@ std::map<std::string, std::string> testAccountICEInitialization(const std::strin
 void setAccountActive(const std::string& accountID, bool active);
 std::map<std::string, std::string> getAccountTemplate(const std::string& accountType);
 std::string addAccount(const std::map<std::string, std::string>& details);
+bool exportOnRing(const std::string& accountID, const std::string& password);
+
 void removeAccount(const std::string& accountID);
 void setAccountEnabled(const std::string& accountID, bool enable);
 std::vector<std::string> getAccountList();
@@ -53,6 +55,7 @@ void registerAllAccounts(void);
 uint64_t sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::map<std::string, std::string>& payloads);
 int getMessageStatus(uint64_t id);
 
+
 std::map<std::string, std::string> getTlsDefaultSettings();
 
 std::vector<unsigned> getCodecList();
@@ -208,6 +211,10 @@ struct ConfigurationSignal {
                 constexpr static const char* name = "IncomingTrustRequest";
                 using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& payload, time_t received);
         };
+        struct ExportOnRingEnded {
+                constexpr static const char* name = "ExportOnRingEnded";
+                using cb_type = void(const std::string& /*account_id*/, int state, const std::string& pin);
+        };
         struct CertificatePinned {
                 constexpr static const char* name = "CertificatePinned";
                 using cb_type = void(const std::string& /*certId*/);
@@ -245,5 +252,3 @@ struct ConfigurationSignal {
 };
 
 } // namespace DRing
-
-#endif // DRING_CONFIGURATIONMANAGERI_H
diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index 707df11a545d949bab671b1974fd805812e9247a..c2c5788a223e5a2f03bd1c0ec8bd04e00d173f47 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -209,6 +209,11 @@ expand_path(const std::string &path)
 #endif
 }
 
+bool isFile (const std::string& path) {
+  struct stat s;
+  return (stat (path.c_str(), &s) == 0) and not (s.st_mode & S_IFDIR);
+}
+
 bool isDirectory(const std::string& path)
 {
     struct stat s;
diff --git a/src/fileutils.h b/src/fileutils.h
index 4bc3e52f56aa85c0eea86c543105c216a81815ed..1992a125afbb939d01a9368b39fac042be531f13 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -66,6 +66,7 @@ namespace ring { namespace fileutils {
 
     bool recursive_mkdir(const std::string& path, mode_t mode=0755);
 
+    bool isFile(const std::string& path);
     bool isDirectory(const std::string& path);
 
     bool isSymLink(const std::string& path);
diff --git a/src/manager.cpp b/src/manager.cpp
index e3d0da2d10aa3bbe1b70753188ed1f0a5a005850..c70f87e504dbefca5381e92d087043f5d8f963bc 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -342,6 +342,11 @@ Manager::finish() noexcept
             hangupCall(call->getCallId());
         callFactory.clear();
 
+        for (const auto &account : getAllAccounts<RingAccount>()) {
+            if (account->getRegistrationState() == RegistrationState::INITIALIZING)
+                removeAccount(account->getAccountID());
+        }
+
         saveConfig();
 
         // Disconnect accounts, close link stacks and free allocated ressources
diff --git a/src/registration_states.h b/src/registration_states.h
index 1f633a7513c67a8aa8a78dbcc0e9b045a63add90..5b7bb423c2aa9de25a463335696234799632e340 100644
--- a/src/registration_states.h
+++ b/src/registration_states.h
@@ -39,7 +39,9 @@ enum class RegistrationState {
     ERROR_HOST,
     ERROR_SERVICE_UNAVAILABLE,
     ERROR_EXIST_STUN,
-    ERROR_NOT_ACCEPTABLE
+    ERROR_NOT_ACCEPTABLE,
+    ERROR_NEED_MIGRATION,
+    INITIALIZING
 };
 
 } // namespace ring
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index c83d75eb54f97ebf282737c25be5f29a794a73f1..5f99d2dfcde96825e1e2a03775b020063dd00e94 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -54,12 +54,17 @@
 #include "fileutils.h"
 #include "string_utils.h"
 #include "array_size.h"
+#include "archiver.h"
 
 #include "config/yamlparser.h"
-
 #include "security/certstore.h"
+#include "libdevcrypto/Common.h"
+#include "base64.h"
 
 #include <yaml-cpp/yaml.h>
+#include <json/json.h>
+
+#include <unistd.h>
 
 #include <algorithm>
 #include <array>
@@ -79,10 +84,11 @@ static constexpr int ICE_COMP_SIP_TRANSPORT {0};
 static constexpr int ICE_INIT_TIMEOUT {10};
 static constexpr auto ICE_NEGOTIATION_TIMEOUT = std::chrono::seconds(60);
 static constexpr auto TLS_TIMEOUT = std::chrono::seconds(30);
+const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
 
 static constexpr const char * const RING_URI_PREFIX = "ring:";
 
-constexpr const char * const RingAccount::ACCOUNT_TYPE;
+constexpr const char* const RingAccount::ACCOUNT_TYPE;
 /* constexpr */ const std::pair<uint16_t, uint16_t> RingAccount::DHT_PORT_RANGE {4000, 8888};
 
 static std::uniform_int_distribution<dht::Value::Id> udist;
@@ -109,6 +115,14 @@ parseRingUri(const std::string& toUrl)
     return toUri;
 }
 
+
+static constexpr const char*
+dhtStatusStr(dht::NodeStatus status) {
+    return status == dht::NodeStatus::Connected  ? "connected"  : (
+           status == dht::NodeStatus::Connecting ? "connecting" :
+                                                   "disconnected");
+}
+
 /**
  * Local ICE Transport factory helper
  *
@@ -139,12 +153,11 @@ RingAccount::createIceTransport(const Args&... args)
 }
 
 RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */)
-    : SIPAccountBase(accountID), via_addr_()
-{
-    cachePath_ = fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID();
-    dataPath_ = cachePath_ + DIR_SEPARATOR_STR "values";
-    idPath_ = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID();
-}
+    : SIPAccountBase(accountID), via_addr_(),
+    cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID()),
+    dataPath_(cachePath_ + DIR_SEPARATOR_STR "values"),
+    idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID())
+{}
 
 RingAccount::~RingAccount()
 {
@@ -173,9 +186,11 @@ RingAccount::newIncomingCall(const std::string& from)
         if (not call) {
             RING_WARN("newIncomingCall: discarding deleted call");
             call_it = pendingSipCalls_.erase(call_it);
-        } else if (call->getPeerNumber() == from) {
-            pendingSipCalls_.erase(call_it);
+        } else if (call->getPeerNumber() == from || (call_it->from_cert and
+                                                     call_it->from_cert->issuer and
+                                                     call_it->from_cert->issuer->getId().toString() == from)) {
             RING_DBG("newIncomingCall: found matching call for %s", from.c_str());
+            pendingSipCalls_.erase(call_it);
             return call;
         } else {
             ++call_it;
@@ -199,87 +214,121 @@ RingAccount::newOutgoingCall(const std::string& toUrl)
     call->setIPToIP(true);
     call->setSecure(isTlsEnabled());
 
+    auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this());
+
     // TODO: for now, we automatically trust all explicitly called peers
     setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
 
-    auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this());
+    const auto toH = dht::InfoHash(toUri);
+
+    call->setState(Call::ConnectionState::TRYING);
     std::weak_ptr<SIPCall> weak_call = call;
-    manager.addTask([shared_this, weak_call, toUri] {
-        auto call = weak_call.lock();
-        if (not call)
-            return false;
 
-        // Create an ICE transport for SIP channel
-        std::shared_ptr<IceTransport> ice {};
+    auto treatedDevices_ = std::make_shared<std::set<dht::InfoHash>>();
 
-        try {
-            ice = shared_this->createIceTransport(("sip:" + call->getCallId()).c_str(),
-                                                  ICE_COMPONENTS, true, shared_this->getIceOptions());
-        } catch (std::runtime_error& e) {
-            RING_ERR("%s", e.what());
-            call->onFailure();
-            return false;
-        }
+    // Find listening Ring devices for this account
+    shared_this->dht_.get<DeviceAnnouncement>(toH, [=](DeviceAnnouncement&& dev) {
+        if (dev.from != toH)
+            return true;
+        if (not treatedDevices_->emplace(dev.dev).second)
+            return true;
+        RING_WARN("Found device to place call %s", dev.dev.toString().c_str());
+
+        runOnMainThread([=](){
+            if (auto call = weak_call.lock()) {
+                RING_WARN("[call %s] Found device %s", call->getCallId().c_str(), dev.dev.toString().c_str());
+
+                auto& manager = Manager::instance();
+                auto dev_call = manager.callFactory.newCall<SIPCall, RingAccount>(*this, manager.getNewCallID(),
+                                                                              Call::CallType::OUTGOING);
+                std::weak_ptr<SIPCall> weak_dev_call = dev_call;
+                dev_call->setIPToIP(true);
+                dev_call->setSecure(isTlsEnabled());
+                auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(),
+                                              ICE_COMPONENTS, true, getIceOptions());
+                if (not ice) {
+                    RING_WARN("Can't create ICE");
+                    dev_call->removeCall();
+                    return;
+                }
 
-        auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT};
+                call->addSubCall(dev_call);
 
-        /* First step: wait for an initialized ICE transport for SIP channel */
-        if (ice->isFailed() or std::chrono::steady_clock::now() >= iceInitTimeout) {
-            RING_DBG("ice init failed (or timeout)");
-            call->onFailure();
-            return false;
-        }
+                auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT};
+
+                manager.addTask([shared_this, weak_dev_call, ice, iceInitTimeout, toUri, dev] {
+                    auto call = weak_dev_call.lock();
+
+                    if (not call)
+                        return false;
 
-        if (not ice->isInitialized())
-            return true; // process task again!
-
-        /* Next step: sent the ICE data to peer through DHT */
-        const dht::Value::Id callvid  = udist(shared_this->rand_);
-        const dht::Value::Id vid  = udist(shared_this->rand_);
-        const auto toH = dht::InfoHash(toUri);
-        const auto callkey = dht::InfoHash::get("callto:" + toUri);
-        dht::Value val { dht::IceCandidates(callvid, ice->getLocalAttributesAndCandidates()) };
-        val.id = vid;
-
-        call->setState(Call::ConnectionState::TRYING);
-        shared_this->dht_.putEncrypted(
-            callkey, toH,
-            std::move(val),
-            [=](bool ok) { // Put complete callback
-                if (!ok) {
-                    RING_WARN("Can't put ICE descriptor on DHT");
-                    if (auto call = weak_call.lock())
+                    if (ice->isFailed() or std::chrono::steady_clock::now() >= iceInitTimeout) {
+                        RING_DBG("ice init failed (or timeout)");
                         call->onFailure();
-                } else
-                    RING_DBG("Successfully put ICE descriptor on DHT");
-            }
-        );
+                        return false;
+                    }
 
-        auto listenKey = shared_this->dht_.listen<dht::IceCandidates>(
-            callkey,
-            [=] (dht::IceCandidates&& msg) {
-                if (msg.id != callvid or msg.from != toH)
-                    return true;
-                RING_WARN("ICE request replied from DHT peer %s\n%s", toH.toString().c_str(),
-                          std::string(msg.ice_data.cbegin(), msg.ice_data.cend()).c_str());
-                if (auto call = weak_call.lock())
-                    call->setState(Call::ConnectionState::PROGRESSING);
-                if (!ice->start(msg.ice_data)) {
-                    call->onFailure();
-                    return true;
-                }
-                return false;
-            }
-        );
+                    if (not ice->isInitialized())
+                        return true; // process task again!
+
+                    RING_WARN("ICE initialised");
+
+                    // Next step: sent the ICE data to peer through DHT
+                    const dht::Value::Id callvid  = udist(shared_this->rand_);
+                    const dht::Value::Id vid  = udist(shared_this->rand_);
+                    const auto callkey = dht::InfoHash::get("callto:" + dev.dev.toString());
+                    dht::Value val { dht::IceCandidates(callvid, ice->getLocalAttributesAndCandidates()) };
+                    val.id = vid;
+
+                    //RING_WARN("ICE initialised");
+                    shared_this->dht_.putEncrypted(
+                        callkey, dev.dev,
+                        std::move(val),
+                        [=](bool ok) { // Put complete callback
+                            if (!ok) {
+                                RING_WARN("Can't put ICE descriptor on DHT");
+                                if (auto call = weak_dev_call.lock())
+                                    call->onFailure();
+                            } else
+                                RING_DBG("Successfully put ICE descriptor on DHT");
+                        }
+                    );
 
-        shared_this->pendingCalls_.emplace_back(PendingCall{
-            std::chrono::steady_clock::now(),
-            ice, weak_call,
-            std::move(listenKey),
-            callkey, toH
-        });
+                    auto listenKey = shared_this->dht_.listen<dht::IceCandidates>(
+                        callkey,
+                        [=] (dht::IceCandidates&& msg) {
+                            if (msg.id != callvid or msg.from != dev.dev)
+                                return true;
+                            RING_WARN("ICE request replied from DHT peer %s\n%s", dev.dev.toString().c_str(),
+                                      std::string(msg.ice_data.cbegin(), msg.ice_data.cend()).c_str());
+                            if (auto call = weak_dev_call.lock())
+                                call->setState(Call::ConnectionState::PROGRESSING);
+                            if (!ice->start(msg.ice_data)) {
+                                call->onFailure();
+                                return true;
+                            }
+                            return false;
+                        }
+                    );
 
-        return false;
+                    shared_this->pendingCalls_.emplace_back(PendingCall{
+                        std::chrono::steady_clock::now(),
+                        ice, weak_dev_call,
+                        std::move(listenKey),
+                        callkey, dev.dev
+                    });
+                    return false;
+                });
+            }
+        });
+        return true;
+    }, [=](bool ok){
+        RING_WARN("newOutgoingCall: found %lu devices", treatedDevices_->size());
+        if (treatedDevices_->empty()) {
+            if (auto call = weak_call.lock()) {
+                call->onFailure();
+            }
+        }
     });
 
     return call;
@@ -409,6 +458,7 @@ RingAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target)
         return false;
     }
 
+    RING_ERR("Sending SIP invite");
     if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) {
         RING_ERR("Unable to send invite message for this call");
         return false;
@@ -421,6 +471,9 @@ RingAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target)
 
 void RingAccount::serialize(YAML::Emitter &out)
 {
+    if (registrationState_ == RegistrationState::INITIALIZING)
+        return;
+
     out << YAML::BeginMap;
     SIPAccountBase::serialize(out);
     out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_;
@@ -429,6 +482,11 @@ void RingAccount::serialize(YAML::Emitter &out)
     out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact_;
     out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted_;
 
+    out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << archivePath_;
+    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt_;
+    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value << YAML::Binary(receiptSignature_.data(), receiptSignature_.size());
+    out << YAML::Key << DRing::Account::ConfProperties::ETH::ACCOUNT << YAML::Value << ethAccount_;
+
     // tls submap
     out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
     SIPAccountBase::serializeTls(out);
@@ -442,39 +500,174 @@ void RingAccount::unserialize(const YAML::Node &node)
     using yaml_utils::parseValue;
 
     SIPAccountBase::unserialize(node);
-
     parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory_);
     parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact_);
     parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted_);
+
+    try {
+        parseValue(node, DRing::Account::ConfProperties::ETH::ACCOUNT, ethAccount_);
+    } catch (const std::exception& e) {
+        RING_WARN("can't read eth account: %s", e.what());
+    }
+
+    try {
+        parseValue(node, DRing::Account::ConfProperties::ARCHIVE_PATH, archivePath_);
+    } catch (const std::exception& e) {
+        RING_WARN("can't read archive path: %s", e.what());
+    }
+
+    try {
+        parseValue(node, Conf::RING_ACCOUNT_RECEIPT, receipt_);
+        auto receipt_sig = node[Conf::RING_ACCOUNT_RECEIPT_SIG].as<YAML::Binary>();
+        receiptSignature_ = {receipt_sig.data(), receipt_sig.data()+receipt_sig.size()};
+    } catch (const std::exception& e) {
+        RING_WARN("can't read receipt: %s", e.what());
+    }
+
     if (not dhtPort_)
         dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE);
     dhtPortUsed_ = dhtPort_;
 
     parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
 
-    checkIdentityPath();
+    loadAccount();
 }
 
 void
-RingAccount::checkIdentityPath()
+RingAccount::createRingDevice(const dht::crypto::Identity& id)
 {
-    if (not tlsPrivateKeyFile_.empty() and not tlsCertificateFile_.empty()) {
-        loadIdentity();
-        return;
+    RING_WARN("createRingDevice");
+    auto dev_id = dht::crypto::generateIdentity("Ring device", id);
+    if (!dev_id.first || !dev_id.second) {
+        throw VoipLinkException("Can't generate identity for this account.");
+    }
+    idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID();
+    fileutils::check_dir(idPath_.c_str(), 0700);
+
+    // save the chain including CA
+    saveIdentity(dev_id, idPath_ + DIR_SEPARATOR_STR "dht");
+    tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt";
+    tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key";
+    tlsPassword_ = {};
+    identity_ = dev_id;
+    ringDeviceId_ = dev_id.first->getPublicKey().getId().toString();
+
+    receipt_ = makeReceipt(id);
+    RING_WARN("createRingDevice with %s", id.first->getPublicKey().getId().toString().c_str());
+    receiptSignature_ = id.first->sign({receipt_.begin(), receipt_.end()});
+}
+
+void
+RingAccount::initRingDevice(const ArchiveContent& a)
+{
+    RING_WARN("initRingDevice");
+    SIPAccountBase::setAccountDetails(a.config);
+    parseInt(a.config, Conf::CONFIG_DHT_PORT, dhtPort_);
+    parseBool(a.config, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
+    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY, allowPeersFromHistory_);
+    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT, allowPeersFromContact_);
+    parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED, allowPeersFromTrusted_);
+    ringAccountId_ = a.id.second->getId().toString();
+    username_ = RING_URI_PREFIX+ringAccountId_;
+    ethAccount_ = dev::KeyPair(dev::Secret(a.eth_key)).address().hex();
+    createRingDevice(a.id);
+}
+
+std::string
+RingAccount::makeReceipt(const dht::crypto::Identity& id)
+{
+    RING_WARN("making receipt");
+    DeviceAnnouncement announcement;
+    announcement.dev = identity_.second->getId();
+    dht::Value ann_val {announcement};
+    ann_val.sign(*id.first);
+
+    std::ostringstream is;
+    is << "{\"id\":\"" << id.second->getId()
+       << "\",\"dev\":\"" << identity_.second->getId()
+       << "\",\"eth\":\"" << ethAccount_
+       << "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}";
+
+    announce_ = std::make_shared<dht::Value>(std::move(ann_val));
+    return is.str();
+}
+
+bool
+RingAccount::hasSignedReceipt()
+{
+    if (receipt_.empty() or receiptSignature_.empty())
+        return false;
+
+    if (not identity_.first or not identity_.second) {
+        RING_WARN("hasSignedReceipt() no identity");
+        return false;
+    }
+
+    auto pk = identity_.second->issuer->getPublicKey();
+    RING_WARN("hasSignedReceipt() with %s", pk.getId().toString().c_str());
+    if (!pk.checkSignature({receipt_.begin(), receipt_.end()}, receiptSignature_)) {
+        RING_WARN("hasSignedReceipt() signature check failed");
+        return false;
+    }
+
+    Json::Value root;
+    Json::Reader reader;
+    if (!reader.parse(receipt_, root))
+        return false;
+
+    auto dev_id = root["dev"].asString();
+    if (dev_id != identity_.second->getId().toString()) {
+        RING_WARN("hasSignedReceipt() dev_id not matching");
+        return false;
+    }
+    auto id = root["id"].asString();
+    if (id != pk.getId().toString()) {
+        RING_WARN("hasSignedReceipt() id not matching");
+        return false;
+    }
+
+    dht::Value announce_val;
+    try {
+        auto announce = base64::decode(root["announce"].asString());
+        msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size());
+        //dht::Value announce_val (announce_msg.get());
+        announce_val.msgpack_unpack(announce_msg.get());
+        if (not announce_val.checkSignature()) {
+            RING_WARN("hasSignedReceipt() announce signature check failed");
+            return false;
+        }
+        DeviceAnnouncement da;
+        da.unpackValue(announce_val);
+        if (da.from.toString() != id or da.dev.toString() != dev_id) {
+            RING_WARN("hasSignedReceipt() announce not matching");
+            return false;
+        }
+    } catch (const std::exception& e) {
+        RING_WARN("hasSignedReceipt(): can't read announce: %s", e.what());
+        return false;
     }
 
-    const auto idPath = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID();
-    tlsPrivateKeyFile_ = idPath + DIR_SEPARATOR_STR "dht.key";
-    tlsCertificateFile_ = idPath + DIR_SEPARATOR_STR "dht.crt";
-    loadIdentity();
+    ringAccountId_ = id;
+    ringDeviceId_ = identity_.first->getPublicKey().getId().toString();
+    username_ = RING_URI_PREFIX + id;
+    announce_ = std::make_shared<dht::Value>(std::move(announce_val));
+
+    auto eth_addr = root["eth"].asString();
+    if (eth_addr != ethAccount_) {
+        RING_WARN("hasSignedReceipt() eth_addr not matching");
+        ethAccount_ = eth_addr;
+    }
+
+    RING_WARN("hasSignedReceipt() -> true");
+    return true;
 }
 
 dht::crypto::Identity
 RingAccount::loadIdentity()
 {
+    RING_WARN("loadIdentity() %s %s", tlsCertificateFile_.c_str(), tlsPrivateKeyFile_.c_str());
     dht::crypto::Certificate dht_cert;
     dht::crypto::PrivateKey dht_key;
-
     try {
 #if TARGET_OS_IPHONE
         const auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() + DIR_SEPARATOR_STR;
@@ -484,42 +677,225 @@ RingAccount::loadIdentity()
         dht_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCertificateFile_));
         dht_key = dht::crypto::PrivateKey(fileutils::loadFile(tlsPrivateKeyFile_), tlsPassword_);
 #endif
+        auto crt_id = dht_cert.getId();
+        if (crt_id != dht_key.getPublicKey().getId())
+            return {};
+
+        identity_ = {
+            std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
+            std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))
+        };
     }
     catch (const std::exception& e) {
         RING_ERR("Error loading identity: %s", e.what());
-        auto ca = dht::crypto::generateIdentity("Ring CA");
-        if (!ca.first || !ca.second) {
-            throw VoipLinkException("Can't generate CA for this account.");
-        }
-        auto id = dht::crypto::generateIdentity("Ring", ca);
-        if (!id.first || !id.second) {
-            throw VoipLinkException("Can't generate identity for this account.");
+    }
+
+    return identity_;
+}
+
+RingAccount::ArchiveContent
+RingAccount::readArchive(const std::string& pwd) const
+{
+    RING_WARN("readArchive()");
+
+    // Read file
+    std::vector<uint8_t> file = fileutils::loadFile(archivePath_);
+
+    // Decrypt
+    file = dht::crypto::aesDecrypt(file, pwd);
+
+    // Load
+    return loadArchive(file);
+}
+
+
+RingAccount::ArchiveContent
+RingAccount::loadArchive(const std::vector<uint8_t>& dat)
+{
+    ArchiveContent c;
+    RING_WARN("loadArchive()");
+
+    std::vector<uint8_t> file;
+
+    // Decompress
+    try {
+        file = archiver::decompress(dat);
+    } catch (const std::exception& ex) {
+        RING_ERR("Decompression failed: %s", ex.what());
+        throw std::runtime_error("failed to read file.");
+    }
+
+    // Decode string
+    std::string decoded {file.begin(), file.end()};
+    Json::Value value;
+    Json::Reader reader;
+    if (!reader.parse(decoded.c_str(),value)) {
+        RING_ERR("Failed to parse %s", reader.getFormattedErrorMessages().c_str());
+        throw std::runtime_error("failed to parse JSON.");
+    }
+
+    // Import content
+    try {
+        c.config = DRing::getAccountTemplate(ACCOUNT_TYPE);
+        for (Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++) {
+            if (itr->asString().empty())
+                continue;
+            if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) {
+            } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
+            } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) {
+            } else if (itr.key().asString().compare(Conf::RING_CA_KEY) == 0) {
+                c.ca_key = {base64::decode(itr->asString())};
+            } else if (itr.key().asString().compare(Conf::RING_ACCOUNT_KEY) == 0) {
+                c.id.first = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString()));
+            } else if (itr.key().asString().compare(Conf::RING_ACCOUNT_CERT) == 0) {
+                c.id.second = std::make_shared<dht::crypto::Certificate>(base64::decode(itr->asString()));
+            } else if (itr.key().asString().compare(Conf::ETH_KEY) == 0) {
+                c.eth_key = base64::decode(itr->asString());
+            } else
+                c.config[itr.key().asString()] = itr->asString();
         }
-        idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID();
-        fileutils::check_dir(idPath_.c_str(), 0700);
+    } catch (const std::exception& ex) {
+        RING_ERR("Can't parse JSON: %s", ex.what());
+    }
 
-        fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", ca.first->serialize(), 0600);
+    return c;
+}
 
-        // save the chain including CA
-        saveIdentity(id, idPath_ + DIR_SEPARATOR_STR "dht");
-#if TARGET_OS_IPHONE
-        tlsCertificateFile_ = "dht.crt";
-        tlsPrivateKeyFile_ =  "dht.key";
-#else
-        tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt";
-        tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key";
-#endif
-        tlsPassword_ = {};
 
-        username_ = RING_URI_PREFIX+id.second->getId().toString();
-        return id;
+std::vector<uint8_t>
+RingAccount::makeArchive(const ArchiveContent& archive) const
+{
+    RING_WARN("makeArchive()");
+
+    Json::Value root;
+
+    auto details = getAccountDetails();
+    for (auto it : details) {
+        if (it.first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) {
+            // Ringtone path is not exportable
+        } else if (it.first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 ||
+                it.first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 ||
+                it.first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) {
+            // replace paths by the files content
+            if (not it.second.empty()) {
+                try {
+                    root[it.first] = base64::encode(fileutils::loadFile(it.second));
+                } catch (...) {}
+            }
+        } else
+            root[it.first] = it.second;
     }
 
-    username_ = RING_URI_PREFIX+dht_cert.getId().toString();
-    return {
-        std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
-        std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))
-    };
+    root[Conf::RING_CA_KEY] = base64::encode(archive.ca_key.serialize());
+    root[Conf::RING_ACCOUNT_KEY] = base64::encode(archive.id.first->serialize());
+    root[Conf::RING_ACCOUNT_CERT] = base64::encode(archive.id.second->getPacked());
+    root[Conf::ETH_KEY] = base64::encode(archive.eth_key);
+
+    Json::FastWriter fastWriter;
+    std::string output = fastWriter.write(root);
+
+    // Compress
+    return archiver::compress(output);
+}
+
+void
+RingAccount::saveArchive(const ArchiveContent& archive_content, const std::string& pwd)
+{
+    std::vector<uint8_t> archive;
+    try {
+        archive = makeArchive(archive_content);
+    } catch (const std::runtime_error& ex) {
+        RING_ERR("Can't export archive: %s", ex.what());
+        return;
+    }
+
+    // Encrypt using provided password
+    auto encrypted = dht::crypto::aesEncrypt(archive, pwd);
+
+    // Write
+    try {
+        if (archivePath_.empty())
+            archivePath_ = idPath_ + DIR_SEPARATOR_STR "export.gz";
+        fileutils::saveFile(archivePath_, encrypted);
+    } catch (const std::runtime_error& ex) {
+        RING_ERR("Export failed: %s", ex.what());
+        return;
+    }
+}
+
+std::pair<std::vector<uint8_t>, dht::InfoHash>
+RingAccount::computeKeys(const std::string& password, const std::string& pin, bool previous)
+{
+    // Compute time seed
+    auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
+    auto tseed = now.count() / std::chrono::duration_cast<std::chrono::seconds>(EXPORT_KEY_RENEWAL_TIME).count();
+    if (previous)
+        tseed--;
+    std::stringstream ss;
+    ss << std::hex << tseed;
+    auto tseed_str = ss.str();
+
+    // Generate key for archive encryption, using PIN as the salt
+    std::vector<uint8_t> salt_key;
+    salt_key.reserve(pin.size() + tseed_str.size());
+    salt_key.insert(salt_key.end(), pin.begin(), pin.end());
+    salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end());
+    auto key = dht::crypto::stretchKey(password, salt_key, 256/8);
+
+    // Generate public storage location as SHA1(key).
+    auto loc = dht::InfoHash::get(key);
+
+    return {key, loc};
+}
+
+void
+RingAccount::addDevice(const std::string& password)
+{
+    auto this_ = std::static_pointer_cast<RingAccount>(shared_from_this());
+    ThreadPool::instance().run([this_,password]() {
+        std::vector<uint8_t> key;
+        dht::InfoHash loc;
+        std::string pin_str;
+        ArchiveContent a;
+        try {
+            RING_DBG("Exporting Ring account %s", this_->getAccountID().c_str());
+
+            a = this_->readArchive(password);
+
+            // Generate random 32bits PIN
+            std::uniform_int_distribution<uint32_t> dis;
+            auto pin = dis(this_->rand_);
+            // Manipulate PIN as hex
+            std::stringstream ss;
+            ss << std::hex << pin;
+            pin_str = ss.str();
+            std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::toupper);
+
+            std::tie(key, loc) = computeKeys(password, pin_str);
+        } catch (const std::exception& e) {
+            RING_ERR("Can't add device: %s", e.what());
+            emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 1, "");
+            return;
+        }
+        try {
+            auto archive = this_->makeArchive(a);
+            auto encrypted = dht::crypto::aesEncrypt(archive, key);
+            if (not this_->dht_.isRunning())
+                throw std::runtime_error("DHT is not running..");
+            this_->dht_.put(loc, encrypted, [this_,pin_str](bool ok) {
+                RING_WARN("Done publishing account archive: %s", ok ? "success" : "failure");
+                if (ok)
+                    emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 0, pin_str);
+                else
+                    emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, "");
+            });
+            RING_WARN("Adding new device with PIN: %s at %s (size %zu)", pin_str.c_str(), loc.toString().c_str(), encrypted.size());
+        } catch (const std::exception& e) {
+            RING_ERR("Can't add device: %s", e.what());
+            emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, "");
+            return;
+        }
+    });
 }
 
 void
@@ -531,6 +907,200 @@ RingAccount::saveIdentity(const dht::crypto::Identity id, const std::string& pat
         fileutils::saveFile(path + ".crt", id.second->getPacked(), 0600);
 }
 
+void
+RingAccount::loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin)
+{
+    setRegistrationState(RegistrationState::INITIALIZING);
+
+    // launch dedicated dht instance
+    if (dht_.isRunning()) {
+        RING_ERR("DHT already running (stopping it first).");
+        dht_.join();
+    }
+    dht_.setOnStatusChanged([this](dht::NodeStatus s4, dht::NodeStatus s6) {
+        RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6));
+    });
+    dht_.run((in_port_t)dhtPortUsed_, {}, true);
+    dht_.bootstrap(loadNodes());
+    auto bootstrap = loadBootstrap();
+    if (not bootstrap.empty())
+        dht_.bootstrap(bootstrap);
+
+    std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this());
+    auto state_old = std::make_shared<std::pair<bool, bool>>(false, true);
+    auto state_new = std::make_shared<std::pair<bool, bool>>(false, true);
+    auto found = std::make_shared<bool>(false);
+
+    auto archiveFound = [w,found,archive_password](const ArchiveContent& a) {
+        *found =  true;
+        if (auto this_ = w.lock()) {
+            this_->initRingDevice(a);
+            this_->saveArchive(a, archive_password);
+            this_->registrationState_ = RegistrationState::UNREGISTERED;
+            Manager::instance().saveConfig();
+            this_->doRegister();
+        }
+    };
+    auto searchEnded = [w,found,state_old,state_new](){
+        if (*found)
+            return;
+        if (state_old->first && state_new->first) {
+            bool network_error = !state_old->second && !state_new->second;
+            RING_WARN("Failure looking for archive on DHT: %s", network_error ? "network error" : "not found");
+            if (auto this_ = w.lock()) {
+                this_->setRegistrationState(network_error ? RegistrationState::ERROR_NETWORK : RegistrationState::ERROR_GENERIC);
+                runOnMainThread([=]() {
+                    Manager::instance().removeAccount(this_->getAccountID());
+                });
+            }
+        }
+    };
+
+    auto search = [w,found,archive_password,archive_pin,archiveFound,searchEnded](bool previous, std::shared_ptr<std::pair<bool, bool>>& state) {
+        std::vector<uint8_t> key;
+        dht::InfoHash loc;
+
+        // compute archive location and decryption keys
+        try {
+            std::tie(key, loc) = computeKeys(archive_password, archive_pin, previous);
+            RING_DBG("Trying to load account from DHT with %s at %s", archive_pin.c_str(), loc.toString().c_str());
+            if (auto this_ = w.lock()) {
+                this_->dht_.get(loc, [w,key,found,archive_password,archiveFound](std::shared_ptr<dht::Value> val) {
+                    std::vector<uint8_t> decrypted;
+                    try {
+                        decrypted = dht::crypto::aesDecrypt(val->data, key);
+                    } catch (const std::exception& ex) {
+                        return true;
+                    }
+                    RING_DBG("Found archive on the DHT");
+                    runOnMainThread([=]() {
+                        try {
+                            archiveFound(loadArchive(decrypted));
+                        } catch (const std::exception& e) {
+                            RING_WARN("Error reading archive: %s", e.what());
+                            if (auto this_ = w.lock()) {
+                                this_->setRegistrationState(RegistrationState::ERROR_GENERIC);
+                                Manager::instance().removeAccount(this_->getAccountID());
+                            }
+                        }
+                    });
+                    return not *found;
+                }, [=](bool ok) {
+                    RING_DBG("DHT archive search ended at %s", loc.toString().c_str());
+                    state->first = true;
+                    state->second = ok;
+                    searchEnded();
+                });
+            }
+        } catch (const std::exception& e) {
+            RING_ERR("Error computing keys: %s", e.what());
+            state->first = true;
+            state->second = true;
+            searchEnded();
+            return;
+        }
+    };
+
+    ThreadPool::instance().run(std::bind(search, true, state_old));
+    ThreadPool::instance().run(std::bind(search, false, state_new));
+}
+
+void
+RingAccount::createAccount(const std::string& archive_password)
+{
+    RING_WARN("Creating new Ring account");
+    setRegistrationState(RegistrationState::INITIALIZING);
+    auto sthis = std::static_pointer_cast<RingAccount>(shared_from_this());
+    ThreadPool::instance().run([sthis,archive_password](){
+        ArchiveContent a;
+        auto& this_ = *sthis;
+
+        RING_WARN("Generating ETH key");
+        auto future_keypair = ThreadPool::instance().get<dev::KeyPair>(std::bind(&dev::KeyPair::create));
+
+        try {
+            if (this_.identity_.first and this_.identity_.second) {
+                RING_WARN("Converting certificate from old ring account");
+                a.id = this_.identity_;
+                try {
+                    a.ca_key = fileutils::loadFile(this_.idPath_ + DIR_SEPARATOR_STR "ca.key");
+                } catch (...) {}
+            } else {
+                auto ca = dht::crypto::generateIdentity("Ring CA");
+                if (!ca.first || !ca.second) {
+                    throw VoipLinkException("Can't generate CA for this account.");
+                }
+                a.id = dht::crypto::generateIdentity("Ring", ca, 4096, true);
+                if (!a.id.first || !a.id.second) {
+                    throw VoipLinkException("Can't generate identity for this account.");
+                }
+                RING_WARN("New account: CA: %s, RingID: %s", ca.second->getId().toString().c_str(), a.id.second->getId().toString().c_str());
+                a.ca_key = std::move(*ca.first);
+            }
+            this_.ringAccountId_ = a.id.second->getId().toString();
+            this_.createRingDevice(a.id);
+            this_.username_ = RING_URI_PREFIX+this_.ringAccountId_;
+            auto keypair = future_keypair.get();
+            this_.ethAccount_ = keypair.address().hex();
+            a.eth_key = keypair.secret().makeInsecure().asBytes();
+            this_.saveArchive(a, archive_password);
+        } catch (...) {
+            this_.setRegistrationState(RegistrationState::ERROR_GENERIC);
+            runOnMainThread([sthis]() {
+                Manager::instance().removeAccount(sthis->getAccountID());
+            });
+        }
+        this_.registrationState_ = RegistrationState::UNREGISTERED;
+        Manager::instance().saveConfig();
+        this_.doRegister();
+    });
+}
+
+void
+RingAccount::loadAccount(const std::string& archive_password, const std::string& archive_pin)
+{
+    if (registrationState_ == RegistrationState::INITIALIZING)
+        return;
+
+    RING_WARN("RingAccount::loadAccount");
+    try {
+        loadIdentity();
+
+        if (hasSignedReceipt()) {
+            if (archivePath_.empty() or not fileutils::isFile(archivePath_))
+                RING_WARN("Account archive not found, won't be able to add new devices.");
+            // normal account loading path
+            return;
+        }
+
+        if (archivePath_.empty() or not fileutils::isFile(archivePath_)) {
+            // no receipt or archive, creating new account
+            if (archive_password.empty()) {
+                RING_WARN("Password needed to create archive");
+                setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
+            } else {
+                if (archive_pin.empty()) {
+                    createAccount(archive_password);
+                } else {
+                    loadAccountFromDHT(archive_password, archive_pin);
+                }
+            }
+        } else {
+            if (archive_password.empty()) {
+                RING_WARN("Password needed to read archive");
+                setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
+            } else {
+                RING_WARN("Archive present but no valid receipt: creating new device");
+                initRingDevice(readArchive(archive_password));
+                Manager::instance().saveConfig();
+            }
+        }
+    } catch (const std::exception& e) {
+        RING_WARN("Error loading account: %s", e.what());
+        setRegistrationState(RegistrationState::ERROR_GENERIC);
+    }
+}
+
 void
 RingAccount::setAccountDetails(const std::map<std::string, std::string> &details)
 {
@@ -545,7 +1115,15 @@ RingAccount::setAccountDetails(const std::map<std::string, std::string> &details
     if (not dhtPort_)
         dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE);
     dhtPortUsed_ = dhtPort_;
-    checkIdentityPath();
+
+    std::string archive_password;
+    std::string archive_pin;
+    parseString(details, DRing::Account::ConfProperties::ARCHIVE_PASSWORD, archive_password);
+    parseString(details, DRing::Account::ConfProperties::ARCHIVE_PIN,      archive_pin);
+    std::transform(archive_pin.begin(), archive_pin.end(), archive_pin.begin(), ::toupper);
+    parseString(details, DRing::Account::ConfProperties::ARCHIVE_PATH,     archivePath_);
+
+    loadAccount(archive_password, archive_pin);
 }
 
 std::map<std::string, std::string>
@@ -554,6 +1132,7 @@ RingAccount::getAccountDetails() const
     std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails();
     a.emplace(Conf::CONFIG_DHT_PORT, ring::to_string(dhtPort_));
     a.emplace(Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_ ? TRUE_STR : FALSE_STR);
+    a.emplace(DRing::Account::ConfProperties::RING_DEVICE_ID, ringDeviceId_);
 
     /* these settings cannot be changed (read only), but clients should still be
      * able to read what they are */
@@ -573,6 +1152,9 @@ RingAccount::getAccountDetails() const
     /* GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT is defined as -1 */
     a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC,    "-1");
 
+    //a.emplace(DRing::Account::ConfProperties::ETH::KEY_FILE,               ethPath_);
+    a.emplace(DRing::Account::ConfProperties::ETH::ACCOUNT,                ethAccount_);
+
     return a;
 }
 
@@ -638,7 +1220,7 @@ RingAccount::handlePendingCallList()
 
 pj_status_t
 check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t* cert_list,
-                       unsigned cert_num)
+                       unsigned cert_num, std::shared_ptr<dht::crypto::Certificate>& cert_out)
 {
     if (cert_num == 0) {
         RING_ERR("[peer:%s] No certificate", from.toString().c_str());
@@ -655,8 +1237,12 @@ check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t
         return PJ_SSL_CERT_EUNTRUSTED;
     }
 
-    std::vector<uint8_t> crt_blob(cert_list[0].data, cert_list[0].data + cert_list[0].size);
-    dht::crypto::Certificate crt(crt_blob);
+    // Assumes the chain has already been checked by GnuTLS.
+    std::vector<std::pair<uint8_t*, uint8_t*>> crt_data;
+    crt_data.reserve(cert_num);
+    for (unsigned i=0; i<cert_num; i++)
+        crt_data.emplace_back(cert_list[i].data, cert_list[i].data + cert_list[i].size);
+    dht::crypto::Certificate crt(crt_data);
 
     const auto tls_id = crt.getId();
     if (crt.getUID() != tls_id.toString()) {
@@ -671,6 +1257,9 @@ check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t
     }
 
     RING_DBG("[peer:%s] Certificate verified", from.toString().c_str());
+
+    cert_out = std::make_shared<dht::crypto::Certificate>(std::move(crt));
+
     return PJ_SUCCESS;
 }
 
@@ -701,18 +1290,38 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming)
 
     // Securize a SIP transport with TLS (on top of ICE tranport) and assign the call with it
     auto remote_h = pc.from;
-    auto id(loadIdentity());
+    if (not identity_.first or not identity_.second)
+        throw std::runtime_error("No identity configured for this account.");
 
+    std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this());
     tls::TlsParams tlsParams {
         .ca_list = "",
-        .cert = id.second,
-        .cert_key = id.first,
+        .cert = identity_.second,
+        .cert_key = identity_.first,
         .dh_params = dhParams_,
         .timeout = std::chrono::duration_cast<decltype(tls::TlsParams::timeout)>(TLS_TIMEOUT),
-        .cert_check = [remote_h](unsigned status, const gnutls_datum_t* cert_list,
-                                 unsigned cert_num) -> pj_status_t {
+        .cert_check = [w,call,remote_h,incoming](unsigned status, const gnutls_datum_t* cert_list, unsigned cert_num) -> pj_status_t {
             try {
-                return check_peer_certificate(remote_h, status, cert_list, cert_num);
+                if (auto sthis = w.lock()) {
+                    auto& this_ = *sthis;
+                    std::shared_ptr<dht::crypto::Certificate> peer_cert;
+                    auto ret = check_peer_certificate(remote_h, status, cert_list, cert_num, peer_cert);
+                    if (ret == PJ_SUCCESS and peer_cert) {
+                        std::lock_guard<std::mutex> lock(this_.callsMutex_);
+                        for (auto& pscall : this_.pendingSipCalls_) {
+                            if (auto pcall = pscall.call.lock()) {
+                                if (pcall == call and not pscall.from_cert) {
+                                    RING_DBG("[call:%s] got peer certificate from TLS negotiation", call->getCallId().c_str());
+                                    tls::CertificateStore::instance().pinCertificate(peer_cert);
+                                    pscall.from_cert = peer_cert;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    return ret;
+                } else
+                    return PJ_SSL_CERT_EUNTRUSTED;
             } catch (const std::exception& e) {
                 RING_ERR("[peer:%s] TLS certificate check exception: %s",
                          remote_h.toString().c_str(), e.what());
@@ -720,9 +1329,8 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming)
             }
         }
     };
-    auto tr = link_->sipTransportBroker->getTlsIceTransport(pc.ice_sp, ICE_COMP_SIP_TRANSPORT,
-                                                            tlsParams);
-    call->setTransport(tr);
+    call->setTransport(link_->sipTransportBroker->getTlsIceTransport(pc.ice_sp, ICE_COMP_SIP_TRANSPORT,
+                                                            tlsParams));
 
     // Notify of fully available connection between peers
     RING_DBG("[call:%s] SIP communication established", call->getCallId().c_str());
@@ -754,6 +1362,7 @@ RingAccount::mapPortUPnP()
          */
         uint16_t port_used;
         std::lock_guard<std::mutex> lock(upnp_mtx);
+        upnp_->removeMappings();
         added = upnp_->addAnyMapping(dhtPort_, ring::upnp::PortType::UDP, false, &port_used);
         if (added) {
             if (port_used != dhtPort_)
@@ -778,6 +1387,13 @@ RingAccount::doRegister()
         return;
     }
 
+    // invalid state transitions:
+    // INITIALIZING: generating/loading certificates, can't register
+    // NEED_MIGRATION: old Ring account detected, user needs to migrate
+    if (registrationState_ == RegistrationState::INITIALIZING
+     || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
+        return;
+
     if (not dhParams_.valid()) {
         generateDhParams();
     }
@@ -798,24 +1414,50 @@ RingAccount::doRegister()
 
 }
 
-static constexpr const char*
-dhtStatusStr(dht::NodeStatus status) {
-    return status == dht::NodeStatus::Connected  ? "connected"  : (
-           status == dht::NodeStatus::Connecting ? "connecting" :
-                                                    "disconnected");
+
+std::vector<std::pair<sockaddr_storage, socklen_t>>
+RingAccount::loadBootstrap() const
+{
+    std::vector<std::pair<sockaddr_storage, socklen_t>> bootstrap;
+    if (!hostname_.empty()) {
+        std::stringstream ss(hostname_);
+        std::string node_addr;
+        while (std::getline(ss, node_addr, ';')) {
+            auto ips = ip_utils::getAddrList(node_addr);
+            if (ips.empty()) {
+                IpAddr resolved(node_addr);
+                if (resolved) {
+                    if (resolved.getPort() == 0)
+                        resolved.setPort(DHT_DEFAULT_PORT);
+                    bootstrap.emplace_back(resolved, resolved.getLength());
+                }
+            } else {
+                for (auto& ip : ips) {
+                    if (ip.getPort() == 0)
+                        ip.setPort(DHT_DEFAULT_PORT);
+                    bootstrap.emplace_back(ip, ip.getLength());
+                }
+            }
+        }
+        for (auto ip : bootstrap)
+            RING_DBG("Bootstrap node: %s", IpAddr(ip.first).toString(true).c_str());
+    }
+    return bootstrap;
 }
 
 void
 RingAccount::doRegister_()
 {
     try {
+        if (not identity_.first or not identity_.second)
+            throw std::runtime_error("No identity configured for this account.");
+
         loadTreatedCalls();
         loadTreatedMessages();
         if (dht_.isRunning()) {
             RING_ERR("DHT already running (stopping it first).");
             dht_.join();
         }
-        auto identity = loadIdentity();
 
         dht_.setOnStatusChanged([this](dht::NodeStatus s4, dht::NodeStatus s6) {
                 RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6));
@@ -837,7 +1479,7 @@ RingAccount::doRegister_()
                 setRegistrationState(state);
             });
 
-        dht_.run((in_port_t)dhtPortUsed_, identity, false);
+        dht_.run((in_port_t)dhtPortUsed_, identity_, false);
 
         dht_.setLocalCertificateStore([](const dht::InfoHash& pk_id) {
             auto& store = tls::CertificateStore::instance();
@@ -853,7 +1495,7 @@ RingAccount::doRegister_()
         dht_.setLoggers(
             [](char const* m, va_list args){ vlogger(LOG_ERR, m, args); },
             [](char const* m, va_list args){ vlogger(LOG_WARNING, m, args); },
-            [](char const* m, va_list args){ vlogger(LOG_DEBUG, m, args); }
+            [](char const* m, va_list args){ /*vlogger(LOG_DEBUG, m, args);*/ }
         );
 #endif
 
@@ -863,36 +1505,21 @@ RingAccount::doRegister_()
         setRegistrationState(RegistrationState::TRYING);
 
         dht_.bootstrap(loadNodes());
-        if (!hostname_.empty()) {
-            std::stringstream ss(hostname_);
-            std::vector<std::pair<sockaddr_storage, socklen_t>> bootstrap;
-            std::string node_addr;
-            while (std::getline(ss, node_addr, ';')) {
-                auto ips = ip_utils::getAddrList(node_addr);
-                if (ips.empty()) {
-                    IpAddr resolved(node_addr);
-                    if (resolved) {
-                        if (resolved.getPort() == 0)
-                            resolved.setPort(DHT_DEFAULT_PORT);
-                        bootstrap.emplace_back(resolved, resolved.getLength());
-                    }
-                } else {
-                    for (auto& ip : ips) {
-                        if (ip.getPort() == 0)
-                            ip.setPort(DHT_DEFAULT_PORT);
-                        bootstrap.emplace_back(ip, ip.getLength());
-                    }
-                }
-            }
-            for (auto ip : bootstrap)
-                RING_DBG("Bootstrap node: %s", IpAddr(ip.first).toString(true).c_str());
+        auto bootstrap = loadBootstrap();
+        if (not bootstrap.empty())
             dht_.bootstrap(bootstrap);
+
+        // Put device annoucement
+        if (announce_) {
+            RING_DBG("Announcing device at %s: %s", dht::InfoHash(ringAccountId_).toString().c_str(), announce_->toString().c_str());
+            dht_.put(dht::InfoHash(ringAccountId_), announce_, dht::DoneCallback{}, {}, true);
         }
 
         // Listen for incoming calls
+        auto ringDeviceId = identity_.first->getPublicKey().getId().toString();
         auto shared = std::static_pointer_cast<RingAccount>(shared_from_this());
-        callKey_ = dht::InfoHash::get("callto:"+dht_.getId().toString());
-        RING_DBG("Listening on callto:%s : %s", dht_.getId().toString().c_str(), callKey_.toString().c_str());
+        callKey_ = dht::InfoHash::get("callto:"+ringDeviceId);
+        RING_DBG("Listening on callto:%s : %s", ringDeviceId.c_str(), callKey_.toString().c_str());
         dht_.listen<dht::IceCandidates>(
             callKey_,
             [shared] (dht::IceCandidates&& msg) {
@@ -901,6 +1528,8 @@ RingAccount::doRegister_()
                 if (msg.from == this_.dht_.getId())
                     return true;
 
+                RING_WARN("ICE candidate from %s.", msg.from.toString().c_str());
+
                 // quick check in case we already explicilty banned this public key
                 auto trustStatus = this_.trust_.getCertificateStatus(msg.from.toString());
                 if (trustStatus == tls::TrustStore::PermissionStatus::BANNED) {
@@ -913,40 +1542,40 @@ RingAccount::doRegister_()
                 if (!res.second)
                     return true;
 
-                if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::ALLOWED) {
-                    this_.findCertificate(
-                        msg.from,
-                        [shared, msg](const std::shared_ptr<dht::crypto::Certificate> cert) mutable {
-                            if (!cert or cert->getId() != msg.from) {
-                                RING_WARN("Can't find certificate of %s for incoming call.",
-                                          msg.from.toString().c_str());
-                                return;
-                            }
-
-                            tls::CertificateStore::instance().pinCertificate(cert);
+                RING_WARN("findCertificate");
+                this_.findCertificate( msg.from,
+                    [shared, msg, trustStatus](const std::shared_ptr<dht::crypto::Certificate> cert) mutable {
+                    RING_WARN("findCertificate: found %p", cert.get());
+                    auto& this_ = *shared;
+                    if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::ALLOWED) {
+                        if (!cert or cert->getId() != msg.from) {
+                            RING_WARN("Can't find certificate of %s for incoming call.",
+                                      msg.from.toString().c_str());
+                            return;
+                        }
 
-                            auto& this_ = *shared;
-                            if (!this_.trust_.isAllowed(*cert)) {
-                                RING_WARN("Discarding incoming DHT call from untrusted peer %s.",
-                                          msg.from.toString().c_str());
-                                return;
-                            }
+                        tls::CertificateStore::instance().pinCertificate(cert);
 
-                            runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); });
+                        auto& this_ = *shared;
+                        if (!this_.trust_.isAllowed(*cert)) {
+                            RING_WARN("Discarding incoming DHT call from untrusted peer %s.",
+                                      msg.from.toString().c_str());
+                            return;
                         }
-                    );
-                    return true;
-                }
-                else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::BANNED) {
-                    this_.findCertificate(msg.from.toString().c_str());
-                }
+
+                        runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), cert); });
+                    } else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::BANNED) {
+                        //this_.findCertificate(msg.from.toString().c_str());
+                        runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), cert); });
+                    }
+                });
                 // public incoming calls allowed or we explicitly authorised this public key
-                runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); });
+                //runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), {}); });
                 return true;
             }
         );
 
-        auto inboxKey = dht::InfoHash::get("inbox:"+dht_.getId().toString());
+        auto inboxKey = dht::InfoHash::get("inbox:"+ringAccountId_);
         dht_.listen<dht::TrustRequest>(
             inboxKey,
             [shared](dht::TrustRequest&& v) {
@@ -1008,7 +1637,7 @@ RingAccount::doRegister_()
 }
 
 void
-RingAccount::incomingCall(dht::IceCandidates&& msg)
+RingAccount::incomingCall(dht::IceCandidates&& msg, std::shared_ptr<dht::crypto::Certificate> from_cert)
 {
     auto from = msg.from.toString();
     RING_WARN("ICE incoming from DHT peer %s\n%s", from.c_str(),
@@ -1048,7 +1677,8 @@ RingAccount::incomingCall(dht::IceCandidates&& msg)
             .call = weak_call,
             .listen_key = {},
             .call_key = {},
-            .from = msg.from
+            .from = msg.from,
+            .from_cert = from_cert
         });
     }
 }
@@ -1056,6 +1686,7 @@ RingAccount::incomingCall(dht::IceCandidates&& msg)
 void
 RingAccount::doUnregister(std::function<void(bool)> released_cb)
 {
+    RING_WARN("doUnregister");
     {
         std::lock_guard<std::mutex> lock(callsMutex_);
         pendingCalls_.clear();
@@ -1078,19 +1709,16 @@ RingAccount::doUnregister(std::function<void(bool)> released_cb)
 void
 RingAccount::connectivityChanged()
 {
+    RING_WARN("connectivityChanged");
     if (not isUsable()) {
         // nothing to do
         return;
     }
 
     auto shared = std::static_pointer_cast<RingAccount>(shared_from_this());
-    doUnregister([shared](bool /* transport_free */) {
-        if (shared->isUsable())
-            shared->doRegister();
-    });
+    dht_.connectivityChanged();
 }
 
-
 bool
 RingAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb)
 {
@@ -1147,7 +1775,7 @@ loadIdList(const std::string& path)
     std::set<dht::Value::Id> ids;
     std::ifstream file(path);
     if (!file.is_open()) {
-        RING_WARN("Could not load %s", path.c_str());
+        RING_DBG("Could not load %s", path.c_str());
         return ids;
     }
     std::string line;
@@ -1235,7 +1863,7 @@ RingAccount::loadNodes() const
     {
         std::ifstream file(nodesPath);
         if (!file.is_open()) {
-            RING_ERR("Could not load nodes from %s", nodesPath.c_str());
+            RING_DBG("Could not load nodes from %s", nodesPath.c_str());
             return nodes;
         }
         std::string line;
@@ -1279,12 +1907,12 @@ RingAccount::loadDhParams(const std::string path)
         // writeTime throw exception if file doesn't exist
         auto duration = system_clock::now() - fileutils::writeTime(path);
         if (duration >= std::chrono::hours(24 * 3)) // file is valid only 3 days
-            throw std::runtime_error("too old file");
+            throw std::runtime_error("file too old");
 
         RING_DBG("Loading DhParams from file '%s'", path.c_str());
         return {fileutils::loadFile(path)};
     } catch (const std::exception& e) {
-        RING_WARN("Failed to load DhParams file '%s': %s", path.c_str(), e.what());
+        RING_DBG("Failed to load DhParams file '%s': %s", path.c_str(), e.what());
         if (auto params = tls::DhParams::generate()) {
             try {
                 fileutils::saveFile(path, params.serialize(), 0600);
@@ -1294,6 +1922,7 @@ RingAccount::loadDhParams(const std::string path)
             }
             return params;
         }
+        RING_ERR("Can't generate DH params.");
         return {};
     }
 }
@@ -1309,8 +1938,7 @@ RingAccount::generateDhParams()
 MatchRank
 RingAccount::matches(const std::string &userName, const std::string &server) const
 {
-    auto dhtId = dht_.getId().toString();
-    if (userName == dhtId || server == dhtId) {
+    if (userName == ringAccountId_ || server == ringAccountId_ || userName == ringDeviceId_) {
         RING_DBG("Matching account id in request with username %s", userName.c_str());
         return MatchRank::FULL;
     } else {
@@ -1321,42 +1949,42 @@ RingAccount::matches(const std::string &userName, const std::string &server) con
 std::string
 RingAccount::getFromUri() const
 {
-    const std::string uri = "<sip:" + dht_.getId().toString() + "@ring.dht>";
+    const std::string uri = "<sip:" + ringAccountId_ + "@ring.dht>";
     if (not displayName_.empty())
         return "\"" + displayName_ + "\" " + uri;
+    RING_DBG("getFromUri %s", uri.c_str());
     return uri;
 }
 
 std::string
 RingAccount::getToUri(const std::string& to) const
 {
+    RING_DBG("getToUri %s", to.c_str());
     return "<sips:" + to + ";transport=tls>";
 }
 
 pj_str_t
 RingAccount::getContactHeader(pjsip_transport* t)
 {
-    auto ringid = dht_.getId().toString();
-    if (!t) {
+    if (t) {
+        // FIXME: be sure that given transport is from SipIceTransport
+        auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self;
+        auto address = tlsTr->getLocalAddress().toString(true);
+        contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE,
+                                         "%s%s<sips:%s%s%s;transport=tls>",
+                                         displayName_.c_str(),
+                                         (displayName_.empty() ? "" : " "),
+                                         identity_.second->getId().toString().c_str(),
+                                         (address.empty() ? "" : "@"),
+                                         address.c_str());
+    } else {
         RING_ERR("getContactHeader: no SIP transport provided");
         contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE,
                                          "%s%s<sips:%s@ring.dht>",
                                          displayName_.c_str(),
                                          (displayName_.empty() ? "" : " "),
-                                         ringid.c_str());
-        return contact_;
-    }
-
-    // FIXME: be sure that given transport is from SipIceTransport
-    auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self;
-    auto address = tlsTr->getLocalAddress();
-    contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE,
-                                     "%s%s<sips:%s%s%s;transport=tls>",
-                                     displayName_.c_str(),
-                                     (displayName_.empty() ? "" : " "),
-                                     ringid.c_str(),
-                                     (ringid.empty() ? "" : "@"),
-                                     address.toString(true).c_str());
+                                         identity_.second->getId().toString().c_str());
+    }
     return contact_;
 }
 
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index 4c0e819169a3f6245fe6a3bcabd7a951f482d6b6..ca20167e105f7c939c20bcd5b28b9740f5f4bec9 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -52,17 +52,33 @@ class Node;
 class Emitter;
 }
 
+
+namespace dev
+{
+    template <unsigned N> class FixedHash;
+    using h160 = FixedHash<20>;
+    using Address = h160;
+}
+
 namespace ring {
 
 namespace Conf {
-const char *const DHT_PORT_KEY = "dhtPort";
-const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath";
-const char *const DHT_CONTACTS = "dhtContacts";
-const char *const DHT_PUBLIC_PROFILE = "dhtPublicProfile";
-const char *const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls";
-const char *const DHT_ALLOW_PEERS_FROM_HISTORY = "allowPeersFromHistory";
-const char *const DHT_ALLOW_PEERS_FROM_CONTACT = "allowPeersFromContact";
-const char *const DHT_ALLOW_PEERS_FROM_TRUSTED = "allowPeersFromTrusted";
+constexpr const char* const DHT_PORT_KEY = "dhtPort";
+constexpr const char* const DHT_VALUES_PATH_KEY = "dhtValuesPath";
+constexpr const char* const DHT_CONTACTS = "dhtContacts";
+constexpr const char* const DHT_PUBLIC_PROFILE = "dhtPublicProfile";
+constexpr const char* const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls";
+constexpr const char* const DHT_ALLOW_PEERS_FROM_HISTORY = "allowPeersFromHistory";
+constexpr const char* const DHT_ALLOW_PEERS_FROM_CONTACT = "allowPeersFromContact";
+constexpr const char* const DHT_ALLOW_PEERS_FROM_TRUSTED = "allowPeersFromTrusted";
+constexpr const char* const ETH_KEY = "ethKey";
+constexpr const char* const ETH_PATH = "ethPath";
+constexpr const char* const ETH_ACCOUNT = "ethAccount";
+constexpr const char* const RING_CA_KEY = "ringCaKey";
+constexpr const char* const RING_ACCOUNT_KEY = "ringAccountKey";
+constexpr const char* const RING_ACCOUNT_CERT = "ringAccountCert";
+constexpr const char* const RING_ACCOUNT_RECEIPT = "ringAccountReceipt";
+constexpr const char* const RING_ACCOUNT_RECEIPT_SIG = "ringAccountReceiptSignature";
 }
 
 class IceTransport;
@@ -126,7 +142,7 @@ class RingAccount : public SIPAccountBase {
         /**
          * Disconnect from the DHT.
          */
-        void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()) override;
+        void doUnregister(std::function<void(bool)> cb = {}) override;
 
         /**
          * @return pj_str_t "From" uri based on account information.
@@ -254,6 +270,8 @@ class RingAccount : public SIPAccountBase {
         void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
         virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override;
 
+        void addDevice(const std::string& password);
+
         void connectivityChanged() override;
 
     public: // overloaded methods
@@ -262,8 +280,64 @@ class RingAccount : public SIPAccountBase {
     private:
         NON_COPYABLE(RingAccount);
 
+        struct PendingCall {
+            std::chrono::steady_clock::time_point start;
+            std::shared_ptr<IceTransport> ice_sp;
+            std::weak_ptr<SIPCall> call;
+            std::future<size_t> listen_key;
+            dht::InfoHash call_key;
+            dht::InfoHash from;
+            std::shared_ptr<dht::crypto::Certificate> from_cert;
+        };
+
+        struct PendingMessage {
+            dht::InfoHash to;
+            std::chrono::steady_clock::time_point received;
+        };
+
+        struct TrustRequest {
+            dht::InfoHash from;
+            std::chrono::system_clock::time_point received;
+            std::vector<uint8_t> payload;
+        };
+
+        /**
+         * Crypto material contained in the archive,
+         * not persisted in the account configuration
+         */
+        struct ArchiveContent {
+            /** Account main private key and certificate chain */
+            dht::crypto::Identity id;
+
+            /** Generated CA key (for self-signed certificates) */
+            dht::crypto::PrivateKey ca_key;
+
+            /** Ethereum private key */
+            std::vector<uint8_t> eth_key;
+
+            /** Account configuration */
+            std::map<std::string, std::string> config;
+        };
+
+        /**
+         * Device announcement stored on DHT.
+         */
+        struct DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement> {
+        private:
+            using BaseClass = dht::SignedValue<DeviceAnnouncement>;
+        public:
+            static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
+            dht::InfoHash dev;
+            MSGPACK_DEFINE_MAP(dev);
+        };
+
+        /**
+         * Compute archive encryption key and DHT storage location from password and PIN.
+         */
+        static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);
+
         void doRegister_();
-        void incomingCall(dht::IceCandidates&& msg);
+        void incomingCall(dht::IceCandidates&& msg, std::shared_ptr<dht::crypto::Certificate> from);
 
         const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)};
 
@@ -293,18 +367,10 @@ class RingAccount : public SIPAccountBase {
         void igdChanged();
 
         dht::DhtRunner dht_ {};
+        dht::crypto::Identity identity_ {};
 
         dht::InfoHash callKey_;
 
-        struct PendingCall {
-            std::chrono::steady_clock::time_point start;
-            std::shared_ptr<IceTransport> ice_sp;
-            std::weak_ptr<SIPCall> call;
-            std::future<size_t> listen_key;
-            dht::InfoHash call_key;
-            dht::InfoHash from;
-        };
-
         void handlePendingCallList();
         bool handlePendingCall(PendingCall& pc, bool incoming);
 
@@ -320,33 +386,46 @@ class RingAccount : public SIPAccountBase {
         std::set<dht::Value::Id> treatedCalls_ {};
         mutable std::mutex callsMutex_ {};
 
-        struct PendingMessage {
-            dht::InfoHash to;
-            std::chrono::steady_clock::time_point received;
-        };
-
         std::map<dht::Value::Id, PendingMessage> sentMessages_ {};
         std::set<dht::Value::Id> treatedMessages_ {};
 
+        std::string ringAccountId_ {};
+        std::string ringDeviceId_ {};
         std::string idPath_ {};
         std::string cachePath_ {};
         std::string dataPath_ {};
+        std::string ethPath_ {};
+        std::string ethAccount_ {};
 
-        struct TrustRequest {
-            dht::InfoHash from;
-            std::chrono::system_clock::time_point received;
-            std::vector<uint8_t> payload;
-        };
+        std::string archivePath_ {};
+
+        std::string receipt_ {};
+        std::vector<uint8_t> receiptSignature_ {};
+        dht::Value announceVal_;
 
         std::vector<TrustRequest> trustRequests_;
 
         tls::TrustStore trust_;
 
-        /**
-         * Validate the values for privkeyPath_ and certPath_.
-         * If one of these fields is empty, reset them to the default values.
-         */
-        void checkIdentityPath();
+        std::shared_ptr<dht::Value> announce_;
+
+        void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {});
+        void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin);
+
+        bool hasCertificate() const;
+        bool hasPrivateKey() const;
+        bool hasSignedReceipt();
+
+        std::string makeReceipt(const dht::crypto::Identity& id);
+        void createRingDevice(const dht::crypto::Identity& id);
+        void initRingDevice(const ArchiveContent& a);
+
+        void createAccount(const std::string& archive_password);
+        std::vector<uint8_t> makeArchive(const ArchiveContent& content) const;
+        void saveArchive(const ArchiveContent& content, const std::string& pwd);
+        ArchiveContent readArchive(const std::string& pwd) const;
+        static ArchiveContent loadArchive(const std::vector<uint8_t>& data);
+        std::vector<std::pair<sockaddr_storage, socklen_t>> loadBootstrap() const;
 
         void saveIdentity(const dht::crypto::Identity id, const std::string& path) const;
         void saveNodes(const std::vector<dht::NodeExport>&) const;
diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp
index 1e6bcb83d73e3f0c9d617dfdba72d6a2eb9a8d39..2f70c7b5d8aa768314ddf98ab108204fcb1c2177 100644
--- a/src/security/certstore.cpp
+++ b/src/security/certstore.cpp
@@ -131,8 +131,12 @@ CertificateStore::findIssuer(std::shared_ptr<crypto::Certificate> crt) const
 {
     std::shared_ptr<crypto::Certificate> ret {};
     auto n = crt->getIssuerUID();
-    if (not n.empty())
-        ret = findCertificateByUID(n);
+    if (not n.empty()) {
+        if (crt->issuer and crt->issuer->getUID() == n)
+            ret = crt->issuer;
+        else
+            ret = findCertificateByUID(n);
+    }
     if (not ret) {
         n = crt->getIssuerName();
         if (not n.empty())
@@ -471,9 +475,23 @@ TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status)
 bool
 TrustStore::isAllowed(const crypto::Certificate& crt)
 {
-    if (getCertificateStatus(crt.getId().toString()) == PermissionStatus::ALLOWED)
+    // Match by certificate pinning (device)
+    auto status = getCertificateStatus(crt.getId().toString());
+    if (status == PermissionStatus::ALLOWED)
         return true;
+    else if (status == PermissionStatus::BANNED)
+        return false;
+
+    // Match by certificate pinning (Ring account)
+    if (crt.issuer) {
+        status = getCertificateStatus(crt.issuer->getId().toString());
+        if (status == PermissionStatus::ALLOWED)
+            return true;
+        else if (status == PermissionStatus::BANNED)
+            return false;
+    }
 
+    // Match by certificate chain
     updateKnownCerts();
     return matchTrustStore(getChain(crt), allowed_);
 }
diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp
index 09641e8e9bbe020f3c82dc8479a34e9010511580..730030b219e5c1ffa52286334922515e700bf3f3 100644
--- a/src/security/tls_session.cpp
+++ b/src/security/tls_session.cpp
@@ -279,8 +279,15 @@ TlsSession::initCredentials()
 
     // Load user-given identity (key and passwd)
     if (params_.cert) {
-        ret = gnutls_certificate_set_x509_key(*xcred_, &params_.cert->cert, 1,
-                                              params_.cert_key->x509_key);
+        std::vector<gnutls_x509_crt_t> certs;
+        certs.reserve(3);
+        auto crt = params_.cert;
+        while (crt) {
+            certs.emplace_back(crt->cert);
+            crt = crt->issuer;
+        }
+
+        ret = gnutls_certificate_set_x509_key(*xcred_, certs.data(), certs.size(), params_.cert_key->x509_key);
         if (ret < 0)
             throw std::runtime_error("can't load certificate: "
                                      + std::string(gnutls_strerror(ret)));
diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am
index 6b791bae6d71710f8d4721c5fad3c299bde1e2f6..71f7b272b32ed48d248475fe878323ab160b4679 100644
--- a/src/sip/Makefile.am
+++ b/src/sip/Makefile.am
@@ -17,9 +17,7 @@ libsiplink_la_SOURCES = \
         sipvoiplink.h \
         siptransport.h \
         sip_utils.cpp \
-        sip_utils.h \
-        base64.h \
-        base64.c
+        sip_utils.h
 
 libsiplink_la_SOURCES+=sippresence.cpp \
                        sippresence.h \
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index cd1d18b463c6fbe6d61dcb3d119e3d061b26a068..544f3f201061a492150d943ec724ff89a3bdb35e 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -407,6 +407,8 @@ void SIPAccount::serialize(YAML::Emitter &out)
 
     out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort_;
 
+    out << YAML::Key << USERNAME_KEY << YAML::Value << username_;
+
     // each credential is a map, and we can have multiple credentials
     out << YAML::Key << Conf::CRED_KEY << YAML::Value << getCredentials();
     out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << keepAliveEnabled_;
@@ -471,6 +473,8 @@ validate(std::string &member, const std::string &param, const T& valid)
 void SIPAccount::unserialize(const YAML::Node &node)
 {
     SIPAccountBase::unserialize(node);
+    parseValue(node, USERNAME_KEY, username_);
+
     if (not publishedSameasLocal_)
         usePublishedAddressPortInVIA();
 
@@ -478,9 +482,17 @@ void SIPAccount::unserialize(const YAML::Node &node)
     parseValue(node, Conf::PORT_KEY, port);
     localPort_ = port;
 
-    if (not isIP2IP()) parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_);
-
-    if (not isIP2IP()) parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_);
+    if (not isIP2IP()) {
+        parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_);
+        parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_);
+        parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_);
+        const auto& credsNode = node[Conf::CRED_KEY];
+        setCredentials(parseVectorMap(credsNode, {
+            Conf::CONFIG_ACCOUNT_REALM,
+            Conf::CONFIG_ACCOUNT_USERNAME,
+            Conf::CONFIG_ACCOUNT_PASSWORD
+        }));
+    }
 
     bool presEnabled = false;
     parseValue(node, PRESENCE_MODULE_ENABLED_KEY, presEnabled);
@@ -494,8 +506,6 @@ void SIPAccount::unserialize(const YAML::Node &node)
         presence_->support(PRESENCE_FUNCTION_SUBSCRIBE, subscribeSupported);
     }
 
-    if (not isIP2IP()) parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_);
-
     // Init stun server name with default server name
     stunServerName_ = pj_str((char*) stunServer_.data());
 
@@ -535,6 +545,7 @@ void SIPAccount::unserialize(const YAML::Node &node)
 void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &details)
 {
     SIPAccountBase::setAccountDetails(details);
+    parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_);
 
     parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort_);
 
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index f234be540b5420d940e2c327e3f8b14e88b3d34a..767e91e3343a1f6176b1c397e8d133d59120f13d 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -112,7 +112,7 @@ SIPCall::SIPCall(SIPAccountBase& account, const std::string& id, Call::CallType
     , avformatrtp_(new AudioRtpSession(id))
 #ifdef RING_VIDEO
     // The ID is used to associate video streams to calls
-    , videortp_(id, getVideoSettings())
+    , videortp_(new video::VideoRtpSession(id, getVideoSettings()))
     , videoInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice())
 #endif
     , sdp_(new Sdp(id))
@@ -326,7 +326,7 @@ SIPCall::answer()
     auto& account = getSIPAccount();
 
     if (not inv)
-        throw VoipLinkException("No invite session for this call");
+        throw VoipLinkException("[call:" + getCallId() + "] answer: no invite session for this call");
 
     if (!inv->neg) {
         RING_WARN("[call:%s] Negotiator is NULL, we've received an INVITE without an SDP",
@@ -372,7 +372,7 @@ SIPCall::hangup(int reason)
 
     if (not inv or not inv->dlg) {
         removeCall();
-        throw VoipLinkException("No invite session for this call");
+        throw VoipLinkException("[call:" + getCallId() + "] hangup: no invite session for this call");
     }
 
     pjsip_route_hdr *route = inv->dlg->route_set.next;
@@ -653,10 +653,11 @@ SIPCall::peerHungup()
     // Stop all RTP streams
     stopAllMedia();
 
-    if (not inv)
-        throw VoipLinkException("No invite session for this call");
+    if (inv)
+        terminateSipSession(PJSIP_SC_NOT_FOUND);
+    else
+        RING_ERR("[call:%s] peerHungup: no invite session for this call", getCallId().c_str());
 
-    terminateSipSession(PJSIP_SC_NOT_FOUND);
     Call::peerHungup();
 }
 
@@ -668,16 +669,20 @@ SIPCall::carryingDTMFdigits(char code)
 
 void
 SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages,
-                         const std::string& /* from */)
+                         const std::string& from)
 {
-    if (not inv)
-        throw VoipLinkException("No invite session for this call");
-
     //TODO: for now we ignore the "from" (the previous implementation for sending this info was
     //      buggy and verbose), another way to send the original message sender will be implemented
     //      in the future
-
-    im::sendSipMessage(inv.get(), messages);
+    if (inv)
+        im::sendSipMessage(inv.get(), messages);
+    else {
+        if (not subcalls.empty()) {
+            for (auto& c : subcalls)
+                c->sendTextMessage(messages, from);
+        } else
+            throw VoipLinkException("[call:" + getCallId() + "] sendTextMessage: no invite session for this call");
+    }
 }
 
 void
@@ -781,8 +786,8 @@ bool
 SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const
 {
 #ifdef RING_VIDEO
-    if (videortp_.isSending())
-        return videortp_.useCodec(codec);
+    if (videortp_->isSending())
+        return videortp_->useCodec(codec);
 #endif
     return false;
 }
@@ -790,6 +795,7 @@ SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const
 void
 SIPCall::startAllMedia()
 {
+    RING_WARN("[call:%s] startAllMedia()", getCallId().c_str());
     if (isSecure() && not transport_->isSecure()) {
         RING_ERR("[call:%s] Can't perform secure call over insecure SIP transport",
                  getCallId().c_str());
@@ -815,7 +821,7 @@ SIPCall::startAllMedia()
         RtpSession* rtp = local.type == MEDIA_AUDIO
             ? static_cast<RtpSession*>(avformatrtp_.get())
 #ifdef RING_VIDEO
-            : static_cast<RtpSession*>(&videortp_);
+            : static_cast<RtpSession*>(videortp_.get());
 #else
             : nullptr;
 #endif
@@ -844,7 +850,7 @@ SIPCall::startAllMedia()
 
 #ifdef RING_VIDEO
         if (local.type == MEDIA_VIDEO)
-            videortp_.switchInput(videoInput_);
+            videortp_->switchInput(videoInput_);
 #endif
 
         rtp->updateMedia(remote, local);
@@ -873,7 +879,7 @@ SIPCall::startAllMedia()
         }
     }
 
-    if (peerHolding_ != peer_holding) {
+    if (not quiet and peerHolding_ != peer_holding) {
         peerHolding_ = peer_holding;
         emitSignal<DRing::CallSignal::PeerHold>(getCallId(), peerHolding_);
     }
@@ -885,7 +891,7 @@ SIPCall::restartMediaSender()
     RING_DBG("[call:%s] restarting TX media streams", getCallId().c_str());
     avformatrtp_->restartSender();
 #ifdef RING_VIDEO
-    videortp_.restartSender();
+    videortp_->restartSender();
 #endif
 }
 
@@ -895,7 +901,7 @@ SIPCall::restartMediaReceiver()
     RING_DBG("[call:%s] restarting RX media streams", getCallId().c_str());
     avformatrtp_->restartReceiver();
 #ifdef RING_VIDEO
-    videortp_.restartReceiver();
+    videortp_->restartReceiver();
 #endif
 }
 
@@ -905,7 +911,7 @@ SIPCall::stopAllMedia()
     RING_DBG("[call:%s] stopping all medias", getCallId().c_str());
     avformatrtp_->stop();
 #ifdef RING_VIDEO
-    videortp_.stop();
+    videortp_->stop();
 #endif
 }
 
@@ -919,47 +925,61 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute)
         isVideoMuted_ = mute;
         videoInput_ = isVideoMuted_ ? "" : Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice();
         DRing::switchInput(getCallId(), videoInput_);
-        emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), isVideoMuted_);
+        if (not quiet)
+            emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), isVideoMuted_);
 #endif
     } else if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_AUDIO) == 0) {
         if (mute == isAudioMuted_) return;
         RING_WARN("[call:%s] audio muting %s", getCallId().c_str(), bool_to_str(mute));
         isAudioMuted_ = mute;
         avformatrtp_->setMuted(isAudioMuted_);
-        emitSignal<DRing::CallSignal::AudioMuted>(getCallId(), isAudioMuted_);
+        if (not quiet)
+            emitSignal<DRing::CallSignal::AudioMuted>(getCallId(), isAudioMuted_);
     }
 }
 
 void
 SIPCall::onMediaUpdate()
 {
+    RING_WARN("[call:%s] onMediaUpdate", getCallId().c_str());
     stopAllMedia();
     openPortsUPnP();
-
     if (startIce()) {
-        auto this_ = std::static_pointer_cast<SIPCall>(shared_from_this());
-        auto ice = iceTransport_;
-        auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10);
-        Manager::instance().addTask([=] {
-            if (ice != this_->iceTransport_) {
-                RING_WARN("[call:%s] ICE transport replaced", getCallId().c_str());
+        if (not quiet)
+            waitForIceAndStartMedia();
+    } else {
+        RING_WARN("[call:%s] ICE not used for media", getCallId().c_str());
+        startAllMedia();
+    }
+}
+
+void
+SIPCall::waitForIceAndStartMedia()
+{
+    auto ice = iceTransport_;
+    auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10);
+    if (not wthis_)
+        wthis_ = std::make_shared<std::weak_ptr<SIPCall>>(std::static_pointer_cast<SIPCall>(shared_from_this()));
+    auto wthis = wthis_;
+    Manager::instance().addTask([wthis,ice,iceTimeout] {
+        if (auto sthis = wthis->lock()) {
+            auto& this_ = *sthis;
+            if (ice != this_.iceTransport_) {
+                RING_WARN("[call:%s] ICE transport replaced", this_.getCallId().c_str());
                 return false;
             }
             /* First step: wait for an ICE transport for SIP channel */
-            if (this_->iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) {
-                RING_DBG("[call:%s] ICE init failed (or timeout)", getCallId().c_str());
-                this_->onFailure(ETIMEDOUT);
+            if (this_.iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) {
+                RING_DBG("[call:%s] ICE init failed (or timeout)", this_.getCallId().c_str());
+                this_.onFailure(ETIMEDOUT);
                 return false;
             }
-            if (not this_->iceTransport_->isRunning())
+            if (not this_.iceTransport_->isRunning())
                 return true;
-            startAllMedia();
-            return false;
-        });
-    } else {
-        RING_WARN("[call:%s] ICE not used for media", getCallId().c_str());
-        startAllMedia();
-    }
+            this_.startAllMedia();
+        }
+        return false;
+    });
 }
 
 void
@@ -1086,4 +1106,27 @@ SIPCall::initIceTransport(bool master, unsigned channel_num)
     return result;
 }
 
+void
+SIPCall::merge(std::shared_ptr<SIPCall> scall)
+{
+    Call::merge(scall);
+    RING_WARN("SIPCall::merge %s -> %s", scall->getCallId().c_str(), getCallId().c_str());
+    inv = std::move(scall->inv);
+    inv->mod_data[getSIPVoIPLink()->getModId()] = this;
+    if (not wthis_)
+        wthis_ = std::make_shared<std::weak_ptr<SIPCall>>(std::static_pointer_cast<SIPCall>(shared_from_this()));
+    if (not scall->wthis_)
+        scall->wthis_  = wthis_;
+    else
+        *scall->wthis_ = *wthis_;
+    setTransport(scall->transport_);
+    sdp_ = std::move(scall->sdp_);
+    peerHolding_ = scall->peerHolding_;
+    upnp_ = std::move(scall->upnp_);
+    std::copy_n(scall->contactBuffer_, PJSIP_MAX_URL_SIZE, contactBuffer_);
+    pj_strcpy(&contactHeader_, &scall->contactHeader_);
+    if (iceTransport_->isStarted())
+        waitForIceAndStartMedia();
+}
+
 } // namespace ring
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 2448dba06e3351aa7e1343adc38943b59de012b7..3319d9777113246d76a08df51c29cdd83dda4df4 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -103,7 +103,7 @@ class SIPCall : public Call
          * Returns a pointer to the VideoRtp object
          */
         video::VideoRtpSession& getVideoRtp () {
-            return videortp_;
+            return *videortp_;
         }
 #endif
 
@@ -212,9 +212,17 @@ class SIPCall : public Call
         bool initIceTransport(bool master, unsigned channel_num=4) override;
 
         void terminateSipSession(int status);
+
+        virtual void merge(std::shared_ptr<Call> scall) {
+            merge(std::dynamic_pointer_cast<SIPCall>(scall));
+        }
+        virtual void merge(std::shared_ptr<SIPCall> scall);
+
     private:
         NON_COPYABLE(SIPCall);
 
+        void waitForIceAndStartMedia();
+
         void stopAllMedia();
 
         /**
@@ -234,7 +242,7 @@ class SIPCall : public Call
         /**
          * Video Rtp Session factory
          */
-        video::VideoRtpSession videortp_;
+        std::unique_ptr<video::VideoRtpSession> videortp_;
 
         std::string videoInput_;
 #endif
@@ -258,6 +266,8 @@ class SIPCall : public Call
         pj_str_t contactHeader_ {contactBuffer_, 0};
 
         std::unique_ptr<ring::upnp::Controller> upnp_;
+
+        std::shared_ptr<std::weak_ptr<SIPCall>> wthis_;
 };
 
 } // namespace ring