# Transact with an Aegis smart account **Humans:** if the Aegis account and runtime handoff are not ready yet, start with the [human Quick Start](https://app.projectaegis.ai/docs/quickstart). Its guided setup creates the account and guardrails, helps fund it and choose a route, then downloads one handoff ZIP. Once that is complete, paste this URL to the agent and give it the ZIP path or installed handoff directory. Do not paste file contents or keys into chat. The [Agent Golden Path](https://app.projectaegis.ai/docs/agent-golden-path) is the short visual handoff for what happens after setup. This document is the detailed agent runbook. **Claude:** this is your runbook. Follow it in order. --- ## What you're doing Someone wants you to execute on-chain transactions from their ERC-4337 smart account. Their funds are protected by contract-level policy — spending limits the smart account checks in its EntryPoint validation/execution path or its v16 signer-only direct execution path before protected funds move. You cannot exceed them, and neither can a bug in your code. That makes your job narrow and specific: **work inside the guardrails, verify before acting, and never surprise the operator.** A denial is the system working, not a problem to route around. This runbook starts after the operator has prepared the account, active policy, minted permission, runtime configuration, and a verified gas/submission route. If the operator cannot provide a prepared handoff ZIP, installed handoff directory, or legacy descriptor path, send them to the [human Quick Start](https://app.projectaegis.ai/docs/quickstart) instead of asking for a private key or seed phrase. --- ## 1. Ask for the handoff and install it safely Prefer an `aegis.handoff.v1` ZIP produced by the dashboard. A previously installed handoff directory or legacy runtime descriptor is also supported. Ask separately for confirmation that the smart account has the assets the operator expects you to use. Install and verify a ZIP before reading any configuration: ```bash python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip python -m pip install "aegis-smart-accounts>=0.5.0" aegis handoff install /path/to/my-agent-aegis-handoff.zip \ --destination ~/.aegis/handoffs/my-agent aegis handoff check ~/.aegis/handoffs/my-agent ``` The installer verifies the schema and every SHA-256 integrity entry, rejects path traversal, links, unexpected files, duplicate archive entries, and unsafe secret permissions, then reports the exact runtime descriptor path. Do not manually unpack the ZIP into a source repository. If verification fails, stop and ask the operator for a fresh package. Read `START-HERE.md` and `handoff.json`. Pass the reported descriptor path to a secure subprocess; do not open or print anything under `secrets/`. The non-secret runtime descriptor uses `AEGIS_RUNTIME_SECRET_FILE` to reference `secrets/runtime-secrets.env` and, for a local signer, `AEGIS_SIGNER_SECRET_FILE` to reference its separate signer-secret file. The runtime-secret file may contain RPC/bundler URLs and an expiring `aegisrt_` key when managed submission or backend diagnostics are enabled. The public descriptor records its expiry. A valid package never contains an owner key or wallet-scoped `aegissa_` management key. If it does, stop and reject it. The manifest names one exact primary permission. Do not replace it with a newer permission. If the handoff is legacy and the permission is missing or several valid permissions are ambiguous, ask the operator to select a primary permission in the dashboard and create a fresh package. Every runtime name is `AEGIS_`-prefixed. Read `AEGIS_ROUTE_MODE` first. `direct` requires an RPC, a v16 capability pass, and signer gas; it intentionally has no bundler URL. `bundler` requires an explicit `AEGIS_BUNDLER_URL`. A blank bundler URL never enables legacy `EntryPoint.handleOps` self-submit. The Python SDK can also provision through resumable `aegis init`, but `~/.aegis/init-state.json` contains public identifiers only and does not create signer material. An SDK/API-first operator must build the same handoff or provide equivalent secure files before execution. The MCP server is a long-running stdio process. Running `npx -y @project-aegis/mcp-server` by itself normally prints nothing and waits for an MCP client; that is expected. Register it with the client instead: ```json { "mcpServers": { "aegis": { "command": "npx", "args": ["-y", "@project-aegis/mcp-server"], "env": { "AEGIS_API_URL": "${AEGIS_API_URL}", "AEGIS_ROUTE_MODE": "${AEGIS_ROUTE_MODE}", "AEGIS_RPC_URL": "${AEGIS_RPC_URL}", "AEGIS_BUNDLER_URL": "${AEGIS_BUNDLER_URL}" } } } } ``` For Claude Code, the shortcut is `claude mcp add aegis -- npx -y @project-aegis/mcp-server`. The MCP server supplies diagnostics and authenticated API tools; it does not hold the bot key or sign/submit a general transaction. The SDK or another execution library handles that part. This default MCP configuration intentionally contains no wallet-scoped management key; use only an expiring, agent/account/chain-bound runtime credential for managed submission, optional reads, or reporting. Add `AEGIS_RUNTIME_API_KEY` to the MCP environment only after such a key has been issued; do not leave a literal placeholder value. `AEGIS_RPC_URL` handles chain reads and, on the direct route, ordinary EVM submission. Guided setup uses a credential-free public RPC by default; it may be rate-limited, so ask the owner for a dedicated agent-specific RPC when capacity or latency matters. On `AEGIS_ROUTE_MODE=bundler`, the separate `AEGIS_BUNDLER_URL` accepts ERC-4337 methods and broadcasts the outer `EntryPoint.handleOps` transaction. SDK/MCP send a scoped managed-route key only to the exact Aegis API origin and `/api/v1/runtime/bundler/` path, never to the read RPC, a custom bundler, or a lookalike URL. If the handoff is incomplete, direct the operator back to dashboard setup; do not ask the model to assemble or inspect signer secrets. Secret files belong outside every Git repository at mode `0600`; the installer defaults to `~/.aegis/handoffs/`. --- ## 2. Never read the private key Pass the descriptor path to the SDK/runtime subprocess. Let that subprocess load the explicitly referenced signer file and pass the key only to the signing library. Do not open the signer file, print it, echo it, or include it in output. Anything you read into the conversation enters the transcript. Do verify it's the right key: check that `Account.from_key(...)` derives `AEGIS_BOT_ADDRESS`, and that the account's on-chain `signer()` matches. A wrong key should fail immediately and legibly, not later as a cryptic error (§6). --- ## 3. Report the limits before you write any code Read the reference first: **** If the runtime can install packages, use the released interfaces rather than a repository checkout: ```bash python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip python -m pip install aegis-smart-accounts aegis --help npx -y @project-aegis/mcp-server ``` If the MCP server is available, call the MCP tool `aegis_doctor` first (this is different from the Python CLI command `aegis doctor`). The published server provides these 13 tools: - `aegis_supported_chains` - `aegis_get_contracts` - `aegis_get_defi_support` - `aegis_amount_to_base_units` - `aegis_resolve_account_context` - `aegis_doctor` - `aegis_get_holdings` - `aegis_get_permission_state` - `aegis_decode_error` - `aegis_get_userop_receipt` - `aegis_preflight_transaction` - `aegis_api_endpoints` - `aegis_api_request` The MCP tools diagnose, read, preflight, decode errors, and make explicitly authenticated API calls; they do not sign or submit a general UserOperation. An exact preflight returns `allowed` or `denied` only after that live chain/generation tuple is promoted in Aegis's generated capability registry. Until then it returns `unknown` with `POLICY_NOT_CHECKED`; treat that as a stop, not as permission to submit or to bypass policy with the owner wallet. Then establish and **show the operator** all of this: | Check | How | |---|---| | Signer is correct | account `signer()` == `AEGIS_BOT_ADDRESS` | | Which permission | `handoff.json.agent.permissionId` == `AEGIS_PERMISSION_ID` == the selected on-chain binding | | What may be spent | `getPermissionAssets(permissionId)` | | How much, how often | `getRemainingQuota(permissionId, agentId, asset)` → `(remainingVolume, remainingTxCount)`; verify permission binding separately | | Where it may go | `getAllowedRecipients(permissionId)` — empty means anywhere | | Gas available | the account's runtime `entryPoint()` and `EntryPoint.balanceOf(account)`, separate from account balance | | Protocols are real | `eth_getCode` on every address you plan to call | Use the package's exact primary permission. If it is no longer active, or a legacy handoff has several active permissions and no explicit primary, stop and ask the operator to select one in the dashboard. Never pick "the newest." **The on-chain enforcer is the source of truth** — holding a token is not the same as being permitted to spend it. The permission ID is not the agent UUID and is not derivable from it — `getPermission(agentUuid)` reverts. The operator should resolve and include the permission ID during provisioning. If it is missing, ask the operator to finish that trusted control-plane step; do not ask for a wallet-scoped management key as part of the runtime handoff. An expiring, agent/account/chain-bound runtime credential may be used only for its documented scopes: `bundler:use` for the exact Aegis-managed route, plus optional read or reporting access. It is never a management credential. Summarize it in plain language before proceeding: *"You can send USDC up to 10 per transaction and 30 per day, to any address; ETH and WETH up to 0.02 per transaction."* If you can't say that sentence yet, you aren't ready to build. Use the included calls-form action with `aegis action run`. On the direct route, the SDK probes the account capability, builds `executeAsAgent` or `executeBatchAsAgent`, simulates, estimates gas, checks the signer gas balance, and signs without broadcasting; `--execute` is always explicit. On the bundler route, `SafeUserOperation.from_userop(...)` follows dry-run → review → explicit submit → receipt, including a fresh signature after estimated gas fields. The older `sign_userop`/`estimate_userop`/`submit_userop` functions remain compatibility primitives and must not be used to bypass lifecycle state checks. The SDK wraps already-encoded target calldata; it does not encode a Uniswap, Aerodrome, or other DEX call. Use the exact target ABI, then run target gas estimation and Aegis preflight. The bundler safety sequence is build → sign → estimate → re-sign → explicit submit. Never reuse the pre-estimate signature. --- ## 4. Verify funding and gas A smart account is a contract, and **a contract cannot originate a transaction.** So gas and money live in different places: | Bucket | Pays for | Filled by | |---|---|---| | Account native balance | policy-protected spendable value; it may also cover first-op bundler prefund | the operator | | Bot EOA balance | ordinary network fees on the direct route; outside account policy | the operator, in small refills | | Account **EntryPoint deposit** | gas on the bundler route, and only gas | the operator, or a later policy-checked account operation | Native ETH in the account is still the account's spendable value. In this implementation, `validateUserOp` can also send `missingAccountFunds` from that balance to EntryPoint when a correctly signed UserOperation is being submitted. That is why an account with native ETH can sometimes execute its first operation even while `EntryPoint.balanceOf(account)` is zero. It is not a guarantee: the balance must cover the estimated prefund, and the submission route still needs to exist. For predictable repeated execution, the operator may keep a standing EntryPoint deposit. Guided setup can call `EntryPoint.depositTo(account)` from the connected owner wallet. After a submission route already exists, the account can also top the deposit up from itself as a normal policy-checked operation: ``` execute(ENTRY_POINT, amountWei, AEGIS_MAGIC‖permissionId‖depositTo(account)) // depositTo(address) = 0xb760faf9 ``` This bills the **native** cap and daily quota like any other ETH spend, so budget for it. **Verify the execution route before building:** - **Direct (recommended when the handoff selects it):** require `supportsDirectSignerExecution() == true`, use `AEGIS_RPC_URL`, and keep a small native balance on the bound signer. This usually avoids the extra bundler/UserOperation hop. Submit one transaction at a time unless you coordinate pending EOA nonces. If submission is ambiguous, inspect the reported deterministic transaction hash before retrying. - **Bundler:** use the handoff's `AEGIS_BUNDLER_URL`. It is normally Aegis-managed on Base, Base Sepolia, and Sepolia; an owner may instead provide a custom compatible endpoint. The bundler broadcasts and is reimbursed from the account's prefund. The bot EOA can stay empty. A standing `depositTo(account)` reserve is recommended, but a sufficiently funded account may cover the first operation through `missingAccountFunds`. - **Missing requirements for the selected route:** stop and ask the operator to complete guided setup. Never silently switch routes. Explain which route and funding bucket are present. Never confuse the smart account's spendable balance, its EntryPoint gas reserve, and the bot EOA's outer-transaction balance. **Experimental path warning:** the IntentRegistry and `/api/v1/intents` endpoints are not end-to-end tested and are not part of this runbook. New accounts use `MODE_POLICY_ONLY`; do not introduce intent envelopes into a production execution unless Aegis explicitly marks them supported. --- ## 5. Probe, preflight, then ask Two free checks. Run both on every unfamiliar call shape. First read the account's immutable `spendResolver()` and `entryPoint()` bindings; those runtime values are authoritative for this account. Then call the bound resolver's **`resolveSpend(account, target, ethValue, data)`** — a `view` call returning exactly what the enforcer will see. It tells you whether the call is decoded at all (`recognized=false` is **denied**, never unchecked) and which asset it's billed to — often not the one you'd guess. Wrapping ETH bills the *native* cap, not WETH. Selectors also collide across protocols, so probe rather than reason from the ABI. Run the exact no-broadcast route simulation. Direct mode uses `eth_call` plus `eth_estimateGas` from the bound signer to the account's enforced direct method. Bundler mode estimates the signed UserOperation against EntryPoint. A policy denial surfaces as a revert before broadcast on either route. Then: **show the operator the preflight result and wait for their go-ahead before broadcasting.** Every time. After it lands, report the transaction hash and what the quota actually decremented. Two rules that bite in practice: - **A standalone `approve` is rejected.** Aegis permits no standing allowances. Any flow needing approval — router swap, LP mint, deposit — must batch the approve *and* the consuming call into one `executeBatch`, each inner call carrying its own `AEGIS_MAGIC‖permissionId‖calldata` prefix. - **Quota is gross outflow, not net.** Reversing a transaction doesn't refund the allowance. A wrap and an unwrap leave balances unchanged while consuming quota in both directions. - **Quota windows are fixed UTC calendar days.** The first recorded spend anchors the usage window to that UTC day; grant time does not. Permissions for the same on-chain agent and asset share usage. A zero `maxDaily` or `maxTxCount` is unbounded even though `getRemainingQuota` returns zero for that field. --- ## 6. When something is denied On the bundler route, **`AA24 signature error` may not mean a bad signature.** `validateUserOp` returns a signature-failure code when the *policy* check fails, so denials wear a misleading mask. The code arrives ABI-encoded inside `FailedOp(uint256,string)` (`0x220266b6`) — decode it or you'll never see it. If `signer()` already matched, **stop looking at the key.** The cause is one of: per-transaction cap, daily quota, an asset not on the allowlist, an unsupported selector, or an unbatched approve. Note that a balance may legitimately exceed its per-tx cap — caps are inclusive, so under a 10 USDC cap exactly `10.000000` passes and `10.000001` does not. Denials leave **no on-chain record** — reverted logs roll back. Report each one to `POST /api/v1/audit/denied` (send `action` as a JSON object, not a string) so blocked attempts still appear in the operator's dashboard. Explain the denial in terms of the limit that caused it. Do not work around it, do not suggest raising the cap unless asked, and never propose using the owner key instead. --- ## Reference - Human setup: - Agent Golden Path: - Skill: - Full LLM reference: - Python SDK: `pip install aegis-smart-accounts` - MCP server: `npx -y @project-aegis/mcp-server` - Dashboard: