Skip to main content

Social Recovery with Smart Accounts

Social recovery provides a secure way to recover your smart account if you lose access to your primary wallet. Instead of relying on a single seed phrase, you can designate trusted guardians who can help recover your account.

Overview

Social recovery leverages the Rhinestone Social Recovery Module (ERC-7579 compatible) to:

  • Guardian Setup: Designate trusted friends, family, or services as guardians
  • Threshold Recovery: Require multiple guardians to approve recovery (e.g., 2 of 3)
  • Secure Process: Multi-step recovery process prevents unauthorized access
  • Module-Based: Easily install/uninstall without affecting other account features

Installation & Setup

The social recovery module is automatically available through the Rhinestone Module SDK:

hooks/useSocialRecovery.ts
import { useState, useEffect } from 'react'
import { getModule } from '@rhinestone/module-sdk'
import { useStartale } from '../providers/StartaleAccountProvider'

// Social Recovery Module configuration
const SOCIAL_RECOVERY_MODULE = getModule({
module: 'social-recovery',
initData: [], // Will be set when installing
type: 'validator',
})

export function useSocialRecovery() {
const { startaleAccount, startaleClient } = useStartale()
const [isModuleInstalled, setIsModuleInstalled] = useState(false)
const [guardians, setGuardians] = useState<string[]>([])
const [threshold, setThreshold] = useState(2)

// Check if module is installed
useEffect(() => {
checkModuleInstallation()
}, [startaleAccount])

async function checkModuleInstallation() {
if (!startaleAccount) return

try {
// Note: This would typically check the account's installed modules
// Implementation depends on the specific account contract
console.log('Checking social recovery module installation...')

// For demo purposes, we'll assume it's not installed initially
setIsModuleInstalled(false)
} catch (error) {
console.error('Error checking module installation:', error)
}
}

return {
isModuleInstalled,
guardians,
threshold,
setGuardians,
setThreshold,
}
}

Social Recovery Component

Let's build a comprehensive social recovery interface:

components/SocialRecovery.tsx
import { useState } from 'react'
import { encodeFunctionData, isAddress } from 'viem'
import { getInstallModule, getUninstallModule } from '@rhinestone/module-sdk'
import { useStartale } from '../providers/StartaleAccountProvider'
import { useSocialRecovery } from '../hooks/useSocialRecovery'

type RecoveryStep = 'setup' | 'installed' | 'recovery'

export function SocialRecovery() {
const { startaleAccount, startaleClient } = useStartale()
const {
isModuleInstalled,
guardians,
threshold,
setGuardians,
setThreshold
} = useSocialRecovery()

const [currentStep, setCurrentStep] = useState<RecoveryStep>('setup')
const [newGuardian, setNewGuardian] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [recoveryInProgress, setRecoveryInProgress] = useState(false)

// Determine current step based on module status
const step: RecoveryStep = isModuleInstalled ? 'installed' : 'setup'

return (
<div className="social-recovery">
<h3>Social Recovery Setup</h3>
<p>Secure your account with trusted guardians who can help you recover access.</p>

{step === 'setup' && (
<SetupPhase
guardians={guardians}
threshold={threshold}
newGuardian={newGuardian}
setNewGuardian={setNewGuardian}
setGuardians={setGuardians}
setThreshold={setThreshold}
onInstall={installSocialRecovery}
isLoading={isLoading}
/>
)}

{step === 'installed' && (
<InstalledPhase
guardians={guardians}
threshold={threshold}
onUninstall={uninstallSocialRecovery}
onInitiateRecovery={() => setRecoveryInProgress(true)}
isLoading={isLoading}
/>
)}

{recoveryInProgress && (
<RecoveryPhase
guardians={guardians}
threshold={threshold}
onCancel={() => setRecoveryInProgress(false)}
/>
)}
</div>
)

async function installSocialRecovery() {
if (!startaleClient || guardians.length === 0 || threshold > guardians.length) {
alert('Please configure guardians and threshold first')
return
}

setIsLoading(true)
try {
console.log('Installing social recovery module...', {
guardians,
threshold,
})

// Get module installation data
const installModule = getInstallModule({
module: 'social-recovery',
initData: encodeFunctionData({
abi: [
{
name: 'initSocialRecovery',
type: 'function',
inputs: [
{ name: 'guardians', type: 'address[]' },
{ name: 'threshold', type: 'uint256' },
],
outputs: [],
},
],
functionName: 'initSocialRecovery',
args: [guardians as `0x${string}`[], BigInt(threshold)],
}),
type: 'validator',
})

// Install the module
const hash = await startaleClient.sendUserOperation({
calls: [installModule],
})

console.log('Social recovery module installed:', hash)
alert('Social recovery module installed successfully!')

// Refresh module status
// checkModuleInstallation()

} catch (error) {
console.error('Failed to install social recovery module:', error)
alert(`Installation failed: ${(error as Error).message}`)
} finally {
setIsLoading(false)
}
}

async function uninstallSocialRecovery() {
if (!startaleClient) return

const confirm = window.confirm('Are you sure you want to remove social recovery? This will disable account recovery via guardians.')
if (!confirm) return

setIsLoading(true)
try {
console.log('Uninstalling social recovery module...')

// Get module uninstallation data
const uninstallModule = getUninstallModule({
module: 'social-recovery',
type: 'validator',
})

// Uninstall the module
const hash = await startaleClient.sendUserOperation({
calls: [uninstallModule],
})

console.log('Social recovery module uninstalled:', hash)
alert('Social recovery module removed successfully!')

} catch (error) {
console.error('Failed to uninstall social recovery module:', error)
alert(`Uninstallation failed: ${(error as Error).message}`)
} finally {
setIsLoading(false)
}
}
}

