Architecture Overview
A technical look at how Aegis enforces what an AI agent can do on-chain — at the contract level, not by trusting the agent or the backend.
Current contract generation: v14. Live on Base mainnet, Base Sepolia, Sepolia, and Arc Testnet. ERC-8004 identity and policy, ERC-4337 account abstraction, EntryPoint v0.6.
The problem, and the guarantee
When you give an autonomous agent access to a wallet, the conventional safeguard is to trust that the agent's own code stays within its mandate. That provides no independent guarantee: a bug, a prompt injection, or a compromised key turns “should not” into “just did.”
Aegis moves the boundary into the contract. Every agent acts through an ERC-4337 smart account whose validateUserOp consults an on-chain PermissionEnforcer before anything executes. A transaction that violates policy reverts inside validation — it never reaches the target contract. The agent cannot bypass this, because the EntryPoint always calls validation first and the account is immutable.
The result is a permission, policy, and audit layer for agents — conceptually “IAM + CloudTrail” for autonomous on-chain activity, with the enforcement guarantee rooted in the chain rather than in software.
System at a glance
Off-chain coordination, on-chain enforcement
Dashboard / API (Next.js frontend + Go / Chi REST backend)
│ │ │
Policy Engine Pre-flight API Audit + Indexer
(policies, POST /validate (off-chain events +
permissions) (simulation) on-chain event poll)
│ │ │
└────────────────┼────────────────┘
│
Base mainnet / Base Sepolia / Sepolia / Arc Testnet
│
┌──────────────┬───────────────┼────────────────┬──────────────────┐
│ │ │ │ │
IdentityRegistry PolicyRegistry PermissionEnforcer SpendResolverRegistry
(ERC-8004) (ERC-8004) (constraints) (calldata decoders)
│ │ │ │
└──────────────┴───────┬───────┴────────────────┘
│
AgentSmartAccount (ERC-4337) ◄── AgentAccountFactory (CREATE2)
│
EntryPoint v0.6The frontend and backend coordinate and observe. They do not — and cannot — enforce. Enforcement lives entirely in the contracts on the right.
Identity & policy (ERC-8004)
IdentityRegistry stores each agent as a bytes32 identity bound to an owner. Deactivated agents fail isAgentActive at enforcement time and can execute nothing.
PolicyRegistry records a policy as the keccak256 hash of its document (the readable terms live off-chain), and a permission as a revocable, time-windowed link between a policy and an agent.
Enforcement engine
PermissionEnforcer holds the constraints that give a permission teeth. Limits are expressed in each asset's raw base units — no price-oracle normalization in the policy path.
- • Per-asset
maxPerTx,maxDaily,maxTxCount - • Allowed actions / protocols / chains (empty = unrestricted)
- • Delegation ceilings — a parent agent caps a child's budget
- • Daily volume resets at 00:00 UTC
checkAction is the view-only check used during validation; consumeAllowance is the atomic check-and-record run during execution.
The enforcement path (ERC-4337)
Where a violating transaction dies
Agent signs a UserOperation
│
▼
Bundler (Alchemy / Pimlico) ──► EntryPoint v0.6
│ calls
▼
AgentSmartAccount.validateUserOp()
├── verify ECDSA signature (EIP-191, the agent's signer key)
├── decode "what moves" via SpendResolverRegistry (view)
├── PermissionEnforcer.checkAction() (view)
│ └── constraint violated ──► revert (never executes)
└── pay EntryPoint prefund from the account's balance
│ (only if valid)
▼
AgentSmartAccount.execute()
├── PermissionEnforcer.consumeAllowance() (atomic check + record)
└── call target contractInner calldata carries an AEGIS_MAGIC prefix so the account can extract the relevant permissionId (and, in intent mode, the intentHash).
The connected owner retains a privileged direct path (raw calldata, no enforcement) — the documented recovery / withdrawal route for the fully-trusted principal. The agent / EntryPoint path is the one that is policed.
Spend Resolver Registrynew in v14
Adding a DeFi protocol no longer means redeploying every account
To enforce a per-asset limit, the account first has to know which assets a call actually moves — and DeFi calldata is protocol-specific. Earlier generations hard-coded every selector (Uniswap, Aerodrome, …) into the account bytecode, so supporting a new protocol meant redeploying accounts.
v14 externalizes that. The SpendResolverRegistry maps a function selector to a decoder adapter and normalizes any call into one canonical shape:
ResolvedSpend {
amount0, token0, // primary asset leaving the account
amount1, token1, // second asset (e.g. LP add)
recognized, // false ──► account rejects the call
requireRecipientSelf, // proceeds (LP / collect) must return to the account
recipient
}Decoding is two-tier: built-in handlers cover ERC-20 primitives, Uniswap V3, Aerodrome, and LP operations; the CommonDeFiSpendResolver adapter adds ~40 more selectors (Universal Router, Multicall, Permit2, Curve, Balancer, Aave, Compound v2/v3, Beefy, V2 routers) and recurses back into the registry for nested calls like multicall.
The division of labor is the key point: the resolver answers “what will move?”; the PermissionEnforcer answers “is this agent allowed to move that much, today?” New protocol coverage is a registry update, not an account migration. As an additional guard, a single call may move at most two outgoing assets.
Account modes
Accounts deploy in one of three enforcement modes. The default is MODE_POLICY_ONLY.
- MODE_POLICY_ONLY — PermissionEnforcer constraints only. The current default for all production accounts.
- MODE_INTENT_ONLY — an IntentRegistry envelope only (a budget with a cap, a use count, and a validity window).
- MODE_BOTH — intent reservation first, then policy check and usage recording.
The intent layer is an optional envelope model: a signed intent reserves budget atomically before a call and commits or refunds on the outcome. The full payload is stored off-chain (S3) with only its hash committed on-chain. It is currently in test mode — deployed but not yet enabled for production accounts while it is validated end-to-end; all production accounts run MODE_POLICY_ONLY.
Audit & on-chain indexer
The audit log merges two sources. Off-chain entries are written synchronously when state-changing API calls succeed (agent registration, policy/permission changes, pre-flight validations). On-chain entries come from an indexer that polls every 12 seconds and writes each event with source='onchain', a tx_hash, and a block_number. Indexer position is checkpointed so restarts never replay.
Eight event types are indexed:
EnforcementResult · ConstraintViolation · UsageRecorded · Executed · AccountCreated · IntentRegistered · IntentStateChanged · X402PaymentAuthorized
Dashboard entries link straight to the block explorer, and the full log exports to JSON or CSV — so an enforcement decision traces back to the raw transaction that produced it.
Agent-native integration
Pre-flight checks, MCP, and x402
Pre-flight validation. POST /api/v1/validate (and a batch variant) runs the same constraint logic off-chain so a dashboard or SDK can predict an outcome before submitting. It is a simulation — the binding decision is always the on-chain one.
MCP server. A published package (@project-aegis/mcp-server) gives any MCP-capable agent typed tools to discover deployments and DeFi coverage and to run aegis_preflight_transaction, which calls the live PermissionEnforcer.checkAction and reports ERC-4337 prefund readiness — without submitting anything.
x402 payments. Stablecoin payment authorizations (EIP-3009 transferWithAuthorization, settled via the account's EIP-1271 signature) are routed through authorizeX402Payment so they are policy-checked and counted against the same limits as direct transfers. The backend relays the bot-signed UserOp gas-sponsored; the owner key is never needed at runtime.
Trust boundary & threat model
What each component is — and is not — trusted to do
| Enforced on-chain (trustless) | Trusted off-chain |
|---|---|
| Signature validation (EIP-191) | Policy document content (committed as a hash) |
| Per-asset value, daily volume, and tx-count limits | Intent payload content (stored in S3; hash on-chain) |
| Action / protocol / chain allowlists | Calldata encoding and metadata |
| Intent budget, use count, validity window | Convenience: pre-flight simulation results |
| Delegation ceilings; recipient-self constraints |
A compromised agent cannot
- • Exceed per-tx, daily-volume, or tx-count limits for any asset
- • Touch a protocol, chain, or action outside its allowlist
- • Approve more than the exact amount the next swap pulls
- • Overspend an intent envelope or act outside its window
- • Redirect LP / collect proceeds away from the account
Each of these reverts inside validation or execution — funds never move.
A compromised backend cannot
- • Forge a signature the account will accept
- • Bypass the on-chain constraint check with a bad permissionId
- • Under-report usage — quota is tracked in contract state
- • Raise a child agent's budget above a parent's ceiling
- • Move funds at all beyond what policy already permits
Worst case it can refuse to act or mis-encode a call — which simply reverts.
Deployments
The v14 contract generation is deployed across four networks — Base mainnet (the canonical production deployment), Base Sepolia, Sepolia, and Arc Testnet — all on EntryPoint v0.6. The deployed contracts are verified on-chain, so their source is viewable and auditable on the block explorer.
Per-chain addresses for IdentityRegistry, PolicyRegistry, PermissionEnforcer, PriceOracle, the fee manager, SpendResolverRegistry, CommonDeFiSpendResolver, and the account factory are listed under Docs → Smart Contracts.