Ava
The execution layer for agents that do DeFi.
Your AI agent can ship an app, run research, book a flight, buy a thing. Ask it to supply 300 USDC to Morpho on Base and bridge the rest to Avalanche, and you get an explanation, maybe some code, and no transaction. Asked directly, Claude answers: "No. No wallet, no signer, no RPC."
Ava is the part that can.
"Supply 300 USDC to Morpho on Base, bridge no more than 200 to Avalanche,
then supply what arrives to Aave"
leg_1 lend base morpho USDC 300
leg_2 bridge base -> avalanche bridge USDC max 200
leg_3 lend avalanche aave USDC amount from leg_2
Three dependent actions from one sentence. leg_3 waits for leg_2 and
supplies the amount the bridge actually delivered, not the 200 that was
requested, because a bridge delivers less than it accepts and a number chosen
at planning time is wrong by exactly the fee.
New here? Jump to Onboarding. It is a verified first hour: setup, the one trap that breaks a fresh clone, and the house rules that exist because ignoring them shipped a bug.
Confirmed on mainnet
Ava built the calldata, signed it with its own Turnkey key, broadcast it, and
the chain confirmed it. The canonical list is apps/site/src/lib/settlements.ts,
and apps/site/scripts/verify-settlements.mjs re-reads every row from a public
node before the site is allowed to build. Last run 2026-08-18, all seven matched:
| Chain | Venue | Transaction | Block |
|---|---|---|---|
| Base | Morpho gtUSDCp | 0x97b78540…36bbba | 49315911 |
| Base | Morpho gtUSDCp | 0xce4109e4…6b0a7f | 49327027 |
| Avalanche | Aave v3 | 0x12990c11…f4209f | 92231544 |
| BNB Chain | Aave v3 | 0x7737cf5c…08b9de | 114508635 |
| Monad | Aave v3 | 0x50940c95…8715cf | 92954146 |
| Monad | Aave v3 | 0x1b23c35d…628af7 | 93291520 |
| BNB Chain | SushiSwap via LI.FI | 0x14850931…de0542 | 114518492 |
The two BNB rows were previously transposed here: 0x14850931… was labelled
Aave v3 when its to is the LI.FI diamond, and 0x7737cf5c… was labelled a
LI.FI swap when its to is the Aave v3 pool. settlements.ts was corrected and
this table was not, which is why the verifier now reads from that one source.
A mainnet proof shows a path executed once. It is not a claim that the route is ready for unattended capital, and this repository never treats it as one.
Two ways to authorize
One-shot. The user is present, sees the exact plan, signs it.
Standing. The user is asleep. "Every hour rotate my USDC into the best yield on Base using Aave and Morpho, never more than 200 per rotation, never over 1000 total, stop after 30 days." They sign the bounds once over EIP-712, and every later rotation is checked by code rather than by a human. Editing any limit after signing breaks the signature.
Asking a human at every rotation would defeat the product. Relaxing the check would defeat the point.
Try it in one call
curl -sX POST https://api.getava.xyz/v1/users/session \
-H 'content-type: application/json' -d '{}'
No CAPTCHA, no email, no wallet popup. Then connect a coding agent over MCP:
claude mcp add --transport http ava https://api.getava.xyz/mcp \
--header "Authorization: Bearer <token>"
Codex, Cursor and Grok setups are at getava.xyz/install. Machine-readable summary at getava.xyz/llms.txt.
Live routes
Five lending routes are callable through the authenticated API today: Morpho
Blue on Base, and Aave v3 on Monad, Avalanche, BNB Chain and Arbitrum One.
Four of the five have settled a real mainnet transaction. Arbitrum has not:
its addresses are RPC-verified and every gate on the path is the same one the
other four use, but nothing has ever been signed there, so its registry entry
carries mainnetProven: false and the capability table prints "no" in its
settled-on-mainnet column. Callable and proven are different claims.
Everything else in the capability registry carries a notExecutableReason
saying exactly why it is not callable: no executor implemented, an executor
nothing imports, operator disabled, or proven by script only.
What does not work yet
Execution of multi-leg workflows is not wired end to end. The planner, the
authorization layer and the per-leg reservation exist and are tested; nothing
yet drives them from a signed plan to a chain. The full list is in
docs/production-architecture-review/WORKFLOW_MILESTONE.md
and is deliberately longer than the list of what does.
Check it yourself
bash scripts/workflow-gates.sh # 17 gates: cross-chain, same-chain, standing
bash scripts/standing-e2e.sh # signs with a key the server never sees
node testbench/run.mjs # a prompt file, through the real MCP call
pnpm run test # ~1000 tests
The canonical design and route table are in
docs/SYSTEM_ARCHITECTURE.md.
Legacy frontend/, server/, eigenlayer/, and agent folders outside this tree remain v3 reference code. New production code lives under v4/.
Technical constitution (research-locked stack): docs/STACK.md.
Architecture spine
intent (kind) -> policy -> kind dispatcher -> venue adapter -> workflow -> receipt
Kind is primary. Swap/rebalance enter the solver market (CoW, Jupiter, Cetus, ...). Lend, bridge, stake, and perp use protocol-family adapters and never fall through to a default swap venue.
Execution is intentionally fail-closed. The stack validates, plans, and can dry-run. It does not treat missing signatures, missing approvals, or missing venue adapters as success.
Live production endpoints
| Surface | URL | Status |
|---|---|---|
| Product web | https://www.getava.xyz | Vercel static, LIVE |
| Public API | https://api.getava.xyz | Fly.io always-on, LIVE |
| Health | https://api.getava.xyz/health | {"ok":true,"productionMode":true,"dependencies":{"ledger":"postgres",...}} |
| MCP | https://api.getava.xyz/mcp | 11 tools observed on 2026-08-07 |
# Verify production
curl -fsS https://api.getava.xyz/health
curl -sS -X POST https://api.getava.xyz/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Packages
| Package | Path | Purpose |
|---|---|---|
@ava/intent | packages/intent | Versioned AvaIntent / StrategyIR, IntentKind, canonical hashes |
@ava/policy | packages/policy | Deterministic policy evaluator for intents and strategies |
@ava/verifiability | packages/verifiability | Execution receipt schemas, hashes, invariants |
@ava/ledger | packages/ledger | Append-only in-memory ledger and outbox interface |
@ava/ledger-postgres | packages/ledger-postgres | Postgres-backed ledger and receipt store |
@ava/broker-cow | packages/broker-cow | CoW drafts, dry-run transport, HTTP Orderbook transport |
@ava/broker-router | packages/broker-router | Kind-first dispatch; CoW + Jupiter prepare-only for swap kinds; fail closed otherwise |
@ava/capability-registry | packages/capability-registry | Capability kind x chain x venue registry |
@ava/wallet-policy | packages/wallet-policy | Session permissions and signature request gate |
@ava/wallet-turnkey | packages/wallet-turnkey | Real Turnkey-backed, policy-gated signer |
@ava/condition-engine | packages/condition-engine | Deterministic StrategyIR trigger evaluator |
@ava/adapter-jupiter | packages/adapter-jupiter | Solana Jupiter quote + unsigned swap build |
@ava/adapter-cetus | packages/adapter-cetus | Sui Cetus quote/build (injected SDK client) |
@ava/adapter-deepbook | packages/adapter-deepbook | Sui DeepBook quote/build (injected SDK client) |
@ava/adapter-suilend | packages/adapter-suilend | Sui Suilend prepare/build (injected SDK client) |
@ava/adapter-walrus | packages/adapter-walrus | Walrus decentralized storage HTTP adapter |
@ava/adapter-membase | packages/adapter-membase | Membase / Unibase agent memory HTTP adapter |
Apps
| App | Path | Purpose |
|---|---|---|
@ava/api-worker | apps/api-worker | Hono ingress: health, capabilities, parse, plan, conditions, fail-closed execute, receipts |
@ava/execution-worker | apps/execution-worker | In-process execution workflow, Turnkey signature port, CoW transport selection |
@ava/agent-service | apps/agent-service | Mastra cognitive tools + OpenRouter model contract for intent/policy/plan |
@ava/web | apps/web | Product shell UI (roles + chat + portfolio/events); live at www.getava.xyz |
Onboarding
Every command in this section was run against a fresh git clone on
2026-08-10, and the surprises are written down rather than smoothed over.
bash scripts/onboarding-check.sh re-verifies these claims, so they cannot
quietly stop being true.
If you only read one line: run pnpm test, never pnpm -r test.
First run
Requires Node 22+ (23.10 is what this was verified on) and pnpm@9.15.4, which
is pinned in packageManager. Docker is NOT required to run the tests.
cd v4
pnpm install
pnpm test # builds packages first, then runs every suite
That is the whole setup. No .env, no database, no API keys, no funded wallet.
Roughly 5 minutes cold, most of it the package build.
The one trap
pnpm -r --if-present test fails on a fresh clone and it is not your
machine:
Failed to resolve entry for package "@ava/condition-engine"
Workspace packages import each other through their built dist output, so the
packages have to be built before anything imports them. The root test script
already chains build:pkgs && pnpm -r test, which is why pnpm test works and
the recursive form does not. Same for pnpm typecheck. Use the root scripts.
If you have already hit it, pnpm run build:pkgs once fixes the tree.
Run the product locally
pnpm --filter @ava/api-worker dev # http://localhost:8787
PORT=8799 pnpm --filter @ava/api-worker dev # if 8787 is taken
PORT is the escape hatch when a stray instance holds 8787. The failure
otherwise is a raw EADDRINUSE stack trace.
It boots with an in_memory ledger and no secrets. Ask it for a real plan:
TOKEN=$(curl -s -X POST localhost:8787/v1/users/session \
-H 'content-type: application/json' -d '{}' | jq -r .token)
curl -s -X POST localhost:8787/v1/workflows/plan \
-H 'content-type: application/json' -H "Authorization: Bearer $TOKEN" \
-d '{"message":"Supply 100 USDC to Morpho on Base then swap 50 to WETH on BNB"}' | jq
Two legs, the second depending on the first, executable: true on the Morpho
leg and false on the swap. The body key is message, not request.
Nothing above can move money. Execution needs credentials that are deliberately absent from a fresh clone.
Check the MCP surface an agent actually sees
pnpm dogfood:mcp # against the local worker on :8787
pnpm dogfood:mcp:prod # against https://getava.xyz/mcp
Run this after touching anything under apps/api-worker/src/lib/mcp.ts, the
portal resolver, or the tool list. It speaks JSON-RPC the way a client does,
initialize first and then tools/list, and it only uses tools the server
advertised: if a step needs one that was not advertised, that is the failure
rather than a reason to call it by name anyway.
It exists because two defects shipped that every unit test passed through. The
default tool list advertised ava_preview_tx and ava_approve_execute, which
both consume an executionId, while ava_copilot_turn, the only tool that
mints one, sat in an opt-in toolset, so the advertised surface was a closed
circuit for anyone discovering the server. And a caller who named no portal
landed on sui, which has no mainnet-proven route, so the first call returned
seeded balances on the one chain family that cannot fill. Both are properties of
the surface an outside caller sees rather than of any function, which is why 559
green tests said nothing about either.
Read-only by default. AVA_DOGFOOD_APPROVE=1 lets it run the approve step,
which is the one call that moves value, so it stays off unless asked for.
A note rather than a failure means a typed refusal, which is Ava working: a
LIVE_WALLET_REQUIRED on preview is the gate doing its job, not a dead end.
Local infra + API + web:
pnpm run dev:db
cp .env.example .env # edit secrets; never commit
pnpm --filter @ava/api-worker dev
pnpm --filter @ava/web dev
./scripts/smoke-api.sh
Web only:
pnpm --filter @ava/web typecheck
pnpm --filter @ava/web dev
Where the guarantees live
47 packages is a lot. These are the ones that carry the guarantees:
| Package | Owns |
|---|---|
packages/execution-contract | Amount (bigint base units, never floats), the ExecutionState machine, claim identity, finality policy |
packages/intent | Natural language to legs with dependsOn and runtime amounts |
packages/mandates | Signed bounds, the rotation check, revocation, schedule due-ness |
packages/ledger-postgres | Atomic reservation. The reason a retry cannot double-spend |
packages/verifiability | Finality, per-leg receipts, aggregate workflow receipts |
packages/capability-registry | The allowlist. A venue not in it has no authority to send |
apps/api-worker | HTTP and MCP ingress. src/lib/workflow-execute.ts is admission plus reservation, workflow-runner.ts is ordering |
Everything else is an adapter, a broker or a surface.
Gates, not vibes
bash scripts/workflow-gates.sh # 17 product gates, hits the running API
bash scripts/standing-e2e.sh # 15 authorization checks, real signing key
pnpm testbench # plain-text prompts through the real MCP tool
bash scripts/onboarding-check.sh # is this section still true
The gate scripts were written BEFORE the features they check, specifically so
the implementation could not grade itself. Both must be green before you claim
anything works. workflow-gates.sh needs the API running.
House rules
These are not style preferences. Each one exists because ignoring it shipped a bug in this repo.
Fail closed. A missing check is a refusal, never a pass. An unreadable revocation store answers "revoked", because a store outage is the worst possible moment to keep spending.
Never invent a value to make a shape complete. A placeholder makes a plan
LOOK finished, a user approves it, and the failure surfaces at signing time.
This shipped twice: a chain: "unspecified" placeholder, and a borrow request
planned as a lend. A plausible wrong answer is worse than an honest refusal.
Money is Amount, never number. Floats lose base units.
Inclusion is not finality. Only a settled-successful leg may hand an amount to a dependent leg. A bridge delivers less than it accepts, so the dependent leg uses what arrived, not what was sent.
Aggregates report their weakest leg. Two verified legs and one reverted leg is a failed workflow, not a partial success.
Test the inversion. Independent critics found bugs where "never use Aave"
authorized Aave and a caps check read the caller's caps instead of the signed
ones. Both passed tests that only checked the happy path. When a rule matters,
break it deliberately and confirm a test fails.
Comments explain WHY. packages/mandates/src/signature.ts is the reference.
No em-dashes, no emoji.
Where the work is
docs/production-architecture-review/WORKFLOW_MILESTONE.md
holds the live blocker list. Read it before starting anything: it is maintained
as the honest account of what does not work, which is more useful than any
status doc.
docs/AGENT_CONTEXT.md is the entry point for agents
and subagents. docs/SYSTEM_ARCHITECTURE.md is
the canonical runtime architecture. Most other files in docs/ are dated
snapshots; treat a filename with a date in it as history, not current state.
Production principles
- No process-global private keys for user funds.
- No fake execution success.
- LLMs compile intents and explain plans only.
- Deterministic engines execute, with receipts.
- Every new broker starts with dry-run and fail-closed behavior.
- Descriptor-only capabilities must not execute money movement.
MCP (Claude, Cursor, Codex, Grok)
Ava ships an HTTP MCP surface on the api-worker:
# Local
pnpm --filter @ava/api-worker dev
curl -sS http://127.0.0.1:8787/mcp | jq '.tools[].name'
# Production
curl -sS -X POST https://api.getava.xyz/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Production exposed ava_approve_execute, ava_portfolio, ava_session,
ava_create_agent, ava_provision_wallet, ava_preview_tx,
ava_create_mandate, ava_list_mandates, ava_eval_mandate,
ava_agent_record, and ava_get_receipt when checked on 2026-08-07. Always
discover with tools/list; the deployed set can differ from repository tools.
There is no dedicated live lending or generic execution MCP tool yet. Full
client setup: docs/MCP.md.
OpenClaw skill pack: packages/openclaw-skill -- install into OpenClaw, run node packages/openclaw-skill/scripts/ava.mjs session|turn|approve|portfolio (testnet-first quote -> confirm -> settle).
MCP config for Claude Code / Cursor:
{
"mcpServers": {
"ava": {
"url": "https://api.getava.xyz/mcp"
}
}
}
Documentation
| Doc | Contents |
|---|---|
| docs/ONBOARDING.md | Start here for humans. Verified first run, the fresh-clone trap, house rules |
| docs/AGENT_CONTEXT.md | Start here for agents/subagents |
| docs/STACK.md | Research-locked stack constitution |
| docs/HOSTING.md | Fly.io always-on hosting (not Railway, not Oracle) |
| docs/PRODUCTION.md | Historical deploy record and live URLs |
| docs/SYSTEM_ARCHITECTURE.md | Canonical runtime architecture, route reachability, trust boundaries, and production blockers |
| docs/production-architecture-review/ | Detailed implementation handoff for Claude, Codex, Grok, and maintainers |
| docs/PRODUCT_TRUTH.md | Historical venue and proof inventory; not runtime status |
| docs/PRODUCT_TRUTH_TODAY.md | Historical Bar A and Bar B record |
| docs/OPEN_GAPS.md | Historical gap inventory |
| scripts/hosting/README.md | One-page Fly.io and Docker deploy order |
| docs/PRD.md | Product requirements |
| docs/PRODUCT_REBOOT.md | Product loop + kill list |
| docs/ARCHITECTURE_OVERVIEW.md | Retired pointer to the canonical architecture |
| docs/COMPONENT_ARCHITECTURE.md | Retired pointer to the canonical architecture |
| docs/CHAIN_CAPABILITIES.md | Capability taxonomy |
| docs/MASTRA_AGENTS.md | Agent service design |
| docs/WORKFLOWS.md | Execution flow notes |
| docs/IMPLEMENTATION_PLAN.md | Tasks A-E status and next work |
| docs/PRODUCTION_READINESS.md | Local prod-like boot checklist |
| docs/MCP.md | MCP connect guide (Claude, Cursor, Codex, Grok) |
| graphify-out/GRAPH_REPORT.md | Codegraph (regenerate: graphify update .; cache not committed) |
.env.example | Fail-closed env template |
docker-compose.yml | Local Postgres |
Current honesty
The API, Postgres ledger, scheduler, and 11-tool MCP surface are live on Fly.io. Wallet provisioning returns real Turnkey addresses and receipts survive restarts. This does not make unattended real-capital execution production-safe.
The current live paths do not require a verified user-controlled mandate before
signing, do not atomically reserve idempotency and wallet nonce before submit,
and do not apply a documented confirmation-depth and reorg policy. Treat all
mainnet route evidence as supervised implementation proof until the P0 gates in
docs/SYSTEM_ARCHITECTURE.md close.
What a receipt does and does not prove. GET /v1/receipts/:id/verify answers two
different questions and deliberately keeps them apart:
verifiedmeans the hashes are intact and the invariants hold. It is about internal consistency.proof.standingmeans what actually happened on chain:none(nothing was submitted),unconfirmed(an identifier was supplied by an external party and nothing has confirmed it), orchain-confirmed(public RPC returned the transaction).
A receipt can be verified: true with proof.standing: "unconfirmed". That
combination is honest and common: it means Ava's own record is sound but the
execution claim rests on someone else's word. Attestation
(POST /v1/receipts/:id/attest) checks a submitted txHash against public RPC
and rejects one that does not exist with ATTEST_TX_NOT_FOUND_ON_CHAIN.
For the reviewed blockers, see
docs/SYSTEM_ARCHITECTURE.md.