Security & Recovery

Agent Wallet Recovery:
Backup Strategies and Disaster Recovery

March 4, 2026 20 min read Python, Security

An AI agent without a tested wallet recovery plan is one hardware failure away from permanent fund loss. Unlike human-operated wallets, agent wallets may hold significant capital with no human to manually intervene during a disaster. This guide covers every layer of the recovery stack: seed phrase backup, multi-sig architectures, social recovery contracts, timelock mechanisms, and fully automated recovery drills.

Recovery Threat Model for AI Agents

Traditional wallet security advice is written for humans. AI agents face a fundamentally different threat landscape. The agent may be running in a cloud VM, a container, or on bare metal. Its private key material is often generated programmatically and stored in environment variables, encrypted files, or key management services. Understanding the full threat matrix is the first step to designing a resilient recovery system.

Critical

Host Compromise

Attacker gains root access to the agent's server. All in-memory key material is exposed.

Critical

Storage Failure

Disk failure or accidental deletion destroys the encrypted keystore with no backup.

Critical

KMS Lock-out

AWS/GCP KMS policy change or account suspension makes key decryption impossible.

High

Signing Key Rotation Bug

Automated key rotation generates a new key but fails to update the backup, leaving funds orphaned.

High

Dependency Supply Chain

A compromised npm/pip package exfiltrates private keys during a routine npm install.

Medium

Agent Logic Bug

The agent signs a malicious transaction due to a logic error, draining funds to attacker address.

3-2-1
Backup rule (3 copies, 2 media, 1 offsite)
2-of-3
Minimum multi-sig threshold
48h
Recommended timelock delay
Weekly
Recovery drill frequency
Non-Negotiable Rule

Never store an unencrypted private key or seed phrase in: environment variables without secrets management, source code (even private repos), database fields, or log files. All of these are systematically scraped by attackers targeting cloud infrastructure.

Seed Phrase Backup and BIP39 Security

Most agent wallets are ultimately derived from a BIP39 mnemonic seed phrase. The seed phrase is the root of the entire key hierarchy — whoever has it controls all derived accounts. For AI agents, the challenge is that the seed must be accessible programmatically (to re-derive keys after a restart) but must not be easily exfiltrated.

BIP39 Key Derivation Chain

Entropy (128-256 bits) → BIP39 Mnemonic (12-24 words) → PBKDF2-HMAC-SHA512(mnemonic + passphrase, "mnemonic", 2048 rounds) → 512-bit Seed → BIP32 Master Private Key (m) → BIP44 Derived Keys: m/44'/60'/0'/0/0 → Ethereum account 0 m/44'/60'/0'/0/1 → Ethereum account 1 m/44'/195'/0'/0/0 → Tron account 0 m/44'/0'/0'/0/0 → Bitcoin account 0

Secure Seed Storage Architecture for Agents

1

Generate entropy in a secure environment

Use the OS CSPRNG (os.urandom in Python) or a hardware HSM. Never use user-visible entropy sources or predictable values.

2

Encrypt the seed before storage

Use AES-256-GCM with a key derived from a hardware-backed master secret (AWS KMS, GCP Secret Manager, or Vault). The encrypted blob is safe to store in a database.

3

Split the master key using Shamir's Secret Sharing

Split the KMS-protecting key into N shares with a threshold of K. Distribute shares to distinct custodians or storage backends (HSM + cold storage + operator).

4

Store the encrypted seed in 3 independent locations

Primary: encrypted in the agent's database. Backup 1: encrypted file in S3/GCS with versioning. Backup 2: printed QR code in a fireproof safe.

5

Test recovery weekly via automated drills

A scheduled job decrypts the backup and verifies it produces the expected public key. Alerts fire if verification fails.

seed_backup.py Python
import os
import json
import base64
import hashlib
import hmac
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import mnemonic as bip39

# Purple Flea Wallet API for backup verification
WALLET_API = "https://purpleflea.com/wallet-api"


