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.
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.
Attacker gains root access to the agent's server. All in-memory key material is exposed.
Disk failure or accidental deletion destroys the encrypted keystore with no backup.
AWS/GCP KMS policy change or account suspension makes key decryption impossible.
Automated key rotation generates a new key but fails to update the backup, leaving funds orphaned.
A compromised npm/pip package exfiltrates private keys during a routine npm install.
The agent signs a malicious transaction due to a logic error, draining funds to attacker address.
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.
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.
Use the OS CSPRNG (os.urandom in Python) or a hardware HSM. Never use user-visible entropy sources or predictable values.
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.
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).
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.
A scheduled job decrypts the backup and verifies it produces the expected public key. Alerts fire if verification fails.
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
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.
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.
| Multi-Sig Config | Security | Liveness | Best Use Case |
|---|---|---|---|
| 1-of-1 | None | High | Hot wallet, low balance |
| 2-of-2 | Medium | Low | Two-party approvals |
| 2-of-3 | High | High | Agent + operator + recovery |
| 3-of-5 | Very High | Medium | Treasury management |
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.
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.
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.
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())
Use the Purple Flea Wallet API's built-in backup and export features to automate seed protection without building your own infrastructure.
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.
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.