Technology

Every contract and service is built from a well-known standard rather than something invented for the occasion, each with the reason it was chosen and the file that uses it, so you know exactly which primitives you are trusting when you hand an agent a budget. The design rationale is in ARCHITECTURE.md.

AIOUM is a decentralized, metered network for AI agent services with a marketplace at its center. Nodes run services (an MCP tool server, a resold API, a model) and front the upstream costs; agents pay per-call micropayments in USDC / USDT / ETH. The volatile ZAI token only secures nodes (stake/slash) and governs the DAO, never pays. That single split, stablecoins for value, ZAI for security, drives most of the technology choices, and it is why the price of a call never moves with the price of a token.


At a glance

LayerTechnologyWhere
ChainThe AIOUM network: an EVM chain. Deployment ladder in offchain/networks.json: Anvil (local) → Base Sepolia (staging) → Base (production, chain id 8453)offchain/networks.json
Chain (local development)Anvil, chainId 31337offchain/, forge test
ContractsSolidity 0.8.28, OpenZeppelin v5.1, Foundrysrc/, foundry.toml
TokenERC-20 + Votes + Permit + Capped + Burnablesrc/token/AIOUMToken.sol
GovernanceOZ Governor + TimelockControllersrc/governance/AIOUMGovernor.sol
Identity / rep / validationERC-8004 registriessrc/registry/
Per-call authEIP-712 PaymentVouchersrc/payments/PaymentManager.sol, offchain/src/sdk/voucher.ts
Gasless USDC fundingEIP-3009 receiveWithAuthorizationsrc/payments/, src/external/IERC3009.sol
Any-token fundingPermit2 (Uniswap)src/payments/, src/external/IPermit2.sol
Deposits from any chainLI.FI routes to USDC + a CREATE2 DepositForwarder per AI walletsrc/payments/DepositForwarder.sol, offchain/src/deposits/
HTTP micropaymentsx402 (HTTP 402)offchain/src/node/, offchain/src/facilitator/
Service routingWeighted-random (stake + reputation + channel headroom), cheaper-preferred, manual pinprivate-node executor (offchain/src/private-node/node-select.ts); routing
Declared modelOperator-set model string per service (a claim, not a proof)/services, tools/list, /health
SchedulingCron in the private node, budget-capped agent walletprivate node
Account abstractionERC-4337 (account-abstraction library v0.9.0; the canonical v0.7 EntryPoint on live chains): smart account + session keys + signature-gated paymastersrc/aa/; account-abstraction
DeploymentHardened facilitator, env-driven live-chain deploy, TLS (Caddy), subgraph docker-composedeploying.md
Off-chain stackNode 22 + TypeScript (ESM) + ethers v6 + expressoffchain/src/
IndexerThe Graph (graph-ts / AssemblyScript)subgraph/

Chains

The AIOUM network: Base, an EVM L2

The AIOUM network runs on Base, an EVM L2; the same contracts deploy on any EVM chain. The deployment ladder is in offchain/networks.json: Anvil (local, chainId 31337) → Base Sepolia (staging, chainId 84532) → Base (production, chainId 8453). Three reasons, each about the cost of a single call. Micropayments only make sense if a settle transaction costs a fraction of a 1-USDC call, and an L2 keeps gas low and predictable instead of competing for L1 blockspace. Assets cross the chain's canonical bridge, and sessions settle in native (Circle) USDC on Base, so the dollar your agent spends is the dollar you funded it with. And the L2 runs the same EVM the contracts are compiled for, byte for byte, so one Foundry build, one test suite and one deploy script (script/Deploy.s.sol) target the local development network and production alike.

Payments still settle in stablecoins/ETH on whichever chain the deployment targets; the chain choice is about cost and tooling, not about what asset moves.

Local development: Anvil (chainId 31337)

