
Smart contract wallets unlock a new class of wallet features — gasless transactions, session keys, spending limits, recovery mechanisms. But more power means more attack surface. A misconfigured session key, an overpermissioned signing policy, or a missing recovery path can mean the difference between a secure wallet experience and a drained account.
This guide covers the security patterns that matter most for smart accounts built on ERC-4337 and EIP-7702 in 2026 — from session key scoping and onchain spending limits to EIP-7702 delegation risks, modular account security (ERC-7579), and the emerging challenge of securing AI agent-controlled wallets. The examples reference Openfort's stack (OpenSigner for key management, EIP-7702 smart accounts, and the policy engine), but the patterns apply regardless of which infra you use.
Why Smart Account Security Is Different
Traditional EOA security is simple: protect the seed phrase. Everything else — permissions, spending limits, recovery — is your problem to handle at the application layer, if at all.
Smart accounts shift the security model. The signing key is still the root of trust, but the account is a smart contract that enforces rules. You can set spending limits, restrict which contracts a key can call, define time bounds, and implement recovery without needing the original key. That programmability is powerful — and it means security is now a multi-layer design problem.
The key layers to secure:
| Layer | What It Protects | Failure Mode |
|---|---|---|
| Key storage | The private key itself | Extraction, theft |
| Signing policy | What the key can sign | Overpermissioned signing |
validateUserOp | Whether a UserOperation is valid | Permissive validation drains gas |
| Onchain permissions | What the account executes | Unlimited spend, wrong contracts |
| Recovery | Access after key loss | Permanent lockout |
| Session key scope | Temporary key limits | Session key abuse |
| Delegation (EIP-7702) | Which implementation runs | Front-running, takeover |
| Module isolation (ERC-7579) | What modules can do | Privilege escalation |
Let's go through each layer.
1. Secure Key Storage: Never Let Keys Touch Browser Memory
The most basic rule: private keys should never exist in plaintext in browser memory, localStorage, or any client-side storage that JavaScript can reach.
What goes wrong: An XSS vulnerability, a malicious browser extension, or a compromised npm dependency can silently extract keys from JavaScript memory. Once the key is out, the game is over — no smart contract rule can undo a stolen key.
The right model:
_10User input (passkey / biometric)_10 │_10 ▼_10TEE or HSM (key lives here — never exported)_10 │_10 ▼_10Signing happens inside the secure enclave_10 │_10 ▼_10Signed transaction emitted (key never leaves)
Openfort's OpenSigner stores keys inside a Trusted Execution Environment (TEE). The key is generated inside the enclave, signs inside the enclave, and never exits. Even if an attacker gains root access to the server, the key cannot be extracted — because the TEE's memory is hardware-isolated from the host OS.
For mobile apps, device-bound keys tied to the Secure Enclave (iOS) or Strongbox (Android) give you the same property. The key is non-extractable; it can only be used locally with biometric confirmation.
Practical rules:
- Never store private keys in
localStorage,sessionStorage, or cookies - Never pass keys as URL parameters or log them
- If using a custodial approach, verify the provider uses TEE or HSM key storage — not just encrypted database fields
- Prefer passkeys (WebAuthn) for user-facing wallets: the private key is device-bound and cannot be phished
2. Session Keys: Scope Everything, Trust Nothing by Default
The single biggest security improvement you can make in a smart account: never use the master key for routine operations.
A master key has full account access — it can transfer all funds, call any contract, and change account configuration. Exposing it to every user interaction is like running production on root. If the key leaks from any session, you lose everything.
Session keys flip this model. A session key is a temporary key with a narrow permission set. When a user starts a game session, a trading flow, or a checkout, your app generates a fresh session key and registers it on the smart account with tight limits:
_25// Registering a session key on an Openfort smart account_25struct SessionKeyParams {_25 address key;_25 uint48 validAfter;_25 uint48 validUntil;_25 uint256 ethLimit;_25 address[] whitelist;_25 bytes4[] allowedSelectors;_25 uint256 operationLimit;_25}_25_25SessionKeyParams memory params = SessionKeyParams({_25 key: sessionKeyAddress,_25 validAfter: uint48(block.timestamp),_25 validUntil: uint48(block.timestamp + 4 hours),_25 ethLimit: 0.01 ether,_25 whitelist: [gameContractAddress],_25 allowedSelectors: [_25 bytes4(keccak256("claimReward(uint256)")),_25 bytes4(keccak256("purchaseItem(uint256,uint256)"))_25 ],_25 operationLimit: 50_25});_25_25account.registerSessionKey(params);
Now the user can play without approving every transaction — but if this session key is compromised, the attacker can only call those two functions, on that one contract, up to 50 times, within 0.01 ETH, for the next 4 hours.
Session key security checklist:
- Always set
validUntil— no eternal session keys - Set
ethLimitand token spending caps appropriate to the session's maximum expected value - Whitelist target contracts; don't issue session keys with
ANY_TARGET - Use
allowedSelectorsto restrict callable functions - Revoke session keys when the session ends — don't rely on expiry alone
- Issue session keys per-device, not per-user, so revocation is granular
3. Validate UserOperations Strictly
The validateUserOp function is the security boundary of every ERC-4337 smart account. It decides whether a UserOperation is authorized to execute. Get this wrong, and you're paying gas for operations that will fail — or worse, executing operations that shouldn't be authorized.
Common mistake: permissive validation. If your validateUserOp accepts operations that will inevitably fail during execution, a malicious bundler can submit those operations repeatedly. Each time, your account pays the gas fee, and nothing useful happens. This is a gas-draining attack that requires no key compromise.
_31// BAD: Only checks signature, doesn't validate operation semantics_31function validateUserOp(_31 PackedUserOperation calldata userOp,_31 bytes32 userOpHash,_31 uint256 missingAccountFunds_31) external returns (uint256 validationData) {_31 // Only verifies signature — any valid signature passes_31 if (_validateSignature(userOp, userOpHash)) {_31 return 0; // SIG_VALIDATION_SUCCESS_31 }_31 return 1; // SIG_VALIDATION_FAILED_31}_31_31// BETTER: Validates signature AND checks operation constraints_31function validateUserOp(_31 PackedUserOperation calldata userOp,_31 bytes32 userOpHash,_31 uint256 missingAccountFunds_31) external returns (uint256 validationData) {_31 if (!_validateSignature(userOp, userOpHash)) {_31 return 1;_31 }_31_31 // Check session key constraints before accepting_31 SessionKey storage sk = sessionKeys[_getSigner(userOp)];_31 if (sk.validUntil < block.timestamp) return 1;_31 if (sk.operationsUsed >= sk.operationLimit) return 1;_31_31 // Pack validAfter/validUntil into validationData_31 return _packValidationData(false, sk.validUntil, sk.validAfter);_31}
Access control on execute. Only the EntryPoint contract (or a vetted executor module in ERC-7579) should be allowed to call your account's execute function. If anyone can call execute directly, they bypass validateUserOp entirely.
_10function execute(address target, uint256 value, bytes calldata data) external {_10 require(msg.sender == ENTRY_POINT, "Only EntryPoint");_10 // ... execute logic_10}
Trail of Bits' audit research found this is one of the most common failure modes in production smart accounts.
4. Onchain Spending Limits: The Last Line of Defense
Policy engines and session key scopes run before transaction execution. But what if your policy engine has a bug? What if a session key misconfiguration allows too much?
Onchain spending limits are enforced by the smart contract itself during execution. They can't be bypassed by a compromised backend, a bug in your signing code, or an attacker who finds a way around your off-chain checks.
The permission fields that matter:
| Field | Protection |
|---|---|
ethLimit | Maximum ETH any single key can spend |
spendTokenInfo.limit | Maximum ERC-20 token amount |
whitelist | Only allowed target contracts |
allowedSelectors | Only allowed function signatures |
limit | Maximum number of operations |
validUntil | Hard expiry — key stops working |
Period-based limits add a time dimension. Instead of a one-time cap, you can set a daily or weekly budget that resets automatically:
_10// 1000 USDC per month, resets on the 1st_10period = MONTH_10limit = 1000 * 1e6 // USDC has 6 decimals
This is particularly valuable for subscription-like patterns, agent wallets with ongoing budgets, or any scenario where you want a guardrail that resets without manual intervention.
Implementation note: When setting spending limits, be conservative for your first deploy. It's easy to increase limits; it's much harder to tell users their funds were drained because limits were set too high. Start at 80% of expected maximum transaction value and adjust based on real usage.
5. Policy Engines: Off-chain Defense-in-Depth
Onchain limits are the backstop. Policy engines are the first gate.
A policy engine sits between a signing request and the actual signing operation. Before any key signs anything, the policy engine evaluates the transaction against a ruleset:
_10Transaction request → Policy engine → [approved/rejected] → Sign / Abort
Because this check happens off-chain (in your backend or a TEE), you can apply logic that's impossible or expensive to do on-chain:
- Address blocklists: Reject transactions to known mixer addresses, sanctioned wallets, or contract addresses flagged as malicious
- Rate limiting: Block more than N transactions per hour per key/user
- Anomaly detection: Flag transactions that deviate significantly from user history (e.g., first-ever transfer over $10k)
- Multi-sig thresholds: Require human approval for transactions above a certain value
- Business logic: Don't sign if the user account is suspended, the session has been flagged, or the destination is outside allowed regions
TEE-based policy engines give you the strongest guarantee: the policy logic itself runs in a secure enclave, so even a compromised server can't modify the rules or force the key to sign an unauthorized transaction. Openfort's backend wallet infrastructure runs policy evaluation inside a TEE for exactly this reason.
Off-chain + onchain = defense-in-depth:
_10Transaction request_10 │_10 ▼_10Policy engine (off-chain): rate limits, blocklists, fraud rules_10 │ Rejected? → Abort_10 ▼_10Smart account (on-chain): spending limits, contract whitelist, time bounds_10 │ Rejected? → Revert_10 ▼_10Transaction executes
If one layer has a gap, the other catches it.
6. Recovery Mechanisms: Plan for Key Loss Before It Happens
The most overlooked security practice: implement recovery before you launch, not after someone loses access.
With a traditional wallet, losing the seed phrase means permanent loss. With smart accounts, recovery is programmable — but only if you set it up in advance. There is no recovery after the fact without a prior mechanism in place.
Pattern 1: Passkey Backup (Recommended First Layer)
The user enrolls a second passkey — on a different device, a password manager, or a hardware key — at signup. If they lose their primary device, they authenticate with the backup passkey and reclaim the account.
This is the lowest-friction recovery mechanism and should be the default for consumer apps:
- During onboarding, prompt users to add a backup passkey ("Save this in case you lose your device")
- Register the backup passkey as a secondary authorized key on the smart account
- On recovery, user authenticates with backup passkey → gains full account access
UX note: Frame this as "secure your wallet" not "create a backup." Backup connotes optional; security connotes essential.
Pattern 2: Social Guardians
A set of trusted addresses that can collectively authorize recovery after a time delay. Useful when passkey backup isn't sufficient or the user loses all devices:
_10Guardians: [friend_wallet, family_wallet, openfort_recovery_service]_10Threshold: 2 of 3_10Delay: 48 hours (window to cancel if not initiated by owner)
The time delay is critical — it's the security window. If an attacker compromises one guardian and initiates a fraudulent recovery, the legitimate owner has 48 hours to cancel it. Without a delay, social recovery is only as secure as the weakest guardian.
Watch out for recovery griefing. Audit research from Trail of Bits found that if any single guardian can initiate recovery (even if threshold approval is required to complete it), a malicious guardian can spam initiation transactions, resetting the recovery timer each time and preventing legitimate recovery from ever completing. Mitigate by requiring a threshold to initiate, not just to complete:
_12// BAD: any guardian can initiate, resetting the timer_12function initiateRecovery() external onlyGuardian {_12 recoveryInitiatedAt = block.timestamp; // Resets every time!_12}_12_12// BETTER: require threshold to initiate_12function initiateRecovery(bytes[] calldata guardianSigs) external {_12 require(guardianSigs.length >= threshold, "Need threshold to initiate");_12 require(recoveryInitiatedAt == 0, "Recovery already pending");_12 // Verify guardian signatures..._12 recoveryInitiatedAt = block.timestamp;_12}
Pattern 3: Last Resort — Shamir's Secret Sharing
For high-value accounts where centralized recovery services are unacceptable, Shamir's Secret Sharing (SSS) splits the recovery key into N shares, requiring K to reconstruct. No single party holds enough to recover alone.
This is complex to implement and operationalize. Use it for enterprise custody or self-sovereign users who understand the tradeoffs.
Recovery anti-patterns to avoid:
- No recovery at all: The most common mistake. One device failure = permanent lockout
- Recovery to a single centralized service: Introduces a single point of failure and custody risk
- Immediate recovery with no time delay: Removes the fraud detection window
- Recovery keys stored alongside primary keys: Defeats the purpose
7. Paymaster Security: Don't Let Gas Sponsorship Become a DoS Vector
Gas sponsorship via paymasters unlocks gasless UX — but a misconfigured paymaster is an open money tap for attackers.
The attack: An attacker floods your app with sponsored transactions, draining your paymaster deposit before any real users are served.
Mitigations:
_28// Paymaster validation with rate limiting and target whitelisting_28function _validatePaymasterUserOp(_28 PackedUserOperation calldata userOp,_28 bytes32 userOpHash,_28 uint256 maxCost_28) internal override returns (bytes memory context, uint256 validationData) {_28 address sender = userOp.sender;_28_28 // Rate limit per wallet address_28 require(_28 opsThisHour[sender] < MAX_OPS_PER_HOUR, // e.g., 10_28 "Rate limit exceeded"_28 );_28 opsThisHour[sender]++;_28_28 // Only sponsor operations targeting whitelisted contracts_28 address target = address(bytes20(userOp.callData[16:36]));_28 require(allowedTargets[target], "Target not whitelisted");_28_28 // Verify function selector matches expected patterns_28 bytes4 selector = bytes4(userOp.callData[36:40]);_28 require(allowedSelectors[selector], "Function not whitelisted");_28_28 // Enforce per-operation gas cap_28 require(maxCost <= MAX_GAS_COST, "Gas exceeds cap");_28_28 return (abi.encode(sender), 0);_28}
Beware of postOp reverts. If your paymaster pays the EntryPoint from a shared pool during validatePaymasterUserOp, then tries to charge the user back in postOp, a revert in postOp does not undo the payment that already happened during validation. Design your paymaster to be safe even if postOp fails.
Always add a per-address rate limit to your paymaster. Even honest users rarely need more than a handful of sponsored transactions per hour. A hard cap of 10-20 ops/hour per address prevents most abuse with zero impact on legitimate usage.
For high-value sponsorship, require an off-chain authorization token signed by your backend before the paymaster will accept the operation. This means only users who've passed your app's business logic checks can get gas sponsored.
8. Key Rotation and Revocation
Keys should have a lifecycle. Rotating keys periodically and revoking them quickly when needed are hygiene practices that limit the blast radius of any compromise.
Proactive rotation: For backend wallets and agent wallets with ongoing access, rotate signing keys on a schedule (monthly or quarterly). The smart account simply adds the new key and removes the old one — no funds movement required.
Reactive revocation: Build revocation into every user flow that involves session keys. When a user logs out, a session ends, or suspicious activity is detected:
- Revoke the session key on-chain immediately
- Invalidate any off-chain session tokens
- Log the revocation event for audit purposes
Key hierarchy: Don't use the same key for everything. A good key hierarchy looks like:
_10Master key (TEE / hardware — rarely used)_10 ├── Admin key (recovery, account config — human-controlled)_10 ├── Operator key (backend automation — TEE-based)_10 └── Session keys (per-user, per-session — short-lived)
Each level has the minimum permissions needed for its purpose. Compromise of a session key doesn't affect the operator key; compromise of the operator key doesn't affect the master key.
9. Auditing and Monitoring
Security isn't a one-time configuration — it's an ongoing practice.
Emit events for everything that matters:
- Key additions and removals
- Session key issuance and revocation
- Spending limit changes
- Recovery initiated, cancelled, or executed
- Policy engine rejections (especially clusters of rejections)
Monitor for anomalies in real time:
- Transactions to new, never-before-seen addresses
- Spending velocity spikes (2x or 3x normal hourly rate)
- Session key usage from unexpected geographies or IPs
- Failed transactions that shouldn't be failing (could indicate probing)
Periodic reviews:
- Audit all active session keys monthly — are any older than expected or scoped wider than necessary?
- Review paymaster usage for signs of abuse
- Check guardian addresses are still controlled by the right people
- Test your recovery path before you need it
10. EIP-7702 Delegation Security
EIP-7702 (activated with Ethereum's Pectra upgrade) lets EOAs temporarily delegate execution to a smart contract implementation. This is powerful — it turns any existing wallet into a smart account — but it introduces attack vectors that didn't exist with pure ERC-4337.
Front-running authorization signatures
When a user signs an EIP-7702 authorization, the signature may be visible in the mempool before it's included in a block. An attacker can front-run this with their own initialization parameters, effectively taking over the account.
Mitigation: require signed initialization. Use an initWithSig pattern where the user signs the initialization parameters alongside the delegation:
_15function initWithSig(_15 address owner,_15 bytes calldata initData,_15 bytes calldata ownerSig_15) external {_15 // Verify the owner explicitly signed these init params_15 bytes32 digest = keccak256(abi.encodePacked(_15 "\x19\x01",_15 _domainSeparatorV4(),_15 keccak256(abi.encode(INIT_TYPEHASH, owner, keccak256(initData)))_15 ));_15 require(ECDSA.recover(digest, ownerSig) == owner, "Invalid init signature");_15_15 _initialize(owner, initData);_15}
Alternatively, require that initialization can only be called from the ERC-4337 EntryPoint, which validates the UserOperation signature before execution.
The private key remains active
After EIP-7702 delegation, the original EOA private key can still sign transactions and change delegations. This is by design, but it means key security doesn't become less important after delegation — it becomes more important, because the key can now also repoint the account to a malicious implementation.
Don't delegate to pre-existing smart contract wallets
Smart contract wallets built before EIP-7702 were not designed for the EOA-as-contract model. They may have initialization functions that assume msg.sender is the deployer, storage layout assumptions that conflict with EOA state, or access control that doesn't account for the private key still being active. Only delegate to contracts that are specifically designed and audited for 7702.
Practical rules:
- Only delegate to audited, 7702-aware contract implementations
- Use
initWithSigor EntryPoint-gated initialization - Keep implementation contracts minimal — Ambire's 7702 contract is ~200 lines
- Treat the private key as still having full control; protect it accordingly
- Monitor for unauthorized delegation changes
11. Modular Account Security (ERC-7579)
ERC-7579 defines a standard for modular smart accounts — accounts that extend functionality through plug-in modules rather than monolithic contracts. This is the direction the ecosystem is heading, and it introduces security considerations specific to module architecture.
Module types and their trust boundaries
ERC-7579 defines distinct module types, and conflating them is a security risk:
| Module Type | Role | Risk if Misconfigured |
|---|---|---|
| Validator | Verifies signatures during validateUserOp | False positives → unauthorized execution |
| Executor | Executes transactions on behalf of the account | Unrestricted executor → full account takeover |
| Fallback Handler | Handles calls to undefined functions | Malicious fallback → unexpected behavior |
| Hook | Pre/post execution checks | Bypassed hook → missing safety checks |
The critical rule: never treat validators and executors as the same type. If a validator module can also execute arbitrary transactions, it can bypass the validation → execution separation that ERC-4337 depends on.
_13// Module installation must enforce type separation_13function installModule(_13 uint256 moduleType,_13 address module,_13 bytes calldata initData_13) external onlyEntryPointOrSelf {_13 if (moduleType == MODULE_TYPE_VALIDATOR) {_13 _installValidator(module, initData);_13 } else if (moduleType == MODULE_TYPE_EXECUTOR) {_13 _installExecutor(module, initData);_13 }_13 // Never allow a single module to register as both_13}
Module installation is a privileged operation
Installing or removing modules changes the account's security properties. These operations should require the highest level of authorization — ideally only the master key or a multi-sig, not session keys.
Audit modules independently
Each module is a trust boundary. A vulnerability in one module shouldn't compromise others. Prefer modules that are:
- Stateless where possible (less storage interaction = fewer reentrancy vectors)
- Independently audited
- Published through registries with attestation (like the Rhinestone Module Registry)
ERC-7780 extends this further with policy modules that check what a UserOperation is trying to achieve and determine if it's allowed — adding another layer of granular control.
12. Securing AI Agent-Controlled Wallets
AI agents controlling smart accounts are the fastest-growing threat surface in 2026. Whether it's a trading bot, a DeFi yield optimizer, or an autonomous game agent, the security model is fundamentally different from human-controlled wallets.
The threat model shifts from key compromise to intent compromise
With a human wallet, the attacker needs the private key. With an agent wallet, the attacker needs to manipulate what the agent decides to do. Prompt injection — tricking the agent into executing unintended actions through crafted inputs — is the primary attack vector. The signature is valid, the key isn't compromised, but the operation is malicious.
Design principles for agent wallets
1. Never give agents master keys. Always use session keys with the tightest possible constraints:
_13// TypeScript: Creating a constrained agent session key with Openfort_13const sessionKey = await openfort.sessions.create({_13 player: agentPlayerId,_13 policy: agentPolicyId,_13 validUntil: Math.floor(Date.now() / 1000) + 3600, // 1 hour_13 whitelist: [TRADING_CONTRACT],_13 allowedFunctions: ["swap(address,address,uint256)", "claim()"],_13 spendingLimit: {_13 token: USDC_ADDRESS,_13 limit: "1000000000", // 1000 USDC_13 period: "daily"_13 }_13});
2. Validate operation semantics, not just signatures. Your policy engine should understand what the agent is trying to do, not just verify that the signature is valid:
_13Agent request: "swap 10,000 USDC for ETH on Uniswap"_13 │_13 ▼_13Policy engine checks:_13 ✓ Is this contract in the allowlist?_13 ✓ Is this function selector allowed?_13 ✓ Is the amount within the daily limit?_13 ✓ Is the slippage reasonable (< 2%)?_13 ✓ Is this the Nth operation this hour? (rate limit)_13 ✗ Is the output token on a blocklist?_13 │_13 ▼_13Sign or reject
3. Isolate the signing key from the agent runtime. Run the key in a TEE; run the agent in a separate process. Even if the agent is fully compromised via prompt injection, it can only submit requests — it cannot extract the key or bypass the policy engine.
4. Implement circuit breakers. Automated systems need automated kill switches:
- If spending velocity exceeds 3x the rolling average, pause all operations
- If the agent attempts a blocked operation more than N times, revoke the session key
- If the policy engine rejects more than K% of requests in a window, alert and pause
5. Log everything for post-incident analysis. Agent wallets generate high transaction volumes. Index all operations, policy decisions, and rejections. When (not if) something goes wrong, you need the audit trail.
Security Checklist for Production Smart Account Apps
Use this as a pre-launch review:
Key storage [ ] Private keys never stored in browser localStorage or session memory [ ] Key management uses TEE or device-bound storage (Secure Enclave / Strongbox) [ ] Passkeys used for user-facing wallets where possible
Session keys
[ ] All session keys have explicit validUntil expiry
[ ] ETH and token spending limits set per session key
[ ] Contract whitelist active — no ANY_TARGET session keys in production
[ ] Session keys revoked on logout, not just expired
Onchain limits [ ] Spending limits set at appropriate values (start conservative) [ ] Period-based limits configured for agent wallets and recurring access patterns [ ] Function selector restrictions applied where possible
Policy engine [ ] Off-chain policy engine deployed with address blocklist [ ] Rate limiting per wallet address [ ] Anomaly detection or threshold-based approval for large transactions
Recovery [ ] At least one recovery mechanism configured before launch [ ] Passkey backup enrolled during onboarding [ ] Social guardian contract deployed with appropriate time delay [ ] Recovery path tested end-to-end
Paymasters [ ] Per-address rate limits configured [ ] Target contract whitelist active [ ] Per-operation gas cap set [ ] Paymaster deposit monitored with alerting
Validation (validateUserOp)
[ ] Only EntryPoint can call execute — no direct external access
[ ] Validation checks operation constraints, not just signature validity
[ ] validAfter and validUntil packed correctly in validationData
[ ] Session key limits enforced during validation, not just execution
EIP-7702 delegation
[ ] Only delegating to audited, 7702-aware implementations
[ ] Initialization requires owner signature (initWithSig) or EntryPoint gating
[ ] Monitoring for unauthorized delegation changes
[ ] Original private key secured with same rigor as pre-delegation
Modular accounts (ERC-7579) [ ] Module types enforced — validators cannot act as executors [ ] Module installation restricted to master key / multi-sig authorization [ ] Each module independently audited [ ] Hook modules cannot be bypassed during execution flow
Agent wallets [ ] Agents use session keys, never master keys [ ] Policy engine validates operation semantics (not just signature) [ ] Signing key isolated from agent runtime (TEE) [ ] Circuit breakers configured for spending velocity anomalies [ ] All agent operations logged for audit
Monitoring [ ] On-chain events indexed and monitored [ ] Anomaly alerts configured [ ] Incident response plan documented
How Openfort Handles This in Practice
Openfort's wallet infrastructure implements these patterns at the platform level, so you don't need to build each layer from scratch:
- OpenSigner: Open-source, MIT-licensed TEE-based key management. Keys are generated and sign inside a hardware enclave. Self-hostable.
- EIP-7702 smart accounts: Full onchain permission system — session keys, spending limits, contract whitelist, function selectors, period-based caps. Designed specifically for 7702 delegation with secure initialization.
- Policy engine: Off-chain pre-signing evaluation with TEE-backed policy execution. Configure rules in the Openfort dashboard or via API. Supports semantic validation for agent wallets — not just signature checks, but operation intent verification.
- Recovery: Built-in passkey backup and guardian contract support
- Paymaster security: Rate limits, contract whitelists, and per-operation caps configurable per sponsorship policy
- Agent wallet support: Session key creation API with spending limits, contract whitelists, and time bounds — purpose-built for AI agent use cases where you need to constrain automated signing
The goal is defense-in-depth without building the layers yourself. The TEE provides hardware-level key isolation, the policy engine provides flexible off-chain control, and the smart account contract enforces the final onchain rules — independent layers that don't share failure modes.
For teams starting from scratch or migrating from a less secure setup, the Openfort quickstart walks through the full setup from key management through recovery configuration.
Security in smart accounts is not binary — it's a spectrum of controls layered on top of each other. A TEE key store without spending limits can still be abused by a compromised session. Spending limits without recovery leave users locked out. An EIP-7702 delegation without secure initialization can be front-run. An AI agent wallet without semantic validation can be manipulated via prompt injection. Monitoring without revocation capability leaves you watching attacks unfold without recourse.
The right posture is depth: each layer assumes the others might fail, and provides an independent check. Get all twelve layers right, and you've built a wallet infrastructure that's meaningfully harder to attack than anything that came before it.
