Design Decisions
Log of key architectural and implementation decisions.
#1Base-First Protocol Deployment
Decision: Deploy Cortex first as protocol contracts and services on Base/Base Sepolia rather than launching a new chain immediately.
Rationale: Base already has stablecoins, ERC-20 liquidity, wallets, explorers, developer distribution, and a credible path for agentic commerce adoption.
Tradeoff: Less control over execution, sequencing, and fee markets than a custom rollup. The same primitives can become predeploys or chain-native modules later.
#2Agent Identity as Contract Records (Not NFTs)
Decision: Store agent identity in a simple mapping (uint256 => AgentRecord) in AgentRegistry.
Rationale: Simpler than ERC-721. No transfer semantics needed — agents are permanently tied to their owner. Avoids NFT marketplace confusion.
Tradeoff: Cannot transfer agent identity. Acceptable for MVP; could add transfer later if needed.
#3EIP-712 Typed Data for Intent Signing
Decision: Use EIP-712 with domain separator for intent signing.
Rationale: Standard approach for off-chain signatures with on-chain verification. Includes chain ID and contract address for replay protection. Human-readable in wallet UIs.
Tradeoff: Requires more gas than simple keccak256(abi.encodePacked(...)). Worth it for security and UX.
#4Per-Owner Nonce (Not Sequential)
Decision: Use a mapping(address => mapping(uint256 => bool)) for nonce tracking instead of sequential nonces.
Rationale: Agents may submit intents from multiple processes concurrently. Sequential nonces would require coordination. Boolean nonces allow any unused value.
Tradeoff: Higher storage costs (cannot pack nonces). Acceptable at current scale.
#5Lazy Intent Expiration
Decision: Check deadline in fillIntent() rather than a separate expiration process.
Rationale: Simpler. No keeper/cron needed. Expired intents remain OPEN in storage but cannot be filled. getIntentStatus() returns storage value.
Tradeoff: Expired intents appear OPEN until someone attempts to fill them. The API/indexer can compute effective status.
#6PolicyModule as Separate Contract
Decision: Keep PolicyModule as a standalone contract rather than embedding logic in PolicyAccount.
Rationale: Multiple accounts can share the same PolicyModule. Policies are upgradeable independently of account code. Cleaner separation of concerns.
Tradeoff: Extra call overhead for policy checks. Negligible on Base for the current usage profile.
#7Permissionless Solver Registry, Hosted Solver First
Decision: Support permissionless solver registration and indexed solver quality, while still allowing an initial hosted solver for demos and early testnet flows.
Rationale: Agents need visible solver metadata, bids, bonds, and performance counters before trusting automated execution.
Tradeoff: Real production solver markets still need stronger incentives, monitoring, and potentially slashing or dispute mechanisms.
#8Express 4 for API
Decision: Use Express 4 with raw pg queries.
Rationale: Lightweight, widely known, stable. No ORM overhead. Consistent with indexer's database access pattern. App factory pattern (createApp(pool)) enables test injection.
Tradeoff: No built-in validation framework. Manual validation in each route. Acceptable for small endpoint surface.
#9Postgres Only (No OpenSearch)
Decision: Use Postgres as the sole data store for MVP.
Rationale: Sufficient for the query patterns needed. JSONB supports flexible event storage. Simpler operations than running OpenSearch.
Tradeoff: Full-text search on metadata would require Postgres FTS or adding OpenSearch later.
#10Address Normalization (Lowercase)
Decision: Store all addresses as lowercase hex strings in Postgres.
Rationale: Avoids case-sensitivity issues in queries. EVM addresses are case-insensitive; checksummed addresses are for display only.
Tradeoff: Cannot reconstruct EIP-55 checksum from stored value. Not needed for MVP.
#11NUMERIC/BIGINT as Strings in API
Decision: Return Postgres NUMERIC and BIGINT values as strings in JSON responses.
Rationale: JavaScript Number cannot represent values > 2^53. Smart contract values routinely exceed this. String representation preserves precision.
Tradeoff: Consumers must parse strings to BigInt. Standard practice in Ethereum tooling.
#12Shared Migration File
Decision: The API service reads the indexer's 001_init.sql migration file.
Rationale: Single source of truth for schema. Both services need the same tables. Avoids schema drift.
Tradeoff: Creates a file-path dependency between services. Mitigated by trying multiple candidate paths and warning if not found.