Local development and the end-to-end demo run against Anvil, the Foundry node, on chainId 31337 (RPC http://127.0.0.1:8545). Anvil is instant, fork-free, deterministic and ships the same EVM as the AIOUM network, so what you rehearse on your computer is what you run on the network. The off-chain stack points at it by default (offchain/src/config.ts), and the full facilitator → node → settle flow is asserted live on Anvil with on-chain balance deltas matching the fee split (see SECURITY.md).


Smart-contract toolchain

Solidity 0.8.28

All contracts in src/ compile with Solidity 0.8.28 (foundry.toml → solc = "0.8.28"), the version the ERC-4337 account-abstraction library requires (^0.8.28) (see the account-abstraction guide). Checked arithmetic (0.8.x) removes a whole class of over/underflow bugs from the reward accounting that decides what an operator and its stakers are owed after each of your agent's sessions. The build uses the IR pipeline (via_ir = true), the optimizer (optimizer_runs = 200) and evm_version = "cancun".

OpenZeppelin Contracts v5.1

Every standard primitive, from the ERC-20 extensions through Ownable, ReentrancyGuard, SafeERC20, EIP712, ECDSA and EnumerableSet to Governor and TimelockController, comes from OpenZeppelin v5.1, remapped in foundry.toml:

remappings = [
    "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    "forge-std/=lib/forge-std/src/",
]

Leaning on audited OZ implementations is a deliberate security posture: battle-tested code for everything that isn't AIOUM-specific, custom logic only for the staking, payment and dispute mechanics. SafeERC20, ReentrancyGuard and checks-effects-interactions apply throughout, so the paths that move your deposit have the fewest surprises left in them.

Foundry (forge)

Testing and deployment use Foundry. forge test runs 226 tests across 11 suites (AIOUMProtocol.t.sol 42, EconomicsUpgrade 54 and Verifiability 50, both of which re-run the 42 protocol regressions under their changed setup, adding 12 and 8, CommitRevealArbitrator 26, ValidationQuorumArbitrator 23, VerifyingPaymaster 13, AccountAbstraction 8, DelegatedSlashing 3, Fuzz 6, Governance 1). The suites include a regression test for every drain-level bug found in earlier versions of the protocol, fuzz and property tests over the staking accounting, and the ERC-4337 session-key flow driven through EntryPoint.handleOps (see SECURITY.md and the account-abstraction guide). Deployment is a Solidity script (script/Deploy.s.sol). Solidity-native tests share the network's exact compiler and EVM semantics, so what passes in the suite is what runs under your agent.

forge build
forge test            # 226 passing across 11 suites

Token & governance standards

AIOUMToken — ERC-20 + Votes + Permit + Capped + Burnable

src/token/AIOUMToken.sol composes five OZ extensions, each chosen for a specific job:

contract AIOUMToken is ERC20, ERC20Burnable, ERC20Capped, ERC20Permit, ERC20Votes, Ownable

ERC-20 is the base token used for staking. ERC20Votes adds checkpointed voting power, where the DAO reads voting weight from. ERC20Permit (EIP-2612) allows gasless approvals via signature, so staking flows don't always need a separate approve transaction. ERC20Capped sets a hard supply cap at deploy, and ERC20Burnable lets supply be burned, by governance or by slashing policy. Ownable completes the set; its owner is the TimelockController (the DAO), never an EOA.

AIOUMGovernor + TimelockController

src/governance/AIOUMGovernor.sol is a standard OZ Governor stack: GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, and GovernorTimelockControl, executing through a TimelockController. The parameters baked into the contract are a voting delay of about 1 day (7200 blocks), a voting period of about 7 days (50400 blocks), a proposal threshold of 100_000e18, and a 4% quorum (GovernorVotesQuorumFraction(4)).

The point: no privileged function is owned by an EOA. ZAI holders vote, the Governor queues, the Timelock enforces a delay, and only then does anything execute. A guardian multisig holds the Timelock proposer/canceller roles during bootstrap and progressively decentralizes to full Governor control. Every fee, stake floor and window that touches your agent's money changes only through that public path.


ERC-8004 — identity, reputation, validation

AIOUM uses ERC-8004-style registries for the agent-trust layer (src/registry/), so identity and reputation are portable, on-chain and resolvable rather than locked in one app's database. IdentityRegistry.sol gives one portable on-chain identity per address, for agents, nodes and clients alike. ReputationRegistry.sol holds settlement-gated feedback: a client can only rate a node it actually paid, because the PaymentManager records the settlement, and disputes add penalties on top. Reputation is Sybil-resistant and earned, not self-asserted. The agent runtime spends that allowance after every settled session, so the reputation next to a listing is issued by agents from real results rather than typed in by people. ValidationRegistry.sol lets staked validators attest to job-result hashes, the verification hook for high-value jobs. It pairs with ValidationQuorumArbitrator, a stake-weighted validator quorum that can replace the DAO-multisig arbitrator; its minValidatorStake is DAO-tunable via setMinValidatorStake (onlyOwner).

These registries are what the marketplace ("an app store for agent services") indexes and what an agent searches when it finds tools on its own. Result-hash anchoring pairs with them: PaymentManager.commitResults(sessionId, resultsRoot) lets the operator anchor a root over the served calls on-chain — each leaf keccak(index, requestHash, responseHash, toolIdHash) — so later re-execution / TEE / zkML verification (or a dispute) can point at exact bytes.


Payment standards

Payments are asset-agnostic and converge on one escrow in src/payments/PaymentManager.sol. Four standards cooperate: EIP-712 authorizes each call, EIP-3009 / Permit2 / native ETH fund a session, and x402 is the HTTP envelope. Your agent funds a session once, then signs a voucher per call, and every one of those messages travels inside x402.

EIP-712 PaymentVoucher — per-call authorization (the heart of payment)

A session is prepaid; for call n the client signs cumulativeAmount = pricePerCall * n as an EIP-712 typed-data voucher. The type is fixed and identical on-chain and off-chain:

PaymentVoucher(uint256 sessionId,uint256 cumulativeAmount)
domain: name "AIOUMPayments", version "1", chainId, verifyingContract = PaymentManager

On-chain it is verified in PaymentManager (EIP712("AIOUMPayments", "1"), ECDSA recover); the matching signer/verifier lives in offchain/src/sdk/voucher.ts (signVoucher, recoverVoucherSigner). Because the voucher is domain-separated by chainId + the PaymentManager address, the node can only ever redeem an amount the client actually signed. Amounts can't be forged or replayed, a node's worst case for non-payment is a single call, and nobody but you ever asserts how many calls your agent made.

EIP-3009 receiveWithAuthorization — gasless USDC funding

openSessionWithAuthorization funds a session straight from a USDC signature via EIP-3009 receiveWithAuthorization (src/external/IERC3009.sol), the x402 "exact" scheme: the payer signs an authorization naming the contract as recipient, and a relayer submits it and pays gas. The payer needs no ETH and no prior approval. USDC natively supports EIP-3009, so it is the cleanest gasless path for the most common settlement asset, and why an agent funded only in USDC can open its first session without ever holding gas.

Permit2 (Uniswap) — accept any ERC-20, including USDT

openSessionWithPermit2 uses Permit2 permitTransferFrom (src/external/IPermit2.sol) as the universal "accept any token" layer. USDT (and many older tokens) lack EIP-2612/EIP-3009, so Permit2 is the one path that works for any ERC-20: a one-time Permit2 approval, then signature-pulled transfers, relayer-submittable (gasless for the payer). Permit2 is canonical and deployed at the same CREATE2 address on every EVM chain (0x000000000022D473030F116dDEE9F6B43aC78BA3).

Two more funding paths share the same escrow: native ETH (openSessionETH) and standard approve + transferFrom (openSession).

Deposits from any chain — the layer beneath the funding paths

One layer below all four is where the USDC comes from. A person deposits any major asset on any major chain and it arrives as USDC on this network. The deposit service (offchain/src/deposits/server.ts) takes chains, assets and routes from the route provider, LI.FI, and quotes a route to USDC delivered to the wallet's deposit address. There sits a DepositForwarder, one per AI wallet at a CREATE2 address known before it is deployed, which forwards what arrived: depositFeeBps (1% at the default DEPOSIT_FEE_BPS) to the treasury, the rest to the AI wallet. Anyone may sweep it and there is nothing to steal, only to forward, so the conversion layer adds no custody in front of the escrow. Funding walks the flow from the person's side.

x402 — HTTP 402 micropayments

x402 is the HTTP layer that ties signed payments to API calls. On tools/call with no or insufficient payment the node returns a JSON-RPC error { code: 402, message, data:{ required, asset, sessionId } }, the x402 signal that payment is required (offchain/src/node/server.ts). The facilitator (offchain/src/facilitator/) is the non-custodial x402 relayer that verifies and submits EIP-3009 authorizations (POST /open/eip3009) and pays their gas. It never holds funds, because the authorization names the contract/node as recipient, so anyone can run one (or several) without custody risk. Protocol details: offchain/SPEC.md.

ERC-4337 smart accounts and session keys

src/aa/, built on the v0.9.0 account-abstraction library, is the machinery behind the budget you give an agent; the account-abstraction guide walks it end to end. The ERC-4337 smart account, AIOUMAccount, is a BaseAccount holding budgeted, expiring session keys: the owner authorizes a key once (spend cap + expiry), and the key opens prepaid sessions and signs per-call vouchers autonomously, with the cap enforced on-chain. Through ERC-1271, AIOUMAccount implements isValidSignature, so a session key's voucher counts as the smart account's; PaymentManager.settle checks every voucher with OZ SignatureChecker, routing EOA clients to ECDSA and contract clients to ERC-1271, so EOA and smart-account clients share one unchanged settle path. AIOUMAccountFactory is the CREATE2 deployer that gives each account a counterfactual address for the ERC-4337 initCode flow. AIOUMVerifyingPaymaster is the network's gas sponsor, so agents never hold the chain's gas token: it sponsors only UserOps carrying a time-boxed signature from the protocol's sponsor signer over the op, its gas budget, the chain and the paymaster instance; a live-chain deploy installs it whenever SPONSOR_SIGNER_ADDRESS is set, and USE_VERIFYING_PAYMASTER=1 selects it on the local development network. AIOUMPaymaster is the local development paymaster, which sponsors every op.

8 forge e2e tests drive real UserOps through EntryPoint.handleOps (test/AccountAbstraction.t.sol), and the VerifyingPaymaster suite drives sponsorship gating through the real EntryPoint. The app's Wallet budget page is the front-end: deploy the account, fund it, authorize a key, hand it to the agent.


Off-chain stack

The off-chain code lives in offchain/src/ and is Node 22 + TypeScript as ESM (note the .js import specifiers required by ESM), driven by tsx (offchain/package.json → "type": "module"). The runtime dependencies are deliberately minimal. ethers v6 (^6.13.4) covers all chain interaction: providers, signers, contract calls, and the EIP-712 typed-data signing/verification in offchain/src/sdk/voucher.ts. express v4 (^4.21.1) plus cors provide the HTTP servers for the facilitator (server.ts) and the MCP-style node runtime (server.ts).

The components share one integration surface. The SDK (offchain/src/sdk/) is types.ts, voucher.ts (EIP-712 vouchers), contracts.ts, artifacts.ts, results.ts and results-core.ts (the results-transcript root that commitResults anchors) and index.ts; every other component is built on it. The facilitator (offchain/src/facilitator/server.ts) is the non-custodial x402 relayer, port 8547. The node runtime (offchain/src/node/) hosts the service as an MCP server, watches for sessions, serves calls against incoming vouchers and submits settle, port 8546. The demo (offchain/src/demo/run.ts) runs a gasless open, 3 paid calls and a settle, printing the money flow as it goes. The agent runtime behind the app (offchain/src/agents/) sits on the client side of the SDK: it holds each agent's budget, pays and settles through the SDK, hands an assistant that may find its own tools the free marketplace search it plans with, and files the agent's rating once each session settles.

Run order on the local development network (cwd offchain/, with Anvil on port 8545):

anvil                 # chainId 31337, RPC :8545
npm run deploy        # deploy + wire the full stack
npm run node          # MCP node runtime  :8546
npm run facilitator   # x402 facilitator  :8547
npm run bundler       # ERC-4337 bundler relay :8548 (browser smart-account runs)
npm run demo          # end-to-end paid-call demo

Routing, model, and scheduling

Weighted-random routing

When several nodes offer the same service, the private-node executor picks one by weighted-random selection scored on stake + reputation + open-channel headroom (1 − activeSessions/maxConcurrent), cheaper-preferred, with a >120%-of-average price-outlier guard and an optional per-step max $/call. (The SDK client, asked for a tool by name, takes the top-rated service and breaks ties by price.) A manual pin overrides the router, and the chosen node is recorded on-chain in the session. The node's /services endpoint reports maxConcurrent (= 1 + (totalStake − minNodeStake)/tokensPerSession) and live activeSessions. The reputation term is the one agents write: every settled session ends with the agent's own 0–100 rating of the operator, so a node that answers slowly or errors loses weight in the very router that sends it the next session. See routing; the Marketplace lists every live service with the node serving it and its declared model.

Operator-declared model

Each service carries an operator-declared model string (e.g. claude-haiku-4-5, claude-opus-4-8, deepseek-r1), surfaced off-chain via /services, tools/list, and /health. It is a claim, not a proof; only the on-chain descriptorURI is authoritative. An agent that finds tools on its own may try one listing against another and keep the better result, the practical check on a claim the chain cannot verify.

Scheduled recurring workflows

The user's private node runs cron schedules for recurring agent workflows through a budget-capped agent wallet: a dedicated signer that pays per call up to the job's budget, the same model as ERC-4337 session keys. That is how the morning brief arrives every morning without you touching anything.


Deployment

The off-chain stack ships the pieces a deployment of your own needs (see deploying.md). The hardened facilitator carries production-grade x402 relayer settings. The env-driven live-chain deploy reads USDC_ADDRESS, PERMIT2_ADDRESS, DEPLOYER_PRIVATE_KEY and REAL_CHAIN (which runs the verify-deploy safety gate — Timelock ownership, canonical Permit2, real tokens — and refuses to finish on a blocker), so one script targets a live chain instead of local Anvil. TLS is a Caddyfile terminating HTTPS in front of the node/facilitator. The subgraph compose is a docker-compose bringing up a local graph-node + IPFS + Postgres. The ownership handover, npm run handover (offchain/src/handover.ts, DAO_OWNER=0x<timelock>), transfers ownership of the staking, reputation, payment, dispute and validation contracts from the bootstrap wallet to the DAO/Timelock. It transfers (never renounces to 0x0), so the governance setters stay live.


Indexing — The Graph

History and analytics use a subgraph built with The Graph (subgraph/): @graphprotocol/graph-cli and @graphprotocol/graph-ts (subgraph/package.json), mappings in AssemblyScript (subgraph/src/) against a GraphQL schema.graphql. It indexes protocol events, identity/service registration, sessions, staking and reputation, so a front-end can run an "app store" for agent services without scanning the chain. The search an agent runs mid-run goes to the discovery indexer from Every setting, which rescans the on-chain ServiceRegistry directly and joins it with each live node's /services answer (the agent runner then reads each node's tools/list for input schemas), so a listing registered a moment ago is already something your agent can find, pay for and rate. The subgraph is the history your own explorers and dashboards query.

# cwd subgraph/
npm run codegen       # graph codegen (needs graph-cli)
npm run build         # graph build
npm run deploy-local  # deploy to a local graph-node

See also

The full design, module map and change history are in ARCHITECTURE.md; testing, static analysis and the security checklist are in SECURITY.md; and the facilitator/node HTTP APIs and voucher semantics are in offchain/SPEC.md.

Every one of these standards carries stablecoins or signatures, except one: ZAI, the only volatile asset in the system, the stake behind every operator your agent pays and the vote behind every parameter, and never the money that pays for a call.

Next: Tokenomics (ZAI) — what the one volatile asset does, and why payments never touch it.