Odel
ava 4.0

ava 4.0

@kamalbuilds1TypeScriptUpdated 2w ago

Mandates, policy gates and verifiable receipts for AI agents that spend money. Live is fail-closed.

Server endpointStreamable HTTPNo authProbed

This is the third-party server itself — Odel doesn't run it. Hitting this URL directly talks straight to the upstream server with no auth or proxying. Connect through Odel to front it with managed auth.

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:

ChainVenueTransactionBlock
BaseMorpho gtUSDCp0x97b78540…36bbba49315911
BaseMorpho gtUSDCp0xce4109e4…6b0a7f49327027
AvalancheAave v30x12990c11…f4209f92231544
BNB ChainAave v30x7737cf5c…08b9de114508635
MonadAave v30x50940c95…8715cf92954146
MonadAave v30x1b23c35d…628af793291520
BNB ChainSushiSwap via LI.FI0x14850931…de0542114518492

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

SurfaceURLStatus
Product webhttps://www.getava.xyzVercel static, LIVE
Public APIhttps://api.getava.xyzFly.io always-on, LIVE
Healthhttps://api.getava.xyz/health{"ok":true,"productionMode":true,"dependencies":{"ledger":"postgres",...}}
MCPhttps://api.getava.xyz/mcp11 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

PackagePathPurpose
@ava/intentpackages/intentVersioned AvaIntent / StrategyIR, IntentKind, canonical hashes
@ava/policypackages/policyDeterministic policy evaluator for intents and strategies
@ava/verifiabilitypackages/verifiabilityExecution receipt schemas, hashes, invariants
@ava/ledgerpackages/ledgerAppend-only in-memory ledger and outbox interface
@ava/ledger-postgrespackages/ledger-postgresPostgres-backed ledger and receipt store
@ava/broker-cowpackages/broker-cowCoW drafts, dry-run transport, HTTP Orderbook transport
@ava/broker-routerpackages/broker-routerKind-first dispatch; CoW + Jupiter prepare-only for swap kinds; fail closed otherwise
@ava/capability-registrypackages/capability-registryCapability kind x chain x venue registry
@ava/wallet-policypackages/wallet-policySession permissions and signature request gate
@ava/wallet-turnkeypackages/wallet-turnkeyReal Turnkey-backed, policy-gated signer
@ava/condition-enginepackages/condition-engineDeterministic StrategyIR trigger evaluator
@ava/adapter-jupiterpackages/adapter-jupiterSolana Jupiter quote + unsigned swap build
@ava/adapter-cetuspackages/adapter-cetusSui Cetus quote/build (injected SDK client)
@ava/adapter-deepbookpackages/adapter-deepbookSui DeepBook quote/build (injected SDK client)
@ava/adapter-suilendpackages/adapter-suilendSui Suilend prepare/build (injected SDK client)
@ava/adapter-walruspackages/adapter-walrusWalrus decentralized storage HTTP adapter
@ava/adapter-membasepackages/adapter-membaseMembase / Unibase agent memory HTTP adapter

Apps

AppPathPurpose
@ava/api-workerapps/api-workerHono ingress: health, capabilities, parse, plan, conditions, fail-closed execute, receipts
@ava/execution-workerapps/execution-workerIn-process execution workflow, Turnkey signature port, CoW transport selection
@ava/agent-serviceapps/agent-serviceMastra cognitive tools + OpenRouter model contract for intent/policy/plan
@ava/webapps/webProduct 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:

PackageOwns
packages/execution-contractAmount (bigint base units, never floats), the ExecutionState machine, claim identity, finality policy
packages/intentNatural language to legs with dependsOn and runtime amounts
packages/mandatesSigned bounds, the rotation check, revocation, schedule due-ness
packages/ledger-postgresAtomic reservation. The reason a retry cannot double-spend
packages/verifiabilityFinality, per-leg receipts, aggregate workflow receipts
packages/capability-registryThe allowlist. A venue not in it has no authority to send
apps/api-workerHTTP 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

DocContents
docs/ONBOARDING.mdStart here for humans. Verified first run, the fresh-clone trap, house rules
docs/AGENT_CONTEXT.mdStart here for agents/subagents
docs/STACK.mdResearch-locked stack constitution
docs/HOSTING.mdFly.io always-on hosting (not Railway, not Oracle)
docs/PRODUCTION.mdHistorical deploy record and live URLs
docs/SYSTEM_ARCHITECTURE.mdCanonical 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.mdHistorical venue and proof inventory; not runtime status
docs/PRODUCT_TRUTH_TODAY.mdHistorical Bar A and Bar B record
docs/OPEN_GAPS.mdHistorical gap inventory
scripts/hosting/README.mdOne-page Fly.io and Docker deploy order
docs/PRD.mdProduct requirements
docs/PRODUCT_REBOOT.mdProduct loop + kill list
docs/ARCHITECTURE_OVERVIEW.mdRetired pointer to the canonical architecture
docs/COMPONENT_ARCHITECTURE.mdRetired pointer to the canonical architecture
docs/CHAIN_CAPABILITIES.mdCapability taxonomy
docs/MASTRA_AGENTS.mdAgent service design
docs/WORKFLOWS.mdExecution flow notes
docs/IMPLEMENTATION_PLAN.mdTasks A-E status and next work
docs/PRODUCTION_READINESS.mdLocal prod-like boot checklist
docs/MCP.mdMCP connect guide (Claude, Cursor, Codex, Grok)
graphify-out/GRAPH_REPORT.mdCodegraph (regenerate: graphify update .; cache not committed)
.env.exampleFail-closed env template
docker-compose.ymlLocal 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:

  • verified means the hashes are intact and the invariants hold. It is about internal consistency.
  • proof.standing means what actually happened on chain: none (nothing was submitted), unconfirmed (an identifier was supplied by an external party and nothing has confirmed it), or chain-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.