A composable execution infrastructure layer for Web3 intents. Instead of routing every intent through one monolithic solver, competing execution modules are simulated, scored, and the best-scoring one executes on-chain — a market of execution strategies rather than static execution logic.
Built with production intent: explicit trust boundaries, auditable access control, and rigor over speculative scope.
The Execution Kernel Protocol defines a standardized execution layer for Web3, where user intents are resolved by an on-chain kernel that ranks interchangeable execution strategies and runs the winner.
- Intent Layer — standardized, owner-registered intent types that requests are declared against
- Execution Layer — the on-chain kernel (
ExecutionEngine) that fetches candidate modules for an intent type, scores their simulated output, and executes the winner - Execution Modules — composable, independently deployed strategy units (routing, MEV protection, liquidity selection, ...) that compete for selection
- Observability Layer — indexing and performance tracking of execution outcomes
- SDK Layer — developer interface for intent creation and execution integration
There is no separate settlement layer: the selected module's execute() call is the on-chain settlement — it performs the swap/route/transfer directly. A prior draft of this document named a standalone SettlementRouter; that idea is dropped until a concrete need for a settlement step distinct from module execution actually appears (e.g. batching or netting across multiple executed intents).
Execution is not performed by a single solver, and — as currently implemented — not by a static pipeline either. Each registered intent type has a pool of candidate modules; every intent execution re-runs the competition:
Intent (intentType, intentData)
↓
IntentRegistry.isIntentActive(intentType)? — reject if not
↓
ModuleRegistry.getModules(intentType) — fetch active candidates
↓
for each candidate that supportsIntent(intentType):
simulate() → ExecutionQuote → ScorePolicy.evaluate() → signed score
↓
highest-scoring module wins
↓
winning module .execute() — this *is* settlement, no separate step
Each module is:
- independently deployed and replaceable, without touching
ExecutionEngine - scored on the same standardized
ExecutionQuote(cost, quality, MEV risk, latency) - free to lose the competition on one call and win it on the next, as weights or on-chain conditions change
Future direction, not yet built: chaining multiple winning modules into a single execution graph (e.g. MEV-protect then route) rather than picking exactly one. Don't treat pipeline chaining as implemented until ExecutionEngine actually composes more than one module per intent.
execution-kernel-protocol/
├── packages/
│
│ ├── contracts/ # On-chain execution core
│ │ ├── src/
│ │ │ ├── core/
│ │ │ │ ├── ExecutionEngine.sol
│ │ │ │ └── IntentRegistry.sol
│ │ │ │
│ │ │ ├── modules/ # Execution primitives
│ │ │ │ ├── ExecutionModuleBase.sol
│ │ │ │ ├── RouterModule.sol
│ │ │ │ └── MevProtectionModule.sol
│ │ │ │
│ │ │ ├── policy/
│ │ │ │ └── ScorePolicy.sol
│ │ │ │
│ │ │ ├── registry/
│ │ │ │ └── ModuleRegistry.sol
│ │ │ │
│ │ │ ├── access/
│ │ │ │ └── ProtocolRoles.sol # single shared owner, see below
│ │ │ │
│ │ │ └── interfaces/
│ │ │
│ │ ├── test/
│ │ ├── script/
│ │ │ └── Deploy.s.sol
│ │ ├── foundry.toml
│ │ └── remappings.txt
│ │
│ ├── sdk/ # Developer integration layer (wraps viem)
│ │ ├── src/
│ │ │ ├── abi/ # hand-authored `as const` ABIs, one per contract
│ │ │ ├── intent/
│ │ │ │ ├── intentBuilder.ts
│ │ │ │ └── types.ts
│ │ │ │
│ │ │ ├── execution/
│ │ │ │ ├── executionClient.ts # wraps ExecutionEngine
│ │ │ │ └── moduleClient.ts # wraps IExecutionModule (any module address)
│ │ │ │
│ │ │ ├── registry/ # intentRegistry/moduleRegistry/scorePolicy/protocolRoles clients
│ │ │ │
│ │ │ └── index.ts # createExecutionKernelClient(...) bundles all of the above
│ │ ├── examples/
│ │ │ └── quickstart.ts # runnable end-to-end example against a local anvil deployment
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ ├── types/ # Shared protocol definitions (zero runtime deps)
│ │ ├── src/
│ │ │ ├── primitives.ts # Address/Bytes32/Hex aliases
│ │ │ ├── intent.ts # mirrors IntentRegistry.sol
│ │ │ ├── execution.ts # mirrors ExecutionQuote.sol, ScorePolicy.Weights
│ │ │ ├── module.ts # mirrors IExecutionModule.sol, ModuleRegistry.sol
│ │ │ └── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ └── config/ # local-anvil chain/address config (no testnet yet)
│ ├── src/
│ │ ├── chains.ts # localAnvil: Chain
│ │ ├── addresses.ts # localAnvilAddresses, localAnvilModules
│ │ ├── constants.ts # ROUTE_INTENT_TYPE
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
│
├── apps/
│
│ ├── execution-node/ # Off-chain execution engine (consumes sdk)
│ │ ├── src/
│ │ │ ├── engine/
│ │ │ │ ├── intentProcessor.ts # raw request -> Intent (labels intentType)
│ │ │ │ └── executionGraphBuilder.ts # off-chain, gas-free preview of what
│ │ │ │ # ExecutionEngine would select right now
│ │ │ │
│ │ │ ├── solvers/
│ │ │ │ └── solver.ts # one generic solver, not per-module — every
│ │ │ │ # module is scored the same generic way, so
│ │ │ │ # a routerSolver/mevSolver split would just
│ │ │ │ # be duplicated boilerplate today
│ │ │ │
│ │ │ ├── execution/
│ │ │ │ └── executor.ts # submits via sdk's executionClient
│ │ │ │
│ │ │ └── index.ts # runIntent(...) ties the pipeline together
│ │ ├── examples/
│ │ │ └── quickstart.ts # runnable end-to-end example against a local anvil deployment
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ ├── indexer/ # Execution observability layer
│ │ ├── src/
│ │ │ ├── listeners/
│ │ │ │ └── eventListener.ts # generic backfill/watch over any (address, abi, eventName)
│ │ │ ├── processors/
│ │ │ │ └── kernelEventProcessor.ts # backfills all 5 kernel contracts' events into the store
│ │ │ ├── metrics/
│ │ │ │ └── executionMetrics.ts # totalExecutions/executionsByModule/moduleWinRate
│ │ │ ├── db/
│ │ │ │ └── memoryStore.ts # in-memory store — swap for a real DB when persistence matters
│ │ │ └── index.ts # createIndexer(...) backfills into a fresh store
│ │ ├── examples/
│ │ │ └── quickstart.ts # runnable end-to-end example against a local anvil deployment
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ ├── api/ # Integration API layer (Fastify, read-only)
│ │ ├── src/
│ │ │ ├── services/
│ │ │ │ └── kernelService.ts # one shared, read-only ExecutionKernelClient
│ │ │ ├── controllers/
│ │ │ │ ├── intentsController.ts
│ │ │ │ ├── modulesController.ts # includes /predict — off-chain solve(), no gas spent
│ │ │ │ ├── metricsController.ts # wraps apps/indexer (aggregate metrics)
│ │ │ │ └── executionsController.ts # raw per-tx history, most-recent-first
│ │ │ ├── routes/
│ │ │ │ ├── intentsRoutes.ts
│ │ │ │ ├── modulesRoutes.ts
│ │ │ │ ├── metricsRoutes.ts
│ │ │ │ └── executionsRoutes.ts
│ │ │ ├── utils/
│ │ │ │ └── json.ts # bigint/Map -> JSON-safe, needed for every response
│ │ │ └── index.ts # buildServer(); no execute/submit route — see CLAUDE.md
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ ├── frontend/ # Protocol console (Next.js App Router + wagmi/viem)
│ ├── src/
│ │ ├── app/ # App Router, not pages/ — see CLAUDE.md
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ ├── providers.tsx # "use client" boundary: WagmiProvider + QueryClientProvider
│ │ │ └── globals.css # design tokens: --bg/--surface/--border/--ink/--accent/status trio
│ │ ├── components/
│ │ │ ├── layout/AppHeader.tsx # always-visible network identity + wallet
│ │ │ ├── wallet/ConnectWallet.tsx
│ │ │ ├── execution/ # ExecutionConsole.tsx (primary surface), CandidateModuleRow.tsx
│ │ │ ├── protocol/OverviewStats.tsx
│ │ │ ├── metrics/RecentExecutions.tsx
│ │ │ └── ui/ # Badge, Panel, StatTile — shared primitives, few call sites each
│ │ ├── hooks/ # useKernelClient, useIntents, useModules, usePrediction,
│ │ │ # useExecutionMetrics, useRecentExecutions
│ │ ├── services/
│ │ │ └── kernelClient.ts # pure wiring: wagmi's viem clients -> sdk's ExecutionKernelClient
│ │ └── lib/
│ │ └── wagmiConfig.ts # localAnvil only, ssr: true — see CLAUDE.md
│ ├── e2e/
│ │ └── full-flow.spec.ts # Playwright: the full flow end to end, real browser — see CLAUDE.md
│ ├── playwright.config.ts
│ ├── package.json
│ └── tsconfig.json
│
│ └── landing/ # exekpro.com — public marketing site (Next.js App Router)
│ ├── src/
│ │ ├── app/
│ │ │ ├── layout.tsx # metadata: title/description/OG/canonical
│ │ │ ├── page.tsx # composes every section, in order
│ │ │ ├── globals.css # single deliberate dark theme, not dual-theme like the console
│ │ │ ├── icon.svg # favicon (Next's file-convention icon)
│ │ │ └── opengraph-image.tsx # generated OG image (next/og ImageResponse)
│ │ ├── components/
│ │ │ ├── layout/ # SiteHeader, SiteFooter, Wordmark
│ │ │ ├── sections/ # Hero, Problem, HowItWorks, ExecutionQuoteSection,
│ │ │ │ # ModularExecution, B2BDeployments, DeveloperExperience,
│ │ │ │ # Validation, SecurityPrinciples, RoadmapStatus, FinalCTA
│ │ │ └── ui/ # Container, SectionHeading, Pill, FlowDiagram, StatTile, CTALink
│ │ └── lib/
│ │ └── links.ts # GITHUB_URL/CONSOLE_URL/DOCS_URL — one place to update when real
│ ├── package.json # deliberately no wagmi/viem/sdk deps — pure static marketing content
│ └── tsconfig.json
│
├── scripts/
│ ├── deploy.ts
│ ├── simulate-intents.ts
│ └── benchmark-execution.ts # Execution performance validation
│
├── docs/
│ ├── architecture.md
│ ├── intents.md
│ ├── execution-graph.md
│ └── threat-model.md
│
├── package.json # npm workspaces root
├── tsconfig.base.json # shared strict TS config, extended per-package
├── .gitignore
└── README.md
ModuleRegistry and IntentRegistry each currently hand-roll their own owner / onlyOwner. ProtocolRoles replaces that duplication with a single shared owner contract both registries defer to — one owner, one place to reason about protocol control, not independent per-registry admins. This is deliberately the simple model for now, not multi-role RBAC (distinct module-manager / intent-manager / protocol-admin roles) — revisit that split if and when different registries genuinely need independent operators.
- Smart Contracts: Execution Modules + Registry + Engine
- Execution Node: intent processing + module-selection orchestration
- Indexer: execution metrics + performance feedback loop
- SDK: developer integration surface
- Frontend: intent-based interaction layer
- Execution is modular, not monolithic
- Competing modules are scored transparently (
ScorePolicy) and the best one wins — no hidden routing - Trust boundaries are explicit: intent-type activation, module registration, and protocol ownership are each a single, auditable control point
- Built toward production: prefer explicit, tested logic over cleverness; expand scope (settlement, multi-role access, graph pipelining) only when a concrete need appears, not speculatively
- System performance is measured and observable by design
- SDK is the primary integration surface for external adoption
This protocol is an execution abstraction layer for Web3 applications: it lets a decentralized system express an intent once and have competing, independently deployed execution strategies fight for the right to fulfill it — with the trust boundary that governs which strategies are eligible kept small, explicit, and centrally owned.