use xmtp_id::associations::{ builder::SignatureRequest, unsigned_actions::{UnsignedAction, UnsignedRevokeAssociation},};// Create unsigned revocationlet unsigned_revocation = UnsignedRevokeAssociation { member_to_revoke: installation_id.into(),};// Create signature requestlet signature_request = SignatureRequest::new( inbox_id.clone(), UnsignedAction::RevokeAssociation(unsigned_revocation),);// Get the text that needs to be signedlet signature_text = signature_request.signature_text();
The revoked member is removed from the AssociationState
The member can no longer sign new identity updates
The member cannot be used to validate new associations
impl IdentityAction for RevokeAssociation { fn update_state( &self, existing_state: Option<AssociationState>, ) -> Result<AssociationState, AssociationError> { let state = existing_state.ok_or(AssociationError::NotCreated)?; // Remove the member Ok(state.remove(&self.member_to_revoke)) }}
XMTP does not detect or automatically remove inactive clients. Inactive client detection must be handled at the application layer, which should then trigger explicit removal.
// Scenario: User lost their phone and wants to revoke that installationlet lost_installation_id = vec![/* installation public key */];let revocation = client .revoke_installation(lost_installation_id) .await?;println!("Installation revoked successfully");
// Scenario: User wants to disassociate a wallet from their inboxlet wallet_address = "0x1234...";let revocation = client .revoke_wallet(wallet_address) .await?;println!("Wallet revoked successfully");
No. Revocations must be signed by the recovery identifier (wallet), not by the installation being revoked.This prevents compromised installations from revoking themselves to cover their tracks.
While not a revocation per se, changing the recovery identifier affects who can perform revocations:
let change_recovery = UnsignedAction::ChangeRecoveryAddress { new_recovery_identifier: new_wallet_identifier,};// Must be signed by current recovery identifierlet signature_request = SignatureRequest::new(inbox_id, change_recovery);
Changing the recovery identifier is a sensitive operation. If the current recovery identifier is compromised, the attacker could change it to lock out the legitimate owner.
The XMTP backend can hide revocations by not returning them in identity update queries.This is an inherent trust assumption in the current architecture. Clients cannot independently verify that they have received all revocations.
Mitigation:Clients should:
Query identity updates from multiple sources if available
#[xmtp_common::test(unwrap_try = true)]async fn test_revoke_installation() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; // Create second installation let installation_2 = client.add_installation().await?; // Revoke it let revoke_action = Action::RevokeAssociation(RevokeAssociation { member_to_revoke: installation_2.into(), }); client.publish_identity_update(revoke_action).await?; // Verify state let state = client.get_association_state().await?; assert!(!state.installation_ids().contains(&installation_2));}