class SeedManager:
    """
    Manages BIP39 seed backup and recovery for AI agents.
    Uses AES-256-GCM encryption with PBKDF2-derived keys.
    """

    def __init__(self, master_password: bytes):
        """
        master_password: comes from KMS, HSM, or Vault.
        Never hardcode this — inject at runtime from secure source.
        """
        self.password = master_password
        self.mnemo = bip39.Mnemonic("english")

    def _derive_key(self, salt: bytes) -> bytes:
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=480000,  # NIST 2024 recommendation
        )
        return kdf.derive(self.password)

    def generate_seed(self, strength: int = 256) -> str:
        """Generate a new BIP39 mnemonic (24 words for max security)."""
        return self.mnemo.generate(strength)

    def encrypt_seed(self, mnemonic_phrase: str,
                      agent_id: str) -> dict:
        """
        Encrypt a mnemonic phrase for backup storage.
        Returns a JSON-safe dict with all parameters for decryption.
        """
        salt = os.urandom(32)
        nonce = os.urandom(12)
        key = self._derive_key(salt)
        aes = AESGCM(key)

        # Include agent_id in AAD to bind backup to specific agent
        ciphertext = aes.encrypt(
            nonce,
            mnemonic_phrase.encode(),
            agent_id.encode()  # AAD
        )

        # Store checksum of plaintext for backup validation
        checksum = hashlib.sha256(mnemonic_phrase.encode()).hexdigest()

        return {
            "version": 1,
            "agent_id": agent_id,
            "salt": base64.b64encode(salt).decode(),
            "nonce": base64.b64encode(nonce).decode(),
            "ciphertext": base64.b64encode(ciphertext).decode(),
            "checksum": checksum[:16],  # first 8 bytes for quick check
        }

    def decrypt_seed(self, backup: dict) -> str:
        """Decrypt a backup blob and return the mnemonic phrase."""
        salt = base64.b64decode(backup["salt"])
        nonce = base64.b64decode(backup["nonce"])
        ct = base64.b64decode(backup["ciphertext"])
        key = self._derive_key(salt)
        aes = AESGCM(key)
        plaintext = aes.decrypt(
            nonce, ct, backup["agent_id"].encode()
        )
        return plaintext.decode()

    def verify_backup(self, backup: dict,
                       expected_address: str) -> bool:
        """
        Verify that backup decrypts correctly and produces
        the expected wallet address. Run this weekly.
        """
        try:
            phrase = self.decrypt_seed(backup)
            if not self.mnemo.check(phrase):
                return False
            # Derive address and compare (using eth_account or similar)
            from eth_account import Account
            Account.enable_unaudited_hdwallet_features()
            acct = Account.from_mnemonic(phrase)
            return acct.address.lower() == expected_address.lower()
        except Exception:
            return False

Multi-Sig Recovery Architecture