Guardian Management

Advanced Guardian Features

utils/guardianUtils.ts
export interface Guardian {
address: string
name?: string
email?: string
addedAt: number
isActive: boolean
}

export function validateGuardianConfiguration(
guardians: Guardian[],
threshold: number
): { isValid: boolean; errors: string[] } {
const errors: string[] = []

if (guardians.length === 0) {
errors.push('At least one guardian is required')
}

if (threshold < 1) {
errors.push('Threshold must be at least 1')
}

if (threshold > guardians.length) {
errors.push('Threshold cannot exceed number of guardians')
}

// Check for duplicate addresses
const addresses = guardians.map(g => g.address.toLowerCase())
const uniqueAddresses = new Set(addresses)
if (addresses.length !== uniqueAddresses.size) {
errors.push('Duplicate guardian addresses detected')
}

// Validate Ethereum addresses
guardians.forEach((guardian, index) => {
if (!isAddress(guardian.address)) {
errors.push(`Guardian ${index + 1} has invalid address format`)
}
})

return {
isValid: errors.length === 0,
errors,
}
}

export function generateRecoveryMessage(
accountAddress: string,
newOwner: string,
nonce: number
): string {
return `Account Recovery Request:
Account: ${accountAddress}
New Owner: ${newOwner}
Nonce: ${nonce}

I confirm this recovery request is authorized.`
}

Recovery Signature Process

utils/recoverySignatures.ts
import { hashMessage, recoverAddress } from 'viem'

export interface RecoverySignature {
guardian: string
signature: string
timestamp: number
}

export async function verifyRecoverySignature(
message: string,
signature: string,
expectedGuardian: string
): Promise<boolean> {
try {
const messageHash = hashMessage(message)
const recoveredAddress = await recoverAddress({
hash: messageHash,
signature: signature as `0x${string}`,
})

return recoveredAddress.toLowerCase() === expectedGuardian.toLowerCase()
} catch (error) {
console.error('Signature verification failed:', error)
return false
}
}

export function hasThresholdMet(
signatures: RecoverySignature[],
guardians: string[],
threshold: number
): boolean {
const validGuardians = signatures.filter(sig =>
guardians.includes(sig.guardian.toLowerCase())
)

return validGuardians.length >= threshold
}

Security Considerations

Best Practices

  1. Guardian Selection:

    • Choose trustworthy individuals who understand the responsibility
    • Diversify guardians geographically and technologically
    • Avoid choosing guardians who might collude
  2. Threshold Configuration:

    • 2-of-3 is most common for personal accounts
    • Higher thresholds provide more security but less convenience
    • Consider backup guardians for critical accounts
  3. Communication Protocol:

    • Establish secure communication channels with guardians
    • Document the recovery process clearly
    • Regular check-ins to ensure guardians' addresses are still valid

Common Pitfalls

  • Single Point of Failure: Don't rely on one type of guardian (e.g., all family members)
  • Lost Guardians: Plan for guardians becoming unavailable
  • Threshold Too High: Ensure you can always reach enough guardians
  • Insufficient Verification: Guardians should verify identity before signing

Testing & Validation

Recovery Simulation

utils/recoverySimulation.ts
export class RecoverySimulator {
private guardians: string[]
private threshold: number

constructor(guardians: string[], threshold: number) {
this.guardians = guardians
this.threshold = threshold
}

simulateRecovery(availableGuardians: string[]): {
canRecover: boolean
requiredSignatures: number
availableSignatures: number
missingGuardians: string[]
} {
const availableCount = availableGuardians.filter(guardian =>
this.guardians.includes(guardian)
).length

const missingGuardians = this.guardians.filter(guardian =>
!availableGuardians.includes(guardian)
)

return {
canRecover: availableCount >= this.threshold,
requiredSignatures: this.threshold,
availableSignatures: availableCount,
missingGuardians,
}
}

testScenarios(): Array<{
scenario: string
unavailableGuardians: number
canRecover: boolean
}> {
const scenarios = []

for (let unavailable = 0; unavailable <= this.guardians.length; unavailable++) {
const available = this.guardians.length - unavailable
scenarios.push({
scenario: `${unavailable} guardian(s) unavailable`,
unavailableGuardians: unavailable,
canRecover: available >= this.threshold,
})
}

return scenarios
}
}

Integration with Other Features

Social recovery works seamlessly with other smart account features:

  • Multi-signature operations: Guardians can approve emergency actions
  • Session keys: Recover access to session-enabled applications
  • Upgrade mechanisms: Update account logic through guardian consensus

Next Steps

  1. Test the Setup: Configure guardians and test the recovery flow
  2. Smart Sessions: Learn about reducing signature friction

Troubleshooting

Module installation fails:

  • Ensure sufficient gas or token balance for transaction
  • Verify all guardian addresses are valid
  • Check that threshold is within valid range

Recovery process stuck:

  • Verify guardian signatures are valid
  • Ensure threshold requirements are met
  • Check for expired signatures or replay attacks

Guardians can't sign:

  • Confirm guardians have access to their wallets
  • Verify the recovery message format is correct
  • Check guardian addresses match exactly