diff --git a/contracts/0.2.0/chatroom-slim.js b/contracts/0.2.0/chatroom-slim.js
index 7724d4c00a..09522c22b4 100644
--- a/contracts/0.2.0/chatroom-slim.js
+++ b/contracts/0.2.0/chatroom-slim.js
@@ -7090,8 +7090,8 @@ ${this.getErrorInfo()}`;
   });
 
   // shared/domains/chelonia/utils.js
-  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight === void 0)?.id;
-  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight === void 0 && k.foreignKey?.includes(contractID)).map((k) => k.id);
+  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight == null)?.id;
+  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight == null && k.foreignKey?.includes(contractID)).map((k) => k.id);
 
   // frontend/model/contracts/shared/constants.js
   var CHATROOM_NAME_LIMITS_IN_CHARS = 50;
@@ -7500,11 +7500,12 @@ ${this.getErrorInfo()}`;
           username: optional(string),
           member: string
         }),
-        process({ data, meta, hash, id }, { state }) {
+        process({ data, meta, hash, id, contractID }, { state }) {
           const { member } = data;
           const isKicked = data.username && member !== data.username;
           if (!state.onlyRenderMessage && !state.users[member]) {
-            throw new Error(`Can not leave the chatroom which ${member} is not part of`);
+            console.warn(`Can not leave the chatroom ${contractID} which ${member} is not part of`);
+            return;
           }
           import_common.Vue.delete(state.users, member);
           if (!state.onlyRenderMessage || state.attributes.type === CHATROOM_TYPES.DIRECT_MESSAGE) {
diff --git a/contracts/0.2.0/chatroom.0.2.0.manifest.json b/contracts/0.2.0/chatroom.0.2.0.manifest.json
index 741733921b..11509a6301 100644
--- a/contracts/0.2.0/chatroom.0.2.0.manifest.json
+++ b/contracts/0.2.0/chatroom.0.2.0.manifest.json
@@ -1 +1 @@
-{"head":{"manifestVersion":"1.0.0"},"body":"{\"version\":\"0.2.0\",\"contract\":{\"hash\":\"21XWnNP5tqDKeAYvG6eryALLv7QyKVsooZr42xvVpGKYgZE4ya\",\"file\":\"chatroom.js\"},\"authors\":[{\"cipher\":\"algo\",\"key\":\"<pubkey from deploy-key.json>\"},{\"cipher\":\"algo\",\"key\":\"<pubkey from alex.json>\"}],\"contractSlim\":{\"file\":\"chatroom-slim.js\",\"hash\":\"21XWnNMFtepu694mgggFzcQTfVJHeZLrA6yCaQEs3EypSeC85T\"}}","signature":{"key":"<which of the 'authors' keys was used to sign 'body'>","signature":"<signature>"}}
\ No newline at end of file
+{"head":{"manifestVersion":"1.0.0"},"body":"{\"version\":\"0.2.0\",\"contract\":{\"hash\":\"21XWnNMSuCrnYWd5k4rfgiER52edE4n4ZsMuMxxgLB8tEXRfpQ\",\"file\":\"chatroom.js\"},\"authors\":[{\"cipher\":\"algo\",\"key\":\"<pubkey from deploy-key.json>\"},{\"cipher\":\"algo\",\"key\":\"<pubkey from alex.json>\"}],\"contractSlim\":{\"file\":\"chatroom-slim.js\",\"hash\":\"21XWnNTNdo9ejZSdvNfCFHnh3kQPqNTXesNScNcXaezGTfJfYF\"}}","signature":{"key":"<which of the 'authors' keys was used to sign 'body'>","signature":"<signature>"}}
\ No newline at end of file
diff --git a/contracts/0.2.0/chatroom.js b/contracts/0.2.0/chatroom.js
index 42e317f39b..ed1df256d5 100644
--- a/contracts/0.2.0/chatroom.js
+++ b/contracts/0.2.0/chatroom.js
@@ -16184,8 +16184,8 @@ ${this.getErrorInfo()}`;
   });
 
   // shared/domains/chelonia/utils.js
-  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight === void 0)?.id;
-  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight === void 0 && k.foreignKey?.includes(contractID)).map((k) => k.id);
+  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight == null)?.id;
+  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight == null && k.foreignKey?.includes(contractID)).map((k) => k.id);
 
   // frontend/model/contracts/shared/constants.js
   var CHATROOM_NAME_LIMITS_IN_CHARS = 50;
@@ -16594,11 +16594,12 @@ ${this.getErrorInfo()}`;
           username: optional(string),
           member: string
         }),
-        process({ data, meta, hash: hash2, id }, { state }) {
+        process({ data, meta, hash: hash2, id, contractID }, { state }) {
           const { member } = data;
           const isKicked = data.username && member !== data.username;
           if (!state.onlyRenderMessage && !state.users[member]) {
-            throw new Error(`Can not leave the chatroom which ${member} is not part of`);
+            console.warn(`Can not leave the chatroom ${contractID} which ${member} is not part of`);
+            return;
           }
           vue_esm_default.delete(state.users, member);
           if (!state.onlyRenderMessage || state.attributes.type === CHATROOM_TYPES.DIRECT_MESSAGE) {
diff --git a/contracts/0.2.0/group-slim.js b/contracts/0.2.0/group-slim.js
index c04c3c3645..81e95c38fd 100644
--- a/contracts/0.2.0/group-slim.js
+++ b/contracts/0.2.0/group-slim.js
@@ -7721,8 +7721,8 @@ ${this.getErrorInfo()}`;
   });
 
   // shared/domains/chelonia/utils.js
-  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight === void 0)?.id;
-  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight === void 0 && k.foreignKey?.includes(contractID)).map((k) => k.id);
+  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight == null)?.id;
+  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight == null && k.foreignKey?.includes(contractID)).map((k) => k.id);
 
   // frontend/model/notifications/mutationKeys.js
   var REMOVE_NOTIFICATION = "removeNotification";
@@ -8424,7 +8424,7 @@ ${this.getErrorInfo()}`;
               }
             }).catch((e) => {
               console.error(`sideEffect(removeMember): ${e.name} thrown during queueEvent to ${contractID} by saveOurLoginState:`, e);
-            }).then(() => (0, import_sbp6.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID)).catch((e) => {
+            }).then(() => (0, import_sbp6.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID, true)).catch((e) => {
               console.error(`sideEffect(removeMember): ${e.name} thrown during revokeGroupKeyAndRotateOurPEK to ${contractID}:`, e);
             });
             for (const notification of rootGetters.notificationsByGroup(contractID)) {
@@ -8439,7 +8439,11 @@ ${this.getErrorInfo()}`;
                 groupID: contractID,
                 username: memberRemovedThemselves ? meta.username : data.member
               });
-              (0, import_sbp6.default)("gi.contracts/group/rotateKeys", contractID, state);
+              (0, import_sbp6.default)("gi.contracts/group/rotateKeys", contractID, state).then(() => {
+                return (0, import_sbp6.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID, false);
+              }).catch((e) => {
+                console.error("Error rotating group keys or our PEK", e);
+              });
               const rootGetters2 = (0, import_sbp6.default)("state/vuex/getters");
               const userID = rootGetters2.ourContactProfiles[data.member]?.contractID;
               if (userID) {
@@ -8760,8 +8764,8 @@ ${this.getErrorInfo()}`;
     },
     methods: {
       "gi.contracts/group/archiveProposal": async function(contractID, proposalHash, proposal) {
-        const { username } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
-        const key = `proposals/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
+        const key = `proposals/${identityContractID}/${contractID}`;
         const proposals2 = await (0, import_sbp6.default)("gi.db/archive/load", key) || [];
         proposals2.unshift([proposalHash, proposal]);
         while (proposals2.length > MAX_ARCHIVED_PROPOSALS) {
@@ -8772,10 +8776,10 @@ ${this.getErrorInfo()}`;
       },
       "gi.contracts/group/archivePayments": async function(contractID, archivingPayments) {
         const { paymentsByPeriod, payments } = archivingPayments;
-        const { username } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
-        const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`;
+        const { identityContractID, username } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
+        const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`;
         const archPaymentsByPeriod = await (0, import_sbp6.default)("gi.db/archive/load", archPaymentsByPeriodKey) || {};
-        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`;
+        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`;
         const archSentOrReceivedPayments = await (0, import_sbp6.default)("gi.db/archive/load", archSentOrReceivedPaymentsKey) || { sent: [], received: [] };
         const sortPayments = (payments2) => payments2.sort((f, l) => compareISOTimestamps(l.meta.createdDate, f.meta.createdDate));
         for (const period of Object.keys(paymentsByPeriod).sort()) {
@@ -8795,13 +8799,13 @@ ${this.getErrorInfo()}`;
           }
           archSentOrReceivedPayments.sent = [...sortPayments(newSentOrReceivedPayments.sent), ...archSentOrReceivedPayments.sent];
           archSentOrReceivedPayments.received = [...sortPayments(newSentOrReceivedPayments.received), ...archSentOrReceivedPayments.received];
-          const archPaymentsKey = `payments/${username}/${period}/${contractID}`;
+          const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`;
           const hashes = paymentHashesFromPaymentPeriod(paymentsByPeriod[period]);
           const archPayments = Object.fromEntries(hashes.map((hash) => [hash, payments[hash]]));
           while (Object.keys(archPaymentsByPeriod).length > MAX_ARCHIVED_PERIODS) {
             const shouldBeDeletedPeriod = Object.keys(archPaymentsByPeriod).sort().shift();
             const paymentHashes = paymentHashesFromPaymentPeriod(archPaymentsByPeriod[shouldBeDeletedPeriod]);
-            await (0, import_sbp6.default)("gi.db/archive/delete", `payments/${shouldBeDeletedPeriod}/${username}/${contractID}`);
+            await (0, import_sbp6.default)("gi.db/archive/delete", `payments/${shouldBeDeletedPeriod}/${identityContractID}/${contractID}`);
             delete archPaymentsByPeriod[shouldBeDeletedPeriod];
             archSentOrReceivedPayments.sent = archSentOrReceivedPayments.sent.filter((payment) => !paymentHashes.includes(payment.hash));
             archSentOrReceivedPayments.received = archSentOrReceivedPayments.received.filter((payment) => !paymentHashes.includes(payment.hash));
@@ -8813,17 +8817,17 @@ ${this.getErrorInfo()}`;
         (0, import_sbp6.default)("okTurtles.events/emit", PAYMENTS_ARCHIVED, { paymentsByPeriod, payments });
       },
       "gi.contracts/group/removeArchivedProposals": async function(contractID) {
-        const { username } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
-        const key = `proposals/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
+        const key = `proposals/${identityContractID}/${contractID}`;
         await (0, import_sbp6.default)("gi.db/archive/delete", key);
       },
       "gi.contracts/group/removeArchivedPayments": async function(contractID) {
-        const { username } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
-        const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp6.default)("state/vuex/state").loggedIn;
+        const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`;
         const periods = Object.keys(await (0, import_sbp6.default)("gi.db/archive/load", archPaymentsByPeriodKey) || {});
-        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`;
+        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`;
         for (const period of periods) {
-          const archPaymentsKey = `payments/${username}/${period}/${contractID}`;
+          const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`;
           await (0, import_sbp6.default)("gi.db/archive/delete", archPaymentsKey);
         }
         await (0, import_sbp6.default)("gi.db/archive/delete", archPaymentsByPeriodKey);
@@ -8873,11 +8877,11 @@ ${this.getErrorInfo()}`;
         const CEKid = findKeyIdByName(state, "cek");
         import_common3.Vue.set(state._volatile.pendingKeyRevocations, CSKid, true);
         import_common3.Vue.set(state._volatile.pendingKeyRevocations, CEKid, true);
-        (0, import_sbp6.default)("chelonia/queueInvocation", contractID, ["gi.actions/out/rotateKeys", contractID, "gi.contracts/group", "pending", "gi.actions/group/shareNewKeys"]).catch((e) => {
+        return (0, import_sbp6.default)("chelonia/queueInvocation", contractID, ["gi.actions/out/rotateKeys", contractID, "gi.contracts/group", "pending", "gi.actions/group/shareNewKeys"]).catch((e) => {
           console.warn(`rotateKeys: ${e.name} thrown during queueEvent to ${contractID}:`, e);
         });
       },
-      "gi.contracts/group/revokeGroupKeyAndRotateOurPEK": (groupContractID) => {
+      "gi.contracts/group/revokeGroupKeyAndRotateOurPEK": (groupContractID, disconnectGroup) => {
         const rootState = (0, import_sbp6.default)("state/vuex/state");
         const { identityContractID } = rootState.loggedIn;
         const state = rootState[identityContractID];
@@ -8886,25 +8890,27 @@ ${this.getErrorInfo()}`;
         const CSKid = findKeyIdByName(state, "csk");
         const CEKid = findKeyIdByName(state, "cek");
         const PEKid = findKeyIdByName(state, "pek");
-        const groupCSKids = findForeignKeysByContractID(state, groupContractID);
         import_common3.Vue.set(state._volatile.pendingKeyRevocations, PEKid, true);
-        if (groupCSKids?.length) {
-          if (!CEKid) {
-            throw new Error("Identity CEK not found");
-          }
-          (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["chelonia/out/keyDel", {
-            contractID: identityContractID,
-            contractName: "gi.contracts/identity",
-            data: groupCSKids,
-            signingKeyId: CSKid
-          }]).catch((e) => {
-            console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e);
+        if (disconnectGroup) {
+          const groupCSKids = findForeignKeysByContractID(state, groupContractID);
+          if (groupCSKids?.length) {
+            if (!CEKid) {
+              throw new Error("Identity CEK not found");
+            }
+            (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["chelonia/out/keyDel", {
+              contractID: identityContractID,
+              contractName: "gi.contracts/identity",
+              data: groupCSKids,
+              signingKeyId: CSKid
+            }]).catch((e) => {
+              console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e);
+            });
+          }
+          (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["chelonia/contract/disconnect", identityContractID, groupContractID]).catch((e) => {
+            console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
           });
         }
-        (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["chelonia/contract/disconnect", identityContractID, groupContractID]).catch((e) => {
-          console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
-        });
-        (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["gi.actions/out/rotateKeys", identityContractID, "gi.contracts/identity", "pending", "gi.actions/identity/shareNewPEK"]).catch((e) => {
+        return (0, import_sbp6.default)("chelonia/queueInvocation", identityContractID, ["gi.actions/out/rotateKeys", identityContractID, "gi.contracts/identity", "pending", "gi.actions/identity/shareNewPEK"]).catch((e) => {
           console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
         });
       },
diff --git a/contracts/0.2.0/group.0.2.0.manifest.json b/contracts/0.2.0/group.0.2.0.manifest.json
index be4ee6837f..dd9e3824ae 100644
--- a/contracts/0.2.0/group.0.2.0.manifest.json
+++ b/contracts/0.2.0/group.0.2.0.manifest.json
@@ -1 +1 @@
-{"head":{"manifestVersion":"1.0.0"},"body":"{\"version\":\"0.2.0\",\"contract\":{\"hash\":\"21XWnNL4g66rNK5wkSH9vZD6H6ZDJXJ84ecvCzPkBoaiCHkgmM\",\"file\":\"group.js\"},\"authors\":[{\"cipher\":\"algo\",\"key\":\"<pubkey from deploy-key.json>\"},{\"cipher\":\"algo\",\"key\":\"<pubkey from alex.json>\"}],\"contractSlim\":{\"file\":\"group-slim.js\",\"hash\":\"21XWnNMhWKeNnBG1upPDBdvrKWuERWGaKdNpCUcyfQ7Bj6QG3y\"}}","signature":{"key":"<which of the 'authors' keys was used to sign 'body'>","signature":"<signature>"}}
\ No newline at end of file
+{"head":{"manifestVersion":"1.0.0"},"body":"{\"version\":\"0.2.0\",\"contract\":{\"hash\":\"21XWnNVqB5fYNAa2mufrsDVWZUsdeW82snkcx4pa4yWjvfTk3c\",\"file\":\"group.js\"},\"authors\":[{\"cipher\":\"algo\",\"key\":\"<pubkey from deploy-key.json>\"},{\"cipher\":\"algo\",\"key\":\"<pubkey from alex.json>\"}],\"contractSlim\":{\"file\":\"group-slim.js\",\"hash\":\"21XWnNUmvc1AqRF86c4Wk9hixK64uyG7LHkNi8iNqVs1kXKKpg\"}}","signature":{"key":"<which of the 'authors' keys was used to sign 'body'>","signature":"<signature>"}}
\ No newline at end of file
diff --git a/contracts/0.2.0/group.js b/contracts/0.2.0/group.js
index 7b0f3e2946..76337a0cbe 100644
--- a/contracts/0.2.0/group.js
+++ b/contracts/0.2.0/group.js
@@ -16842,8 +16842,8 @@ ${this.getErrorInfo()}`;
   });
 
   // shared/domains/chelonia/utils.js
-  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight === void 0)?.id;
-  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight === void 0 && k.foreignKey?.includes(contractID)).map((k) => k.id);
+  var findKeyIdByName = (state, name) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).find((k) => k.name === name && k._notAfterHeight == null)?.id;
+  var findForeignKeysByContractID = (state, contractID) => state._vm?.authorizedKeys && Object.values(state._vm.authorizedKeys).filter((k) => k._notAfterHeight == null && k.foreignKey?.includes(contractID)).map((k) => k.id);
 
   // frontend/model/notifications/mutationKeys.js
   var REMOVE_NOTIFICATION = "removeNotification";
@@ -17545,7 +17545,7 @@ ${this.getErrorInfo()}`;
               }
             }).catch((e) => {
               console.error(`sideEffect(removeMember): ${e.name} thrown during queueEvent to ${contractID} by saveOurLoginState:`, e);
-            }).then(() => (0, import_sbp7.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID)).catch((e) => {
+            }).then(() => (0, import_sbp7.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID, true)).catch((e) => {
               console.error(`sideEffect(removeMember): ${e.name} thrown during revokeGroupKeyAndRotateOurPEK to ${contractID}:`, e);
             });
             for (const notification of rootGetters.notificationsByGroup(contractID)) {
@@ -17560,7 +17560,11 @@ ${this.getErrorInfo()}`;
                 groupID: contractID,
                 username: memberRemovedThemselves ? meta.username : data.member
               });
-              (0, import_sbp7.default)("gi.contracts/group/rotateKeys", contractID, state);
+              (0, import_sbp7.default)("gi.contracts/group/rotateKeys", contractID, state).then(() => {
+                return (0, import_sbp7.default)("gi.contracts/group/revokeGroupKeyAndRotateOurPEK", contractID, false);
+              }).catch((e) => {
+                console.error("Error rotating group keys or our PEK", e);
+              });
               const rootGetters2 = (0, import_sbp7.default)("state/vuex/getters");
               const userID = rootGetters2.ourContactProfiles[data.member]?.contractID;
               if (userID) {
@@ -17881,8 +17885,8 @@ ${this.getErrorInfo()}`;
     },
     methods: {
       "gi.contracts/group/archiveProposal": async function(contractID, proposalHash, proposal) {
-        const { username } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
-        const key = `proposals/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
+        const key = `proposals/${identityContractID}/${contractID}`;
         const proposals2 = await (0, import_sbp7.default)("gi.db/archive/load", key) || [];
         proposals2.unshift([proposalHash, proposal]);
         while (proposals2.length > MAX_ARCHIVED_PROPOSALS) {
@@ -17893,10 +17897,10 @@ ${this.getErrorInfo()}`;
       },
       "gi.contracts/group/archivePayments": async function(contractID, archivingPayments) {
         const { paymentsByPeriod, payments } = archivingPayments;
-        const { username } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
-        const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`;
+        const { identityContractID, username } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
+        const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`;
         const archPaymentsByPeriod = await (0, import_sbp7.default)("gi.db/archive/load", archPaymentsByPeriodKey) || {};
-        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`;
+        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`;
         const archSentOrReceivedPayments = await (0, import_sbp7.default)("gi.db/archive/load", archSentOrReceivedPaymentsKey) || { sent: [], received: [] };
         const sortPayments = (payments2) => payments2.sort((f, l) => compareISOTimestamps(l.meta.createdDate, f.meta.createdDate));
         for (const period of Object.keys(paymentsByPeriod).sort()) {
@@ -17916,13 +17920,13 @@ ${this.getErrorInfo()}`;
           }
           archSentOrReceivedPayments.sent = [...sortPayments(newSentOrReceivedPayments.sent), ...archSentOrReceivedPayments.sent];
           archSentOrReceivedPayments.received = [...sortPayments(newSentOrReceivedPayments.received), ...archSentOrReceivedPayments.received];
-          const archPaymentsKey = `payments/${username}/${period}/${contractID}`;
+          const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`;
           const hashes = paymentHashesFromPaymentPeriod(paymentsByPeriod[period]);
           const archPayments = Object.fromEntries(hashes.map((hash2) => [hash2, payments[hash2]]));
           while (Object.keys(archPaymentsByPeriod).length > MAX_ARCHIVED_PERIODS) {
             const shouldBeDeletedPeriod = Object.keys(archPaymentsByPeriod).sort().shift();
             const paymentHashes = paymentHashesFromPaymentPeriod(archPaymentsByPeriod[shouldBeDeletedPeriod]);
-            await (0, import_sbp7.default)("gi.db/archive/delete", `payments/${shouldBeDeletedPeriod}/${username}/${contractID}`);
+            await (0, import_sbp7.default)("gi.db/archive/delete", `payments/${shouldBeDeletedPeriod}/${identityContractID}/${contractID}`);
             delete archPaymentsByPeriod[shouldBeDeletedPeriod];
             archSentOrReceivedPayments.sent = archSentOrReceivedPayments.sent.filter((payment) => !paymentHashes.includes(payment.hash));
             archSentOrReceivedPayments.received = archSentOrReceivedPayments.received.filter((payment) => !paymentHashes.includes(payment.hash));
@@ -17934,17 +17938,17 @@ ${this.getErrorInfo()}`;
         (0, import_sbp7.default)("okTurtles.events/emit", PAYMENTS_ARCHIVED, { paymentsByPeriod, payments });
       },
       "gi.contracts/group/removeArchivedProposals": async function(contractID) {
-        const { username } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
-        const key = `proposals/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
+        const key = `proposals/${identityContractID}/${contractID}`;
         await (0, import_sbp7.default)("gi.db/archive/delete", key);
       },
       "gi.contracts/group/removeArchivedPayments": async function(contractID) {
-        const { username } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
-        const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`;
+        const { identityContractID } = (0, import_sbp7.default)("state/vuex/state").loggedIn;
+        const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`;
         const periods = Object.keys(await (0, import_sbp7.default)("gi.db/archive/load", archPaymentsByPeriodKey) || {});
-        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`;
+        const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`;
         for (const period of periods) {
-          const archPaymentsKey = `payments/${username}/${period}/${contractID}`;
+          const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`;
           await (0, import_sbp7.default)("gi.db/archive/delete", archPaymentsKey);
         }
         await (0, import_sbp7.default)("gi.db/archive/delete", archPaymentsByPeriodKey);
@@ -17994,11 +17998,11 @@ ${this.getErrorInfo()}`;
         const CEKid = findKeyIdByName(state, "cek");
         vue_esm_default.set(state._volatile.pendingKeyRevocations, CSKid, true);
         vue_esm_default.set(state._volatile.pendingKeyRevocations, CEKid, true);
-        (0, import_sbp7.default)("chelonia/queueInvocation", contractID, ["gi.actions/out/rotateKeys", contractID, "gi.contracts/group", "pending", "gi.actions/group/shareNewKeys"]).catch((e) => {
+        return (0, import_sbp7.default)("chelonia/queueInvocation", contractID, ["gi.actions/out/rotateKeys", contractID, "gi.contracts/group", "pending", "gi.actions/group/shareNewKeys"]).catch((e) => {
           console.warn(`rotateKeys: ${e.name} thrown during queueEvent to ${contractID}:`, e);
         });
       },
-      "gi.contracts/group/revokeGroupKeyAndRotateOurPEK": (groupContractID) => {
+      "gi.contracts/group/revokeGroupKeyAndRotateOurPEK": (groupContractID, disconnectGroup) => {
         const rootState = (0, import_sbp7.default)("state/vuex/state");
         const { identityContractID } = rootState.loggedIn;
         const state = rootState[identityContractID];
@@ -18007,25 +18011,27 @@ ${this.getErrorInfo()}`;
         const CSKid = findKeyIdByName(state, "csk");
         const CEKid = findKeyIdByName(state, "cek");
         const PEKid = findKeyIdByName(state, "pek");
-        const groupCSKids = findForeignKeysByContractID(state, groupContractID);
         vue_esm_default.set(state._volatile.pendingKeyRevocations, PEKid, true);
-        if (groupCSKids?.length) {
-          if (!CEKid) {
-            throw new Error("Identity CEK not found");
-          }
-          (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["chelonia/out/keyDel", {
-            contractID: identityContractID,
-            contractName: "gi.contracts/identity",
-            data: groupCSKids,
-            signingKeyId: CSKid
-          }]).catch((e) => {
-            console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e);
+        if (disconnectGroup) {
+          const groupCSKids = findForeignKeysByContractID(state, groupContractID);
+          if (groupCSKids?.length) {
+            if (!CEKid) {
+              throw new Error("Identity CEK not found");
+            }
+            (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["chelonia/out/keyDel", {
+              contractID: identityContractID,
+              contractName: "gi.contracts/identity",
+              data: groupCSKids,
+              signingKeyId: CSKid
+            }]).catch((e) => {
+              console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e);
+            });
+          }
+          (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["chelonia/contract/disconnect", identityContractID, groupContractID]).catch((e) => {
+            console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
           });
         }
-        (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["chelonia/contract/disconnect", identityContractID, groupContractID]).catch((e) => {
-          console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
-        });
-        (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["gi.actions/out/rotateKeys", identityContractID, "gi.contracts/identity", "pending", "gi.actions/identity/shareNewPEK"]).catch((e) => {
+        return (0, import_sbp7.default)("chelonia/queueInvocation", identityContractID, ["gi.actions/out/rotateKeys", identityContractID, "gi.contracts/identity", "pending", "gi.actions/identity/shareNewPEK"]).catch((e) => {
           console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e);
         });
       },
diff --git a/frontend/controller/actions/identity.js b/frontend/controller/actions/identity.js
index 59e85ef618..c47b5cdadc 100644
--- a/frontend/controller/actions/identity.js
+++ b/frontend/controller/actions/identity.js
@@ -389,9 +389,7 @@ export default (sbp('sbp/selectors/register', {
           )
         }))
       }
-      if (!contractIDs.includes(identityContractID)) {
-        contractIDs.push(identityContractID)
-      }
+
       await sbp('gi.db/settings/save', SETTING_CURRENT_USER, identityContractID)
 
       const loginAttributes = { identityContractID, encryptionParams, username }
@@ -407,69 +405,77 @@ export default (sbp('sbp/selectors/register', {
 
       sbp('state/vuex/commit', 'login', loginAttributes)
       await sbp('chelonia/storeSecretKeys', () => transientSecretKeys)
+
       // IMPORTANT: we avoid using 'await' on the syncs so that Vue.js can proceed
       //            loading the website instead of stalling out.
       // See the TODO note in startApp (main.js) for why this is not awaited
-      sbp('chelonia/contract/sync', contractIDs).then(async function () {
-        // contract sync might've triggered an async call to /remove, so wait before proceeding
-        await sbp('chelonia/contract/wait', contractIDs)
-        // similarly, since removeMember may have triggered saveOurLoginState asynchronously,
-        // we must re-sync our identity contract again to ensure we don't rejoin a group we
-        // were just kicked out of
-        await sbp('chelonia/contract/sync', identityContractID, { force: true })
-        await sbp('gi.actions/identity/updateLoginStateUponLogin')
-        await sbp('gi.actions/identity/saveOurLoginState') // will only update it if it's different
-
-        // The state above might be null, so we re-grab it
-        const state = sbp('state/vuex/state')
-
-        // Call 'gi.actions/group/join' on all groups which may need re-joining
-        await Promise.all(groupsToRejoin.map(groupId => {
-          return (
-            // (1) Check whether the contract exists (may have been removed
-            //     after sync)
-            state.contracts[groupId] &&
+      sbp('chelonia/contract/sync', identityContractID, { force: true })
+        .catch((err) => {
+          sbp('okTurtles.events/emit', LOGIN_ERROR, { username, identityContractID, error: err })
+          const errMessage = err?.message || String(err)
+          console.error('Error during login contract sync', errMessage)
+
+          const promptOptions = {
+            heading: L('Login error'),
+            question: L('Do you want to log out? Error details: {err}.', { err: err.message }),
+            primaryButton: L('No'),
+            secondaryButton: L('Yes')
+          }
+
+          sbp('gi.ui/prompt', promptOptions).then((result) => {
+            if (!result) {
+              sbp('gi.actions/identity/logout')
+            }
+          }).catch((e) => {
+            console.error('Error at gi.ui/prompt', e)
+          })
+
+          throw new Error('Unable to sync identity contract')
+        }).then(() =>
+          sbp('chelonia/contract/sync', contractIDs).then(async function () {
+          // contract sync might've triggered an async call to /remove, so wait before proceeding
+            await sbp('chelonia/contract/wait', contractIDs)
+            // similarly, since removeMember may have triggered saveOurLoginState asynchronously,
+            // we must re-sync our identity contract again to ensure we don't rejoin a group we
+            // were just kicked out of
+            await sbp('chelonia/contract/sync', identityContractID, { force: true })
+            await sbp('gi.actions/identity/updateLoginStateUponLogin')
+            await sbp('gi.actions/identity/saveOurLoginState') // will only update it if it's different
+
+            // The state above might be null, so we re-grab it
+            const state = sbp('state/vuex/state')
+
+            // Call 'gi.actions/group/join' on all groups which may need re-joining
+            await Promise.all(groupsToRejoin.map(groupId => {
+              return (
+              // (1) Check whether the contract exists (may have been removed
+              //     after sync)
+                state.contracts[groupId] &&
             // (2) Check whether the join process is still incomplete
             //     This needs to be re-checked because it may have changed after
             //     sync
             !state.profiles?.[username] &&
             // (3) Call join
             sbp('gi.actions/group/join', { contractID: groupId, contractName: 'gi.contracts/group' })
-          )
-        }))
-
-        // update the 'lastLoggedIn' field in user's group profiles
-        sbp('state/vuex/getters').groupsByName
-          .map(entry => entry.contractID)
-          .forEach(cId => {
-            // We send this action only for groups we have fully joined (i.e.,
-            // accepted an invite add added our profile)
-            if (state[cId]?.profiles?.[username]) {
-              sbp('gi.actions/group/updateLastLoggedIn', { contractID: cId }).catch(console.error)
-            }
+              )
+            }))
+
+            // update the 'lastLoggedIn' field in user's group profiles
+            sbp('state/vuex/getters').groupsByName
+              .map(entry => entry.contractID)
+              .forEach(cId => {
+                // We send this action only for groups we have fully joined (i.e.,
+                // accepted an invite add added our profile)
+                if (state[cId]?.profiles?.[username]) {
+                  sbp('gi.actions/group/updateLastLoggedIn', { contractID: cId }).catch(console.error)
+                }
+              })
+          }).finally(() => {
+            sbp('okTurtles.events/emit', LOGIN, { username, identityContractID })
           })
-
-        sbp('okTurtles.events/emit', LOGIN, { username, identityContractID })
-      }).catch((err) => {
-        sbp('okTurtles.events/emit', LOGIN_ERROR, { username, identityContractID, error: err })
-        const errMessage = err?.message || String(err)
-        console.error('Error during login contract sync', errMessage)
-
-        const promptOptions = {
-          heading: L('Login error'),
-          question: L('Do you want to log out? Error details: {err}.', { err: err.message }),
-          primaryButton: L('No'),
-          secondaryButton: L('Yes')
-        }
-
-        sbp('gi.ui/prompt', promptOptions).then((result) => {
-          if (!result) {
-            sbp('gi.actions/identity/logout')
-          }
-        }).catch((e) => {
-          console.error('Error at gi.ui/prompt', e)
+        ).catch((err) => {
+          console.error('Error during contract sync upon login', err)
         })
-      })
       return identityContractID
     } catch (e) {
       console.error('gi.actions/identity/login failed!', e)
diff --git a/frontend/model/contracts/chatroom.js b/frontend/model/contracts/chatroom.js
index 72bc2092a7..f6d4cd8d21 100644
--- a/frontend/model/contracts/chatroom.js
+++ b/frontend/model/contracts/chatroom.js
@@ -300,11 +300,12 @@ sbp('chelonia/defineContract', {
         username: optional(string), // coming from the gi.contracts/group/leaveChatRoom
         member: string // username to be removed
       }),
-      process ({ data, meta, hash, id }, { state }) {
+      process ({ data, meta, hash, id, contractID }, { state }) {
         const { member } = data
         const isKicked = data.username && member !== data.username
         if (!state.onlyRenderMessage && !state.users[member]) {
-          throw new Error(`Can not leave the chatroom which ${member} is not part of`)
+          console.warn(`Can not leave the chatroom ${contractID} which ${member} is not part of`)
+          return
         }
         Vue.delete(state.users, member)
 
diff --git a/frontend/model/contracts/manifests.json b/frontend/model/contracts/manifests.json
index 569d5d17ee..b633da234d 100644
--- a/frontend/model/contracts/manifests.json
+++ b/frontend/model/contracts/manifests.json
@@ -1,6 +1,6 @@
 {
   "manifests": {
-    "gi.contracts/chatroom": "21XWnNLtWByWqqxVAzC4hKTFBPeuc2WkiT72MivJcfYLizqHi1",
+    "gi.contracts/chatroom": "21XWnNJr1tLQM1XwNVBUxVCuKADSSJmXVK3DWShAEt6HJeAvD8",
     "gi.contracts/group": "21XWnNTihqK5Pwu1Fy9Bf3Eh4MN446oKu7QTSgnQD54q4R93Cy",
     "gi.contracts/identity": "21XWnNSxNrVFTMBCUVcZV3CG833gTbJ9V8ZyX7Fxgmpj9rNy4r"
   }
diff --git a/shared/domains/chelonia/chelonia.js b/shared/domains/chelonia/chelonia.js
index d9fa0402c6..f11cd58856 100644
--- a/shared/domains/chelonia/chelonia.js
+++ b/shared/domains/chelonia/chelonia.js
@@ -448,6 +448,10 @@ export default (sbp('sbp/selectors/register', {
         [`${contract.manifest}/${action}/sideEffect`]: async (mutation: Object, state: ?Object) => {
           if (contract.actions[action].sideEffect) {
             state = state || contract.state(mutation.contractID)
+            if (!state) {
+              console.warn(`[${contract.manifest}/${action}/sideEffect]: Skipping side-effect since there is no contract state for contract ${mutation.contractID}`)
+              return
+            }
             const gProxy = gettersProxy(state, contract.getters)
             await contract.actions[action].sideEffect(mutation, { state, ...gProxy })
           }
diff --git a/shared/domains/chelonia/internals.js b/shared/domains/chelonia/internals.js
index 5bac284a08..18b1362d91 100644
--- a/shared/domains/chelonia/internals.js
+++ b/shared/domains/chelonia/internals.js
@@ -666,6 +666,7 @@ export default (sbp('sbp/selectors/register', {
         if (!state._volatile.pendingKeyRevocations) config.reactiveSet(state._volatile, 'pendingKeyRevocations', Object.create(null))
         const [updatedKeys, updatedMap] = validateKeyUpdatePermissions(contractID, signingKey, state, v)
         const keysToDelete = ((Object.values(updatedMap): any): string[])
+        console.log('@@@KEY_UPDATE', cloneDeep({ contractID, updatedKeys, keysToDelete }))
         for (const keyId of keysToDelete) {
           if (has(state._volatile.pendingKeyRevocations, keyId)) {
             delete state._volatile.pendingKeyRevocations[keyId]
@@ -676,8 +677,8 @@ export default (sbp('sbp/selectors/register', {
         for (const key of updatedKeys) {
           if (!has(state._vm.authorizedKeys, key.id)) {
             key._notBeforeHeight = height
+            config.reactiveSet(state._vm.authorizedKeys, key.id, cloneDeep(key))
           }
-          config.reactiveSet(state._vm.authorizedKeys, key.id, cloneDeep(key))
         }
         keyAdditionProcessor.call(self, (updatedKeys: any), state, contractID, signingKey)