Social recovery lets a quorum of guardian DIDs replace the signing key bound to your DID, after a configurable timelock. Use it as a backup to seed-phrase storage — without it, losing a key means losing the DID.

When to use this

  • Long-lived identity DIDs that must survive key loss.
  • Treasury controller DIDs where no single human should hold sole authority.
  • Production validator DIDs where downtime from key loss is unacceptable.

Prerequisites

  • A funded DID (the one being protected).
  • 3–7 guardian DIDs whose owners you trust and can reach.

Recipe

1

Configure guardians and threshold

A threshold of M-of-N: threshold guardians must co-sign the recovery to authorise a key change.
await signAndSend({
  RecoveryConfigure: {
    protected_did: process.env.SIGIL_SENDER_DID,
    guardians: [
      'did:oas:sigil:agent:guardian1...',
      'did:oas:sigil:agent:guardian2...',
      'did:oas:sigil:agent:guardian3...',
      'did:oas:sigil:agent:guardian4...',
      'did:oas:sigil:agent:guardian5...',
    ],
    threshold: 3,                        // 3-of-5
    timelock_blocks: 14_400,             // ~24h at 6s blocks
    require_human_root_lineage: true,    // guardians must trace to humans
  },
});
The chain stores this policy keyed by protected_did. It can be changed later only with the current key or via a successful recovery.
2

Initiate a recovery (when needed)

Any guardian initiates. The proposal locks in a candidate new public key and starts the timelock.
await signAndSendAs(guardianKey, {
  RecoveryPropose: {
    protected_did: 'did:oas:sigil:agent:original...',
    new_public_key: newPublicKeyHex,
    rationale: 'Original key lost in laptop theft 2026-05-12',
  },
});
3

Collect guardian co-signatures

Each guardian inspects the proposal and signs.
await signAndSendAs(guardianKey, {
  RecoveryApprove: {
    protected_did: 'did:oas:sigil:agent:original...',
    proposal_id: 'rec:abc123...',
  },
});
Track the running approval count:
curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_getRecoveryProposal",
       "params":{"proposal_id":"rec:abc123..."}}'
Once approvals >= threshold and the timelock elapses, the proposal becomes finalisable.
4

Finalise the recovery

Anyone may finalise after the timelock. The chain rotates the public key for protected_did in one atomic step.
await signAndSend({
  RecoveryFinalize: {
    protected_did: 'did:oas:sigil:agent:original...',
    proposal_id: 'rec:abc123...',
  },
});
Subsequent transactions from protected_did are now verified against the new public key.

Cancelling a malicious recovery

The current key holder (or any guardian) may cancel an in-progress recovery before finalisation:
await signAndSend({
  RecoveryCancel: {
    proposal_id: 'rec:abc123...',
  },
});
If the rightful owner sees an unexpected recovery, they cancel from the still-valid old key. Configure your monitoring (or the wallet’s notification settings) to alert on RecoveryPropose events for your DID.

Common errors

SymptomCauseFix
not enough guardiansguardians.length < thresholdAdd more guardians
guardian not in policyApproval from a DID outside the configured setVerify the guardian DID list
timelock not elapsedTried to finalise too earlyWait for proposal.executable_at_height
proposal cancelledCancelled by owner or guardianRe-initiate if recovery is still needed
lineage requirement unsatisfiedGuardian without human-root lineage when require_human_root_lineage: trueUse guardians with valid HMR lineage or relax the requirement

Best practices

  • Geographically and operationally diversify guardians — different jurisdictions, different organisations, different key custody methods.
  • Keep threshold ≥ 3 even for small guardian sets to defend against a single compromise.
  • Set timelock_blocks long enough to detect a malicious recovery (≥ 24h on testnet, ≥ 72h on mainnet).
  • Test the flow end-to-end on testnet before relying on it.

See also