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.
| URL | Serves | Worker |
|---|---|---|
https://exekpro.com/ |
ExeKPro Protocol Console (apps/frontend) |
exekpro-console |
https://exekpro.com/about |
ExeKPro public website (apps/landing) |
exekpro-landing |
Two independently deployed Cloudflare Workers behind one zone, split by path via Worker Routes (not a Custom Domain, which can't split a hostname by path) β see each app's own wrangler.jsonc. The console has no public kernel deployment behind it yet, though β it only reads/writes whatever chain the connecting wallet points at (see Getting Started below), since packages/config doesn't have a real testnet/mainnet entry yet.
Prerequisites: Node 22+, Foundry (anvil/forge/cast), npm.
git clone https://github.com/psatomas/ExeKPro.git
cd ExeKPro
npm install
# 1. Deploy the kernel to a local anvil chain and register the demo ROUTE
# intent + both example modules (starts anvil itself if it isn't already
# running). See scripts/deploy-local.sh for exactly what this does.
./scripts/deploy-local.sh
# 2. In a separate terminal: the read-only API
npm run start --workspace=apps/api
# 3. In another separate terminal: the console
npm run dev --workspace=apps/frontendOpen http://localhost:3000, connect a wallet pointed at http://127.0.0.1:8545 (chain id 31337), and the Route intent should be selectable end to end β simulate & score both candidate modules, execute, and see the result flow through the indexer to the API back to the console.
| Service | Default port | Started by |
|---|---|---|
| anvil (local chain) | 8545 |
scripts/deploy-local.sh (or run anvil yourself first) |
apps/api |
4000 |
npm run start --workspace=apps/api |
apps/frontend |
3000 |
npm run dev --workspace=apps/frontend |
Other runnable, code-only examples against the same local deployment (no frontend/API involved) β each is a real end-to-end script, not a mock:
node packages/sdk/examples/quickstart.tsβ every SDK client (intentBuilder, registries,scorePolicy,executionClient), including a governanceupdateWeights()call.node apps/execution-node/examples/quickstart.tsβ the off-chain process β solve β submit pipeline (runIntent(...)).node apps/indexer/examples/quickstart.tsβ backfilling kernel events and deriving execution metrics.
To run the contract test suite: forge test from packages/contracts/. To run the frontend's own real end-to-end test (e2e/full-flow.spec.ts β a real headless browser, real wagmi, real chain, real apps/api): with anvil/apps/api already running as above, npm run e2e from apps/frontend/.
- Console shows "No intents registered yet." / step 2 onward is greyed out. Not a bug β the chain your wallet is connected to genuinely has nothing registered on it yet. Run
./scripts/deploy-local.sh(or restartanvilfirst if you'd already sent any transaction on it β the script requires a fresh chain, since the deployed addresses are only deterministic from nonce 0). - Console appears stuck on "Loading registered intents..." forever, no error shown. Most likely your wallet is connected to a chain other than local anvil (check the network badge in the header β a red "Wrong network" badge confirms it).
packages/configcurrently only has alocalAnvildeployment entry; connecting via Sepolia or any other chain silently returns no data rather than an error, by design of today'suseKernelClient()(seeapps/frontend/CLAUDE.md) β switch your wallet to local anvil. scripts/deploy-local.shexits with "already has a nonce of N". Your anvil chain isn't fresh β restart it (a newanvilprocess always starts at nonce 0) and re-run the script.
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.
ExeKPro/
βββ 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
β βββ wrangler.jsonc # Worker name exekpro-console, Route exekpro.com/*
β βββ open-next.config.ts
β βββ package.json
β βββ tsconfig.json
β
β βββ landing/ # exekpro.com/about β public marketing site (Next.js App Router)
β βββ src/
β β βββ app/
β β β βββ layout.tsx # metadata: title/description/OG/canonical, basePath-aware ("/about")
β β β βββ page.tsx # composes every section, in order
β β β βββ globals.css # single deliberate dark theme, not dual-theme like the console
β β β βββ icon.png / apple-icon.png
β β β βββ opengraph-image-asset/route.tsx # generated OG image (next/og ImageResponse), a plain
β β β # route rather than Next's file-convention β see CLAUDE.md
β β βββ 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
β β βββ basePath.ts # shared "/about" constant β next.config.ts + next/image src both use it
β βββ wrangler.jsonc # Worker name exekpro-landing, Routes exekpro.com/about, exekpro.com/about/*
β βββ open-next.config.ts
β βββ package.json # deliberately no wagmi/viem/sdk deps β pure static marketing content
β βββ tsconfig.json
β
βββ .github/workflows/
β βββ ci.yml # forge test + npm run typecheck, every PR
β βββ deploy-console.yml # apps/frontend -> exekpro-console, on push to main
β βββ deploy-landing.yml # apps/landing -> exekpro-landing, on push to main
β
βββ scripts/
β βββ deploy-local.sh # one-command local kernel deploy + demo intent/module registration
β
βββ docs/ # placeholder β not yet populated
β
βββ package.json # npm workspaces root
βββ tsconfig.base.json # shared strict TS config, extended per-package
βββ .gitignore
βββ LICENSE # Apache-2.0
βββ CONTRIBUTING.md
βββ 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
See CONTRIBUTING.md β local setup (links back to Getting Started above), what to run before opening a PR, and commit conventions.
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.