A single-key wallet is a single point of failure. Multi-sig wallets require M-of-N signatures to authorize any transaction. For AI agents, this is particularly powerful: the agent holds key 1, a hardware wallet holds key 2 (controlled by the agent's operator), and a time-locked emergency key provides key 3 for recovery scenarios.

Gnosis Safe for Agent Multi-Sig

The Gnosis Safe (Safe Protocol) smart contract is the industry standard for on-chain multi-sig. It supports arbitrary M-of-N thresholds, transaction queuing, and custom guard contracts that can restrict what transactions the agent is allowed to sign.

Recommended agent multi-sig configuration: Threshold: 2-of-3 Key 1: Agent hot wallet (automated, cloud-based) Key 2: Operator hardware wallet (manual confirmation) Key 3: Time-locked recovery key (60-day timelock, cold storage) Transaction flow (normal operation): Agent signs tx → Operator confirms → Executed Transaction flow (operator unavailable): Agent queues emergency tx → 60-day timelock → Auto-executed Recovery scenario (agent key compromised): Operator + Recovery key → 2-of-3 → Remove compromised key → Add new key
Multi-Sig ConfigSecurityLivenessBest Use Case
1-of-1NoneHighHot wallet, low balance
2-of-2MediumLowTwo-party approvals
2-of-3HighHighAgent + operator + recovery
3-of-5Very HighMediumTreasury management

Social Recovery Contracts

Social recovery wallets, pioneered by Vitalik Buterin, allow a set of "guardians" to collectively rotate the wallet's signing key if the primary key is lost. For AI agents, the guardians might be: the agent's operator, a trusted third party service, and a DAO governance contract.

ERC-4337 Account Abstraction makes social recovery practical without requiring specialized smart contract wallets — any EOA can be wrapped in a social recovery account via the EntryPoint mechanism.

Social Recovery Contract State Machine: Normal operation: owner = agent_hot_key All transactions signed by agent Recovery initiation: Guardian threshold (e.g. 3-of-5) call initiateRecovery(new_owner) Recovery period starts (48h delay) Recovery execution (after delay): Any guardian calls executeRecovery() owner = new_owner All guardian approvals reset Cancellation (agent still has key): Owner calls cancelRecovery() Guardians must re-initiate from scratch
Purple Flea Wallet API

The Purple Flea Wallet API at purpleflea.com/wallet-api includes built-in backup and export endpoints. Agents can schedule automated encrypted exports of their wallet state to the Purple Flea secure vault, with restoration available through the API's multi-factor recovery flow. This eliminates the need to implement your own backup infrastructure for the primary use case.

Timelock and Escape Hatch Mechanisms

Timelocks add a mandatory delay between when a transaction is proposed and when it can be executed. For agent wallets holding large balances, this provides a critical window: even if an attacker compromises the agent and initiates a drain, the timelock gives the operator time to detect and cancel the malicious transaction.

Timelock contract interface (simplified): function schedule(address to, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay) → schedules tx for execution after `delay` seconds → emits CallScheduled event (monitoring hook) function execute(address to, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) → executes tx if delay elapsed and not cancelled function cancel(bytes32 id) → cancels pending tx (callable by CANCELLER_ROLE) Recommended delays: Small transfers (<$1k): 0s (immediate) Medium transfers ($1-50k): 1h Large transfers ($50k+): 24-48h Key rotation: 72h Contract upgrades: 7 days
Monitoring Is Non-Negotiable

A timelock only protects you if someone is watching for CallScheduled events. Set up an on-chain event monitor (using Alchemy Notify, Tenderly, or a custom indexer) that immediately alerts the operator when any high-value transaction is scheduled. Without monitoring, the timelock is security theater.

Automated Backup Validation and Recovery Drills

The only recovery plan you can trust is one you have tested. Automated recovery drills should be a standard part of any agent's operational runbook. The following code implements a complete weekly backup validation job that can be run as a cron job or scheduled task.

recovery_drill.py Python
import asyncio
import json
import os
import httpx
from datetime import datetime, timezone
from dataclasses import dataclass
from enum import Enum

WALLET_API = "https://purpleflea.com/wallet-api"


class DrillResult(Enum):
    PASS = "pass"
    FAIL = "fail"
    WARN = "warn"


@dataclass
class DrillReport:
    timestamp: str
    agent_id: str
    primary_backup_ok: bool
    secondary_backup_ok: bool
    address_match: bool
    wallet_api_accessible: bool
    multisig_signers_ok: bool
    overall: DrillResult
    issues: list[str]


class RecoveryDrillRunner:
    """
    Weekly automated recovery drill for AI agent wallets.
    Verifies all backup paths and alerts on failures.
    """

    def __init__(self,
                 agent_id: str,
                 api_key: str,
                 expected_address: str,
                 seed_manager: "SeedManager",
                 alert_webhook: str):
        self.agent_id = agent_id
        self.api_key = api_key
        self.expected_address = expected_address
        self.seed_mgr = seed_manager
        self.webhook = alert_webhook

    async def run_drill(self) -> DrillReport:
        issues: list[str] = []
        checks = {}

        # 1. Test primary backup (database)
        try:
            primary_backup = await self._load_primary_backup()
            checks["primary"] = self.seed_mgr.verify_backup(
                primary_backup, self.expected_address
            )
            if not checks["primary"]:
                issues.append("PRIMARY BACKUP VERIFICATION FAILED")
        except Exception as e:
            checks["primary"] = False
            issues.append(f"PRIMARY BACKUP ERROR: {e}")

        # 2. Test secondary backup (S3/GCS)
        try:
            sec_backup = await self._load_secondary_backup()
            checks["secondary"] = self.seed_mgr.verify_backup(
                sec_backup, self.expected_address
            )
            if not checks["secondary"]:
                issues.append("SECONDARY BACKUP VERIFICATION FAILED")
        except Exception as e:
            checks["secondary"] = False
            issues.append(f"SECONDARY BACKUP ERROR: {e}")

        # 3. Verify wallet API reachability
        async with httpx.AsyncClient(
            headers={"Authorization": f"Bearer {self.api_key}"}
        ) as client:
            try:
                r = await client.get(
                    f"{WALLET_API}/v1/accounts/{self.expected_address}",
                    timeout=5.0
                )
                checks["api"] = r.status_code == 200
            except Exception:
                checks["api"] = False
                issues.append("WALLET API UNREACHABLE")

        # 4. Determine overall result
        if not checks.get("primary") or not checks.get("secondary"):
            overall = DrillResult.FAIL
        elif not checks.get("api"):
            overall = DrillResult.WARN
        else:
            overall = DrillResult.PASS

        report = DrillReport(
            timestamp=datetime.now(timezone.utc).isoformat(),
            agent_id=self.agent_id,
            primary_backup_ok=checks.get("primary", False),
            secondary_backup_ok=checks.get("secondary", False),
            address_match=checks.get("primary", False),
            wallet_api_accessible=checks.get("api", False),
            multisig_signers_ok=True,  # extend as needed
            overall=overall,
            issues=issues
        )

        # Alert on failures
        if overall != DrillResult.PASS:
            await self._send_alert(report)

        return report

    async def _send_alert(self, report: DrillReport):
        """Send alert to operator webhook (Slack, PagerDuty, etc.)."""
        async with httpx.AsyncClient() as client:
            await client.post(self.webhook, json={
                "text": (
                    f"WALLET RECOVERY DRILL {report.overall.value.upper()}\n"
                    f"Agent: {report.agent_id}\n"
                    f"Issues: {', '.join(report.issues) or 'None'}"
                )
            })

    async def _load_primary_backup(self) -> dict:
        # Load from your primary backup location (database, env, etc.)
        raw = os.environ.get("AGENT_SEED_BACKUP_PRIMARY")
        return json.loads(raw)

    async def _load_secondary_backup(self) -> dict:
        # Load from secondary backup (S3, GCS, etc.)
        import boto3
        s3 = boto3.client("s3")
        obj = s3.get_object(
            Bucket="my-agent-backups",
            Key=f"seeds/{self.agent_id}.json"
        )
        return json.loads(obj["Body"].read())


# Cron entrypoint: run weekly
async def main():
    from seed_backup import SeedManager
    mgr = SeedManager(master_password=os.getenv("MASTER_PASSWORD").encode())
    runner = RecoveryDrillRunner(
        agent_id="agent-001",
        api_key=os.getenv("PF_API_KEY"),
        expected_address="0xYourAgentAddress",
        seed_manager=mgr,
        alert_webhook=os.getenv("ALERT_WEBHOOK")
    )
    report = await runner.run_drill()
    print(f"Drill result: {report.overall.value} | Issues: {report.issues}")

asyncio.run(main())

Protect Your Agent's Funds

Use the Purple Flea Wallet API's built-in backup and export features to automate seed protection without building your own infrastructure.