Smart Wallet Security Best Practices for 2026

Joan Alavedra24 min read
Smart Wallet Security Best Practices for 2026

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:

LayerWhat It ProtectsFailure Mode
Key storageThe private key itselfExtraction, theft
Signing policyWhat the key can signOverpermissioned signing
validateUserOpWhether a UserOperation is validPermissive validation drains gas
Onchain permissionsWhat the account executesUnlimited spend, wrong contracts
RecoveryAccess after key lossPermanent lockout
Session key scopeTemporary key limitsSession key abuse
Delegation (EIP-7702)Which implementation runsFront-running, takeover
Module isolation (ERC-7579)What modules can doPrivilege 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:


_10
User input (passkey / biometric)
_10
_10
_10
TEE or HSM (key lives here — never exported)
_10
_10
_10
Signing happens inside the secure enclave
_10
_10
_10
Signed 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
_25
struct SessionKeyParams {
_25
address key;
_25
uint48 validAfter;
_25
uint48 validUntil;
_25
uint256 ethLimit;
_25
address[] whitelist;
_25
bytes4[] allowedSelectors;
_25
uint256 operationLimit;
_25
}
_25
_25
SessionKeyParams 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
_25
account.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 ethLimit and token spending caps appropriate to the session's maximum expected value
  • Whitelist target contracts; don't issue session keys with ANY_TARGET
  • Use allowedSelectors to 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
_31
function 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
_31
function 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.


_10
function 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:

FieldProtection
ethLimitMaximum ETH any single key can spend
spendTokenInfo.limitMaximum ERC-20 token amount
whitelistOnly allowed target contracts
allowedSelectorsOnly allowed function signatures
limitMaximum number of operations
validUntilHard 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
_10
period = MONTH
_10
limit = 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:


_10
Transaction 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:


_10
Transaction request
_10
_10
_10
Policy engine (off-chain): rate limits, blocklists, fraud rules
_10
│ Rejected? → Abort
_10
_10
Smart account (on-chain): spending limits, contract whitelist, time bounds
_10
│ Rejected? → Revert
_10
_10
Transaction 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.

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:

  1. During onboarding, prompt users to add a backup passkey ("Save this in case you lose your device")
  2. Register the backup passkey as a secondary authorized key on the smart account
  3. 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:


_10
Guardians: [friend_wallet, family_wallet, openfort_recovery_service]
_10
Threshold: 2 of 3
_10
Delay: 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
_12
function initiateRecovery() external onlyGuardian {
_12
recoveryInitiatedAt = block.timestamp; // Resets every time!
_12
}
_12
_12
// BETTER: require threshold to initiate
_12
function 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
_28
function _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:

  1. Revoke the session key on-chain immediately
  2. Invalidate any off-chain session tokens
  3. Log the revocation event for audit purposes

Key hierarchy: Don't use the same key for everything. A good key hierarchy looks like:


_10
Master 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:


_15
function 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 initWithSig or 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 TypeRoleRisk if Misconfigured
ValidatorVerifies signatures during validateUserOpFalse positives → unauthorized execution
ExecutorExecutes transactions on behalf of the accountUnrestricted executor → full account takeover
Fallback HandlerHandles calls to undefined functionsMalicious fallback → unexpected behavior
HookPre/post execution checksBypassed 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
_13
function 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
_13
const 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:


_13
Agent request: "swap 10,000 USDC for ETH on Uniswap"
_13
_13
_13
Policy 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
_13
Sign 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.

Share this article

Keep Reading