Implementation
Version: 1.1 (MVP handoff)
Date: 2026-07-11
Status: Ready for multi-agent fan-out
Protocol name: Open Yield
XEL surfaces: Auto Yield (stake/harvest/compound) + Auto Pay (yield → services/agents)
Authority: docs/LOCKED-MODEL-2026-07-10.md wins on conflicts
Plane epic: XEL-152
0. Naming (locked)
Section titled “0. Naming (locked)”| Name | What it is |
|---|---|
| Open Yield | Open protocol / capability brand for permissionless capital routing + harvest |
| Auto Yield | Owner-facing mode: opt-in stake, permissionless harvest, compound residual |
| Auto Pay | Route harvested yield (and/or liquid) to pay inference, storage, x402, other agents — after survival bills |
User line:
Characters on XEL: persona by GEN, capital by Open Yield.
Protocol line:
XEL characters use Open Yield as a swappable treasury capability. Defaults ship with XEL; owners can self-provision venues later.
0.1 Product framing inside XEL
Section titled “0.1 Product framing inside XEL”| XEL piece | Powered by | Role |
|---|---|---|
| Persona | GEN (optional) | Who the character is |
| Memory | Walrus + Seal | What they remember |
| Mind | Inference providers | How they think |
| Endowment | On-chain Sui object | The money (one balance) |
| Open Yield → Auto Yield | Venue adapters + keepers | Stake / harvest / compound under policy |
| Open Yield → Auto Pay | Payment rails + spend policy | Pay services/agents from yield waterfall |
| Heartbeat | Permissionless keepers | Daily poke that runs harvest + survival |
0.2 External handoff vs XEL truth (read before coding)
Section titled “0.2 External handoff vs XEL truth (read before coding)”A generic “build YieldRouter on Base first + Wormhole + Askew” handoff is directionally right for a standalone DeFi router, but wrong first path for XEL:
| External handoff says | XEL reality |
|---|---|
| Start Base Solidity YieldRouter | Start Sui — endowment + treasury already live; no .sol in repo |
| Adapters Morpho/Aave first | Suilend + NAVI already exist; Morpho/Aave = later Base expansion |
| Wormhole primary | Mayan (Wormhole/CCTP rail) Base/Sol → Sui inbound already |
| x402 as yield data bus | x402 is payments; discovery = DefiLlama, truth = adapter read_apy |
| Auto-compound always on | Stake OFF by default; harvest permissionless; compound residual only |
| New vault shares UX | Per-character endowment object, not multi-user ERC-4626 vault |
MVP path: implement Open Yield inside XEL Sui treasury first, re-expose as capability; optional Base router is Phase B+ only.
Locked-model hard rules (do not violate)
Section titled “Locked-model hard rules (do not violate)”- ONE balance — no keep_alive/storage/interaction allocation buckets.
- Stake is OPTIONAL, OFF by default, owner-initiated (manual stake/unstake). Character never auto-stakes by default.
- ONE auto exception: survival auto-unstake only (storage due, liquid insufficient).
- Fee on yield only (~5% skim at harvest) — never deposits, never withdrawals, never principal.
- Harvest is permissionless via
treasury::heartbeat+ atomic PTB claim (keeper cannot invent amounts). - Owner withdrawal absolute down to zero except fan-credit liability / conditioned tranches.
- Auto Pay never spends fan-credit liability; survival bills beat discretionary agent/service pays.
0.3 External resources (use, but don’t invent paths)
Section titled “0.3 External resources (use, but don’t invent paths)”| Topic | Resource | XEL use |
|---|---|---|
| x402 payments | https://x402.org/ · https://github.com/x402-foundation/x402 | Auto Pay rails (already partial in runtime/payments/x402.mjs) |
| Coinbase AgentKit | https://github.com/coinbase/agentkit | Optional EVM wallet actions later — not required for Sui MVP |
| Wormhole | https://wormhole.com/docs | Via Mayan rail already, not raw integration first |
| Sui / Move | https://docs.sui.io/ | Home chain for endowment |
| Discovery | https://yields.llama.fi/pools | Open Yield map only |
| Existing XEL adapters | runtime/treasury/suilend-adapter.mjs, navi-adapter.mjs | Execution truth |
1. Architecture (end-to-end)
Section titled “1. Architecture (end-to-end)”Owner / Agent │ enable Auto Yield (opt-in) · configure Auto Pay policy ▼┌─────────────────────────────────────────────────────────────┐│ Open Yield Intelligence (offchain, swappable) ││ • Discovery: DefiLlama pools (map) ││ • Truth: venue adapter read_apy ││ • Score: net APY after fee, TVL, liquidity, allowlist ││ • Plan: stake / unstake / harvest / survival-unstake ││ • Auto Pay plan: which bills/agents get residual yield │└───────────────────────────┬─────────────────────────────────┘ │ only via allowlisted adapters ▼┌─────────────────────────────────────────────────────────────┐│ Execution (onchain + adapters) ≈ YieldRouter role on Sui ││ • Endowment (shared object) — one balance ││ • TreasuryPolicy — venue allowlist, caps, fee bps ││ • Adapters: Suilend (default), NAVI (fallback) ││ • Heartbeat: claim_yield → fee skim → residual compound ││ • Auto Pay: spend policy + x402 / provider claims │└───────────────────────────┬─────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────┐│ XEL product surface ││ • Funding snapshot: staked / liquid / net APY / runway ││ • Settings: Open Yield / Auto Yield OFF by default ││ • Auto Pay: pay storage/inference from yield when funded ││ • Events: Harvested, Staked, Unstaked, SurvivalUnstake, Paid │└─────────────────────────────────────────────────────────────┘Trust boundary
Section titled “Trust boundary”| Concern | Trust |
|---|---|
| Principal custody | On-chain endowment only |
| Venue choice for auto paths | Owner policy allowlist |
| APY for display ranking | DefiLlama (discovery) |
| APY for execution / UI realized | Adapter read_apy only |
| Harvest amounts | Venue PTB output, never keeper-supplied |
2. Data layer
Section titled “2. Data layer”2.1 Discovery (map) — DefiLlama + reputation agent
Section titled “2.1 Discovery (map) — DefiLlama + reputation agent”GET https://yields.llama.fi/poolsContinuous agent (not one-shot): periodically scan → filter reputable → diff vs last snapshot → emit new opportunity events for orchestrator / owner review.
Never auto-stakes. Owner Auto Yield still OFF by default; agent only proposes.
Reputation gate (MVP defaults):
| Rule | Default | Rationale |
|---|---|---|
| Min TVL | $50,000,000 USD | “Reputable” floor (configurable via XEL_OPEN_YIELD_MIN_TVL_USD) |
| Asset class | stablecoin pools first | Survival endowment denomination |
| Chains | sui, base, ethereum, solana | Discovery multi-chain; execute Sui allowlist only in MVP |
| Project allowlist mode | optional hard allowlist | When set, only those projects |
| Exclude | ilRisk extreme, outdated, no symbol, meme farms | Fail closed on unknown risk flags when present |
| New opportunity | first seen OR TVL crossed floor | Persist snapshot; alert only on new/changed |
// Pseudocode — runtime/treasury/yield-discovery.mjs + opportunity-agent.mjsconst ALLOWED_CHAINS = new Set(["sui", "base", "ethereum", "solana"]);const MIN_TVL_USD = Number(process.env.XEL_OPEN_YIELD_MIN_TVL_USD || 50_000_000);
function discoverPools(raw) { return raw.data .filter(p => p.stablecoin === true && ALLOWED_CHAINS.has(String(p.chain).toLowerCase()) && Number(p.tvlUsd) >= MIN_TVL_USD ) .sort((a, b) => b.tvlUsd - a.tvlUsd);}
// opportunity-agent: cron / keeper// 1) discoverPools// 2) intersect adapter-ready OR flag as "needs_adapter"// 3) diff vs last snapshot (pool id / project+chain+symbol)// 4) emit open-yield-opportunity.v1 { status: proposed, tvl, apy, chain, project }// 5) never call supply unless owner enabled + venue allowlisted + human/policy goReputation beyond TVL (Phase A light / Phase B full):
- TVL ≥ floor (hard gate)
- Listed on DefiLlama with non-null project
- Prefer audited / known families (Suilend, NAVI, Aave, Morpho, Scallop…) via allowlist boost
- Adapter exists +
read_apysucceeds →executableelsewatch_only - APY sanity: reject if gross APY absurd vs peers (e.g. > 3× median of same-chain stable pools) unless owner risk=high
2.2 Truth (execute) — adapter read_apy
Section titled “2.2 Truth (execute) — adapter read_apy”Existing:
runtime/treasury/suilend-adapter.mjs→action: "read_apy"runtime/treasury/navi-adapter.mjs→action: "read_apy"
Rule: Never stake/unstake on DefiLlama APY alone. Always re-read adapter APY at plan time.
2.3 Allowlist intersection
Section titled “2.3 Allowlist intersection”candidate = discovery ∩ policy.allowed_venues ∩ adapter_readyscore = f(net_apy_bps, tvl_usd, withdraw_liquidity, adapter_health)Default MVP venues (Sui USDC):
| Venue | Role | Adapter |
|---|---|---|
| Suilend | Default stable lending | suilend-adapter.mjs |
| NAVI | Fallback | navi-adapter.mjs |
| Scallop | Listed, adapter later | TBD |
3. Scoring engine (intelligence)
Section titled “3. Scoring engine (intelligence)”File (new): runtime/treasury/auto-yield-scorer.mjs
Inputs (per pool + character policy)
Section titled “Inputs (per pool + character policy)”| Field | Source | Required |
|---|---|---|
gross_apy_bps | adapter read_apy | yes for executable |
protocol_yield_skim_bps | TreasuryPolicy (default 500) | yes |
tvl_usd | DefiLlama | discovery only |
venue_id | allowlist | yes |
adapter_ready | readiness endpoint | yes for execute |
max_allocation_micros | policy | yes |
stakeable_micros | balance − fan_credit − conditioned non-stakeable | yes |
Net APY
Section titled “Net APY”net_apy_bps = gross_apy_bps * (10_000 - protocol_yield_skim_bps) / 10_000Score (deterministic MVP)
Section titled “Score (deterministic MVP)”score = 0.55 * normalize(net_apy_bps) + 0.25 * normalize(log10(tvl_usd)) + 0.20 * (adapter_ready ? 1 : 0)Reject if: not allowlisted, adapter not ready, TVL < floor, or stakeable_micros == 0.
Outputs
Section titled “Outputs”{ "schemaVersion": "auto-yield-plan.v1", "characterId": "...", "mode": "owner_opt_in_stake", "selected": { "venue": "suilend", "marketRef": "market:suilend:usdc", "grossApyBps": 381, "netApyBps": 343, "amountMicros": 500000000 }, "alternatives": [], "blockers": []}4. Execution layer (already partially built)
Section titled “4. Execution layer (already partially built)”4.1 On-chain (Sui Move) — reuse / extend
Section titled “4.1 On-chain (Sui Move) — reuse / extend”| Entry | Purpose | Status |
|---|---|---|
treasury::create_endowment | Shared endowment object | exists |
treasury::top_up_endowment | Deposit (no fee) | exists |
treasury::withdraw_endowment_principal | Owner withdraw | exists |
treasury::heartbeat | Permissionless harvest cycle | exists |
treasury::configure_yield_harvest | Interval / claim flags | exists |
treasury::set_yield_venue_allowed | On-chain allowlist hashes | exists |
record_stake_intent / record_claim_yield_intent | Intent audit | exists |
MVP contract work (if not already on v3):
- Align endowment to single balance (delete allocation buckets if still present in deployed code path).
- Emit
SurvivalUnstakeevent path for auto-unstake-only exception. - Ensure heartbeat residual compounds into principal; fee skim only from gross yield.
4.2 Adapters
Section titled “4.2 Adapters”| Action | Suilend | NAVI |
|---|---|---|
read_apy | yes | yes |
supply / deposit | yes | yes |
withdraw | yes | yes |
claim_yield | yes | yes |
Gates (all must pass for broadcast):
XEL_STABLECOIN_YIELD_EXECUTION_ENABLED- per-venue
*_ALLOW_BROADCAST - signer present
- allowlist match
- per-action max micros caps
- not mainnet_execution_disabled (close XEL-70 seam)
4.3 Backend API surface (extend existing)
Section titled “4.3 Backend API surface (extend existing)”Existing under /api/treasury/sui/stablecoin-yield/*:
GET .../readinessPOST .../planPOST .../executePOST .../auto-stake← rename product semantics: only runs when owner Auto Yield enabled
New / rename for product clarity:
| Endpoint | Purpose |
|---|---|
GET /api/characters/{id}/auto-yield | Status: enabled, venue, staked, apy, last harvest |
PUT /api/characters/{id}/auto-yield | Owner toggle + amount/risk envelope |
POST /api/characters/{id}/auto-yield/discover | DefiLlama ∩ allowlist ∩ scores |
POST /api/characters/{id}/auto-yield/stake | Owner-initiated stake plan+execute |
POST /api/characters/{id}/auto-yield/unstake | Owner-initiated unstake |
POST /api/characters/{id}/auto-yield/harvest/plan | Dry-run harvest PTB (keeper/owner) |
GET /api/auto-yield/venues | Public readiness + live APYs |
Keep old stablecoin-yield routes as aliases during migration.
4.4 Keepers
Section titled “4.4 Keepers”| Component | File | Role |
|---|---|---|
| Heartbeat runner | runtime/keepers/heartbeat-runner.mjs | Permissionless harvest poke |
| Lifecycle | runtime/treasury/lifecycle.mjs | stake / claim_yield / sweep plans |
| Gas sponsor | runtime/treasury/gas-sponsor.mjs | Sponsored PTB bundle |
| Wallet watcher | runtime/treasury/wallet-watcher*.mjs | Inbound fund recognition |
Harvest flow (permissionless):
- Keeper loads shared endowment + position refs.
- Adapter builds
claim_yieldPTB command producingnetYieldcoin. - Same PTB calls
treasury::heartbeat(fee skim + residual compound + storage path if due). - Bounty paid from policy; no keeper-supplied amounts.
Survival auto-unstake only:
- Storage renewal due; liquid < bill.
- Unstake minimum micros from venue.
- Never for chat/interaction; never below fan-credit liability.
- Emit event.
5. Fees (required — already partially onchain)
Section titled “5. Fees (required — already partially onchain)”Open Yield takes protocol fees. Locked rules:
| Fee | Default | When | Never |
|---|---|---|---|
| Yield skim | ~5% (protocol_yield_skim_bps = 500) | At harvest, on gross yield only | Deposits, tips, top-ups, withdrawals, principal |
| Storage routing fee | same ~5% family | On storage renewal when XEL routes | Self-provisioned storage |
| Interaction fee | on post-compute margin | At settlement | Unspent fan credit refunds |
| Harvest keeper bounty | policy heartbeat_bounty | Permissionless poke incentive | Not a second % tip on top of skim unless product says so |
| Bridge / swap | pass-through venue cost | Mayan/Turbos quotes | Hidden markup; gas sponsor overage stays with character |
Onchain (exists): TreasuryPolicy.protocol_yield_skim_bps, waterfall protocol_skim_micros at harvest.
Must wire in Open Yield product:
- Estimates always show gross APY → net APY after fee.
- Harvest path always skims fee to protocol treasury (fail if fee sink misconfigured).
- UI/API never imply “fee-free yield.”
- Fee rate configurable via policy; default ~5% (500 bps); policy-configurable forever (LOCKED-MODEL).
- Auto Pay spends only post-fee residual after survival bills.
Fee destination: protocol fee wallet / treasury object (XEL-configured); never keeper’s arbitrary address for the skim.
gross_yield → protocol_skim = gross * skim_bps / 10000 // Open Yield fee → net_yield = gross - protocol_skim → survival bills (storage, etc.) → residual compound + optional Auto Pay5b. Yield estimates (auto)
Section titled “5b. Yield estimates (auto)”Every opportunity + character position must expose honest estimates (no invented APY).
| Estimate | Formula / source | Surface |
|---|---|---|
gross_apy_bps | adapter read_apy (preferred) or DefiLlama for watch_only | opportunities, venues |
net_apy_bps | gross * (10000 - skim_bps) / 10000 | always |
est_daily_yield_micros | staked_micros * net_apy_bps / 10000 / 365 | funding snapshot |
est_monthly_yield_micros | daily * 30 | funding / Auto Pay |
runway_days | existing total-bill / net yield coverage math | character health |
bridge_cost_estimate | Mayan quote | before inbound sweep |
swap_cost_estimate | Turbos (or route) quote | in-chain USDC↔SUI gas path |
gas_sponsor_budget | gas-sponsor sizing | stake/harvest/sweep plans |
Rules: missing adapter APY → null / N/A, not demo 5%. Estimates labeled estimate, not guaranteed.
API: include estimates block on GET .../auto-yield and opportunity objects.
5c. Multichain bridging + in-chain swapping
Section titled “5c. Multichain bridging + in-chain swapping”Bridging (cross-chain)
Section titled “Bridging (cross-chain)”| Direction | Provider | Status | Open Yield use |
|---|---|---|---|
| Base / Solana USDC → Sui | Mayan (mayan-executor.mjs, CCTP/Wormhole rails) | Built, execution gated | Fund endowment before stake |
| Sui → Base/Sol | Not MVP | — | No principal rebalance off-home in MVP |
| Arbitrary rebalance to foreign yield | Phase B+ | — | Only if owner policy allows non-home |
Flow: wallet-watcher detects USDC → gas-front if needed → Mayan quote → bridge → confirm delivery → credit endowment → (optional) owner Auto Yield stake.
In-chain swapping (Sui)
Section titled “In-chain swapping (Sui)”| Swap | Venue | File | Use |
|---|---|---|---|
| USDC → SUI (gas) | Turbos (default) | turbos-route.mjs + gas-sponsor.mjs | Sponsored ops without holding SUI float |
| Other stables / LST | later | — | Only allowlisted pools |
Swaps are policy-capped (max slippage, allowlisted venues). Lifecycle already has swap action class.
5d. Gas sponsorship (required)
Section titled “5d. Gas sponsorship (required)”Characters often hold USDC only. Open Yield ops (stake, harvest, bridge repay, unstake) need native gas.
| Piece | File | Behavior |
|---|---|---|
| Sui gas sponsor | runtime/treasury/gas-sponsor.mjs | Character sender, XEL gasOwner, one USDC→SUI swap, repay sponsor in SUI, overage stays with character |
| Base/Sol gas front | wallet-watcher + Mayan path | Front native gas to move USDC, repay from bridged proceeds (existing markup rules) |
Open Yield requirement: every stake/harvest/unstake/sweep plan must include gas-sponsor (or fail-closed if sponsor missing). No faucet fallback in prod (XEL-72).
5e. Cross-chain summary (MVP vs later)
Section titled “5e. Cross-chain summary (MVP vs later)”| Capability | MVP | Later |
|---|---|---|
| Bridge in (Base/Sol → Sui) | Yes (Mayan) | — |
| In-chain swap for gas | Yes (Turbos + sponsor) | more venues |
| Fees on yield | Yes (~5% skim) | rate governance |
| Yield estimates | Yes (gross/net/runway) | multi-venue portfolio |
| Bridge out / multi-home yield | No | Phase B+ |
| Auto rebalance across chains | No | Phase B+ |
6. XEL re-integration checklist
Section titled “6. XEL re-integration checklist”| Surface | Change |
|---|---|
| Public skill / docs | Document Auto Yield capability in skill.md / treasury docs |
| Character funding UI | Show liquid / staked / net APY / last harvest; toggle OFF default |
| Settings | ”Auto Yield” card: off by default; owner sets amount |
| Funding snapshot API | Include auto_yield block (no invented APY if missing) |
| Provider registry | Optional treasury.yield.v1 capability entry |
| Persona path | Unchanged — GEN still optional persona only |
| x402 | Pay for inference/services; not primary yield data bus |
7. Security & fail-closed
Section titled “7. Security & fail-closed”- No broadcast without dual flags + signer + allowlist.
- Secrets never in API responses (existing redaction).
- Fan credit ring-fence before any stake math.
- Conditioned tranches: stakeable-during-hold respected.
- Mainnet disabled until XEL-70 NO-GO seams closed.
- DefiLlama outage → discovery empty; existing positions still harvest via adapters.
8. MVP scope vs later
Section titled “8. MVP scope vs later”Phase A — Ship Open Yield inside XEL (Sui only) — 1–2 weeks fan-out
Section titled “Phase A — Ship Open Yield inside XEL (Sui only) — 1–2 weeks fan-out”- Discovery module + scorer + API.
- Owner Auto Yield toggle (OFF default).
- Owner stake/unstake via Suilend (NAVI fallback).
- Permissionless harvest via heartbeat (already mostly built).
- Survival auto-unstake path.
- Auto Pay v0: after harvest/survival, residual may fund storage renewal + inference within spend policy (x402 / provider claims already partial).
- Honest UI (real APY only).
- Close XEL-70 enablement seams for opt-in stake (not default auto).
Phase B
Section titled “Phase B”- Scallop adapter
- Auto Pay multi-agent splits (pay other agents from yield)
- Base Morpho/Aave adapters (standalone Open Yield expansion)
- Optional Base
YieldRouter.solonly if product expands beyond character endowments - x402 yield-data provider (optional alternative to DefiLlama)
- Multi-venue rebalance proposals (advisor only until owner signs)
Explicitly out of MVP
Section titled “Explicitly out of MVP”- Fully automatic stake without owner enable
- Leveraged strategies
- Governance token
- Trusting DefiLlama for execution amounts
- Building Base Foundry vault before Sui Open Yield works in XEL
15. Orchestration: offchain (confirmed)
Section titled “15. Orchestration: offchain (confirmed)”Decision making, scoring, goal interpretation = OFFCHAIN open agents.
Does not need to run fully on Sui / Base / Solana.
| Layer | Where | Why |
|---|---|---|
| Score / discover / choose venue | Offchain orchestrator | Gameable if “onchain oracle of best APY” |
| Goal parse (“stable runway”) | Offchain | Natural language + policy |
deposit / withdraw / claim / harvest PTB | Onchain + adapters | Money trust |
| Heartbeat poke | Offchain keepers, permissionless | Anyone can fire; amounts still onchain |
| Auto Pay routing plan | Offchain within spend policy | Execution still policy-gated |
Onchain can hold policy + allowlist + caps + fee math. It should not hold “pick the best pool in the world.”
16. Per-chain strategy (XEL-integrated Open Yield)
Section titled “16. Per-chain strategy (XEL-integrated Open Yield)”Strategy lock (overrides generic “Base-first MVP” blogs)
Section titled “Strategy lock (overrides generic “Base-first MVP” blogs)”| Goal | Choice for this product |
|---|---|
| Fastest path that ships on XEL characters | Sui first (execution already ~70% built) |
| Standalone multi-chain Open Yield later | Chain-agnostic interface; add Base/Sol after Sui Auto Yield works |
| Orchestration | Always offchain (all chains) |
| Cross-chain for XEL MVP | Inbound only Base/Sol → Sui via Mayan (already) |
Generic advice “start Base because AgentKit + x402 mature” is true for a greenfield EVM vault.
XEL already homes endowments on Sui — building Base YieldRouter first would not power characters until you also bridge + re-home funds.
Per-chain matrix (what to build when)
Section titled “Per-chain matrix (what to build when)”| Component | Base | Solana | Sui (MVP home) |
|---|---|---|---|
| Execution contracts | Solidity YieldRouter later (Phase B+) | Anchor vault later (Phase C) | Now: endowment + treasury + venue allowlist |
| Yield adapters | Morpho / Aave later | Kamino / etc later | Now: Suilend + NAVI (Scallop next) |
| x402 | Mature (payments) | Supported | Now: payments via existing XEL x402 runtime |
| Agent wallets | AgentKit later if EVM operator | Optional | Now: hosted Sui + AgentCap policies |
| Bridge role | Source of USDC into XEL | Source of USDC into XEL | Destination endowment (home) |
| Orchestrator | Same offchain agent | Same | Same |
| Auto Harvest | Later per-chain router | Later | Now: heartbeat + claim PTB |
| Auto Pay | Later | Later | Now: yield → storage/inference under policy |
What “per chain” means in Open Yield design
Section titled “What “per chain” means in Open Yield design”Do not require three full products day one. Require a chain adapter interface:
open-yield.chain-adapter.v1 discover(local) | readApy | supply | withdraw | claimYield | health| Phase | Chains live for execution | Orchestrator |
|---|---|---|
| A (MVP) | Sui only | Offchain; DefiLlama multi-chain discovery OK for ranking but execute only Sui allowlist |
| B | Sui + optional Base adapter | Same agent; can deposit on Base only if character policy allows non-home (default: no) |
| C | + Solana | Same |
Per-chain checklist (implementation)
Section titled “Per-chain checklist (implementation)”Sui — Phase A (do now) — tickets XEL-153…163
Section titled “Sui — Phase A (do now) — tickets XEL-153…163”Map generic “Sui-first Open Yield” → existing XEL names (do not greenfield):
| Generic handoff name | XEL already / build as |
|---|---|
| YieldRouter (Move) | treasury + shared Endowment (not new vault package first) |
| FeeModule | protocol_yield_skim_bps + waterfall in treasury |
| AdapterRegistry | set_yield_venue_allowed + backend allowlist env |
| Protocol adapters first | Suilend + NAVI (done) — Scallop/Haedal/Kai = next, not first |
| harvest() | treasury::heartbeat + adapter claim_yield PTB |
| Auto Pay | spend policy + x402 + AY-11 |
| Orchestrator | offchain keepers + new discovery/scorer (AY-01/02) |
| x402 yield data | No — DefiLlama discovery; x402 = Auto Pay payments |
| Turnkey wallets | optional later; XEL hosted Sui + AgentCap now |
- Endowment shared object + top_up / withdraw
- Fee on yield skim path
- Suilend + NAVI adapters +
read_apy - Heartbeat keeper skeleton
- Mayan inbound Base/Sol → Sui
- Open Yield discovery + scorer (AY-01/02)
- Owner Auto Yield toggle OFF default (AY-04)
- Owner stake/unstake + harvest PTB polish (AY-05/06)
- Survival auto-unstake (AY-07)
- Auto Pay waterfall (AY-11 / XEL-163)
- FE + enablement (AY-08/09)
- Docs brand Open Yield (AY-10)
Base — Phase B (only after Sui Auto Yield live)
Section titled “Base — Phase B (only after Sui Auto Yield live)”- Foundry
YieldRouter+FeeModule+AdapterRegistry(if expanding beyond character-home model) - Morpho + Aave adapters
- Keep Base primarily as funding rail into Sui until product needs Base-native yield positions
- AgentKit optional for EVM operator agents
- Do not move XEL character home off Sui without owner-signed re-home
Solana — Phase C
Section titled “Solana — Phase C”- Anchor vault or continue “receive + bridge to Sui” only
- Yield adapters only if multi-home Open Yield productizes
- Default for XEL characters remains: land on Sol receive wallet → Mayan → Sui endowment
Orchestration deployment model
Section titled “Orchestration deployment model”[ Open Yield Orchestrator ] ← open source, anyone runs │ reads DefiLlama + adapter read_apy │ respects character TreasuryPolicy ▼ chain adapter (sui | base | sol) ▼ onchain execution only- One orchestrator, many chain adapters.
- Not three separate brains per chain.
- XEL ships a default orchestrator/keeper; third parties can replace.
Decision record
Section titled “Decision record”| Question | Answer |
|---|---|
| Orchestration fully on Sui? | No — offchain agents |
| Start chain for XEL Open Yield? | Sui |
| Start chain for greenfield EVM-only Open Yield? | Base (out of scope for current XEL epic) |
| Multi-chain day 1? | Discovery multi-chain; execution Sui-only |
| Cross-chain? | Inbound to Sui first; no auto rebalance off-home in MVP |
9. Multi-agent fan-out map
Section titled “9. Multi-agent fan-out map”| Lane | Ticket | Repo area | Depends on |
|---|---|---|---|
| A | AY-01 Discovery | runtime/treasury/yield-discovery.mjs | none |
| B | AY-02 Scorer | runtime/treasury/auto-yield-scorer.mjs | AY-01 schema only |
| C | AY-03 API surface | backend/main.py | AY-01/02 |
| D | AY-04 Owner toggle + policy | backend + schema | none |
| E | AY-05 Stake/unstake executor | adapters + backend | AY-04 |
| F | AY-06 Harvest PTB integration | keepers + lifecycle | AY-05 optional |
| G | AY-07 Survival auto-unstake | keepers + contracts | AY-05 |
| H | AY-08 FE Auto Yield settings | frontend/ | AY-03 |
| I | AY-09 Enablement / prod gates | ops + runbook | A–G |
| J | AY-10 Docs + skill re-integration | docs/, site/ | parallel |
| K | AY-12 Opportunity agent (TVL≥$50M) | discovery + cron | AY-01 |
| L | AY-13 Yield estimates | scorer + API | AY-02 |
| M | AY-14 Bridge + in-chain swap | mayan + turbos | parallel |
| N | AY-15 Gas sponsorship on plans | gas-sponsor.mjs | AY-05/06 |
| O | AY-16 Protocol fee take E2E | treasury skim + UI | AY-06 |
| P | AY-11 Auto Pay | spend policy + x402 | AY-16 |
Parallel waves:
- Wave 1 (parallel): AY-01, AY-02, AY-04, AY-10, AY-12
- Wave 2 (parallel): AY-03, AY-05, AY-06, AY-07, AY-13, AY-14, AY-16
- Wave 3 (parallel): AY-08, AY-09, AY-11, AY-15
Each ticket below has deterministic test cases required for merge.
10. Ticket pack (copy into Plane)
Section titled “10. Ticket pack (copy into Plane)”Shared acceptance (every ticket)
Section titled “Shared acceptance (every ticket)”- No invented money numbers in UI/API when data missing (
null/ omit /$0only when truly zero). - Tests must be deterministic: fixed fixtures, no live mainnet required for CI.
- Live mainnet smoke is optional gate documented separately; never the only test.
- Branch:
.claude/worktrees/xel-ay-<id>/frommain. - Do not flip prod broadcast flags without explicit mav go.
AY-01 — Yield discovery (DefiLlama map)
Section titled “AY-01 — Yield discovery (DefiLlama map)”Title: Auto Yield: DefiLlama discovery module (stablecoin, multi-chain filter, TVL sort)
Scope:
- Add
runtime/treasury/yield-discovery.mjs - Fetch/normalize DefiLlama
/pools(injectable fetch for tests) - Filters: stablecoin, chains, min TVL, optional project allowlist
- Output schema
auto-yield-discovery.v1
Deterministic tests (tests/yield-discovery.test.mjs):
| # | Case | Input | Expected |
|---|---|---|---|
| T1 | TVL sort desc | 3 pools TVL 5M, 1M, 10M | order 10M, 5M, 1M |
| T2 | Min TVL floor | pool TVL 999_999 | excluded |
| T3 | Non-stablecoin excluded | stablecoin: false | excluded |
| T4 | Chain filter | chain aptos | excluded when only sui/base/eth/sol allowed |
| T5 | Empty upstream | {data:[]} | {pools:[], blockers:["empty_upstream"]} |
| T6 | Fetch failure | fetch throws | fail-closed status blocked, no throw uncaught |
| T7 | Schema version | any success | schemaVersion === "auto-yield-discovery.v1" |
| T8 | Idempotent normalize | same fixture twice | deep-equal outputs |
Done when: npm run test:yield-discovery (or suite include) green.
AY-02 — Auto Yield scorer
Section titled “AY-02 — Auto Yield scorer”Title: Auto Yield: scoring engine (net APY after fee, allowlist, adapter readiness)
Scope:
runtime/treasury/auto-yield-scorer.mjsnet_apy_bps = gross * (10000 - skim) / 10000- Intersect discovery + allowlist + readiness
- Produce
auto-yield-plan.v1
Deterministic tests (tests/auto-yield-scorer.test.mjs):
| # | Case | Input | Expected |
|---|---|---|---|
| T1 | Net APY math | gross 500 bps, skim 500 | net 950 bps |
| T2 | Skim 0 | gross 500, skim 0 | net 500 |
| T3 | Reject not allowlisted | venue morpho | blockers includes venue_not_allowed |
| T4 | Reject adapter not ready | ready=false | not selected |
| T5 | Prefer higher net APY | two ready venues | higher net wins |
| T6 | Zero stakeable | stakeable_micros=0 | plan mode no_op, selected null |
| T7 | Cap amount | stakeable 10e6, max 3e6 | amountMicros 3e6 |
| T8 | Fan credit excluded | balance 10, liability 4 | stakeable ≤ 6 (same units) |
| T9 | Tie-break | equal score | stable sort by venue id asc |
| T10 | Missing gross APY | no adapter apy | cannot be executable selected |
Done when: scorer pure functions covered; no network I/O in unit tests.
AY-03 — Backend Auto Yield API
Section titled “AY-03 — Backend Auto Yield API”Title: Auto Yield: character + public API surface (discover/status/plan)
Scope:
GET /api/characters/{id}/auto-yieldPOST /api/characters/{id}/auto-yield/discoverGET /api/auto-yield/venues- Wire discovery + scorer; no broadcast in this ticket
- Auth: owner for character routes; public venues read-only
Deterministic tests (tests/backend_auto_yield_api_test.py):
| # | Case | Expected |
|---|---|---|
| T1 | Status when never enabled | enabled=false, staked=0, apy null/omit |
| T2 | Discover with fixture monkeypatch | returns scored pools, no secrets |
| T3 | Non-owner discover | 401/403 |
| T4 | Venues readiness shape | each venue has ready, gross_apy_bps nullable |
| T5 | Missing character | 404 |
| T6 | DefiLlama down | 200 with empty pools + blocker, not 500 |
| T7 | No invented APY | when adapter fails, apy fields null not “5%“ |
| T8 | Response redaction | no secret, keypair, private keys |
Done when: pytest green with mocked discovery/adapters.
AY-04 — Owner toggle + policy persistence
Section titled “AY-04 — Owner toggle + policy persistence”Title: Auto Yield: owner opt-in toggle (OFF default) + envelope storage
Scope:
- Persist
auto_yield_enabled,target_venue,max_stake_micros,enabled_at - Default OFF for all characters
- Align with locked model (no auto-stake without enable)
- Schema/migration if needed
Deterministic tests:
| # | Case | Expected |
|---|---|---|
| T1 | Fresh character | enabled false |
| T2 | Owner enables | enabled true, audit row |
| T3 | Owner disables | enabled false; does not force unstake in this ticket (document follow-up) |
| T4 | Non-owner PUT | 403 |
| T5 | Invalid max_stake negative | 400 |
| T6 | Enable without venue when only one allowlisted | defaults to suilend or explicit required field |
| T7 | Policy reload | GET matches last PUT |
Done when: DB + API round-trip tests pass.
AY-05 — Owner stake / unstake executor
Section titled “AY-05 — Owner stake / unstake executor”Title: Auto Yield: owner-initiated stake/unstake via Suilend/NAVI adapters
Scope:
POST .../auto-yield/stakeand.../unstake- Reuse stablecoin-yield plan/execute
- Require
auto_yield_enabledOR explicit one-shot owner stake flag (product choice: require enable first) - Respect caps, allowlist, fan-credit, conditioned stakeable flag
- No default auto-stake cron
Deterministic tests (tests/backend_auto_yield_stake_test.py + adapter mocks):
| # | Case | Expected |
|---|---|---|
| T1 | Stake while disabled | 403/blocked auto_yield_disabled |
| T2 | Stake while enabled, mock adapter | audit supply, digest mock, no secret leak |
| T3 | Amount above stakeable | capped or 400 per contract (pick one; document) |
| T4 | Venue not allowlisted | blocked |
| T5 | Unstake partial | withdraw amount respected |
| T6 | Unstake more than position | fail-closed |
| T7 | Broadcast flags off | status prepared/blocked not submitted |
| T8 | NAVI fallback when Suilend not ready | selects navi if policy allows |
| T9 | Idempotency key replay | no double supply |
| T10 | Conditioned non-stakeable tranche | excluded from stakeable |
Done when: mock execution path green; live smoke is AY-09.
AY-06 — Permissionless harvest integration
Section titled “AY-06 — Permissionless harvest integration”Title: Auto Yield: harvest via heartbeat PTB (claim_yield → fee → compound)
Scope:
- Compose adapter
claim_yieldinto heartbeat PTB (XEL-67 path) - Fee skim only on yield; residual compounds
- Permissionless: keeper or anyone with gas/bounty path
- No keeper-supplied net_yield amount
Deterministic tests:
| # | Case | Expected |
|---|---|---|
| T1 | Plan without position | no-op harvest, heartbeat still valid if policy allows zero yield |
| T2 | Mock claim produces coin | heartbeat consumes same PTB result ref |
| T3 | Fee skim 5% | net to principal 90% of gross fixture |
| T4 | Fee never on principal top-up | separate top_up test unchanged amount |
| T5 | Missing endowment object | fail-closed skip |
| T6 | Shared object model required | owned-only state blocked for external keeper |
| T7 | Bounty ≤ yield rule | if bounty > yield, contract/sim error |
| T8 | Multi-command plan order | claim before heartbeat |
Done when: unit/integration with mocked PTB builder green.
AY-07 — Survival auto-unstake only
Section titled “AY-07 — Survival auto-unstake only”Title: Auto Yield: survival auto-unstake (storage due, minimum only)
Scope:
- When storage renewal due AND liquid < bill AND staked > 0 → unstake min(shortfall, staked)
- Never for interaction
- Never below fan-credit liability
- Event + audit
Deterministic tests:
| # | Case | Expected |
|---|---|---|
| T1 | Storage due, liquid enough | no unstake |
| T2 | Storage due, liquid short 2, staked 10 | unstake 2 |
| T3 | Shortfall 10, staked 3 | unstake 3 |
| T4 | Interaction shortfall only | no unstake |
| T5 | Fan credit would be breached | unstake reduced/blocked per rule |
| T6 | No stake position | no-op |
| T7 | Broadcast off | plan only |
| T8 | Event payload | includes reason survival_storage |
Done when: pure policy tests + mocked executor tests pass.
AY-08 — Frontend Auto Yield
Section titled “AY-08 — Frontend Auto Yield”Title: FE: Auto Yield settings + funding honesty (off default, real APY only)
Scope:
- Settings card: toggle, staked amount, venue, net APY
- Funding page: liquid vs staked, last harvest
- No demo 5% APY constants
- Empty/error → hide or N/A
Deterministic tests (component/unit):
| # | Case | Expected |
|---|---|---|
| T1 | enabled false default copy | shows Off / enable CTA |
| T2 | apy null | shows N/A now 5% product default |
| T3 | staked + liquid render | correct split from fixture |
| T4 | toggle calls PUT | mock fetch once |
| T5 | error state | no crash, no invented numbers |
Done when: FE unit tests green; no STAKING_APY = 0.05 demo left on Auto Yield surfaces.
AY-09 — Prod enablement gates (opt-in path)
Section titled “AY-09 — Prod enablement gates (opt-in path)”Title: Auto Yield: close enablement NO-GO seams (SDK roots, flags, keeper roster)
Scope:
- Document supersedes auto-stake-by-default language → owner opt-in
- Install isolated Suilend/NAVI SDK roots on prod path
- Resolve
mainnet_execution_disabledfor approved owner stake only - Keeper roster with shared endowment ids
- Pre-broadcast prepare smoke
Deterministic / checklist tests:
| # | Case | Expected |
|---|---|---|
| T1 | readiness endpoint | suilend ready true only if SDK root resolves |
| T2 | flags default | execution false, broadcast false in staging template |
| T3 | prepare smoke fixture | status prepared without submit |
| T4 | runbook updated | no “character NEVER auto-stakes” contradiction with opt-in cron |
| T5 | kill switch | single env disables all broadcast |
Done when: mav-approved staging prepare smoke; prod flags still false until explicit go.
AY-10 — Docs + skill re-integration
Section titled “AY-10 — Docs + skill re-integration”Title: Docs: Auto Yield as XEL capability (persona by GEN, funds by Auto Yield)
Scope:
- New
docs/23-auto-yield.md - Update
docs/08-payments-treasury-provider-discovery.mdPhase 4 language - Update
site/skill.mdtreasury capability blurb - Link locked model rules
- Deprecate “GEN Yield Router” external naming in XEL docs
Deterministic checks:
| # | Case | Expected |
|---|---|---|
| T1 | Doc exists | docs/23-auto-yield.md present |
| T2 | Locked rules cited | stake off default, fee on yield only |
| T3 | No OpenYield product name | grep clean in new docs |
| T4 | API routes listed match AY-03 | path parity |
Done when: docs merged; validate script if any link check exists.
11. Suggested Plane epic
Section titled “11. Suggested Plane epic”Epic title: XEL Auto Yield (treasury capability) — discovery, owner stake, permissionless harvest
Children: AY-01 … AY-10 (create as XEL issues; renumber to next free XEL-ids).
Labels (if used): auto-yield, treasury, sui, backend, frontend, contracts, docs
12. Agent fan-out instructions (for herdr / parallel sessions)
Section titled “12. Agent fan-out instructions (for herdr / parallel sessions)”Each agent gets one ticket, one nested worktree:
cd /root/projects/XELgit fetch origingit worktree add .claude/worktrees/xel-ay-01 -b feat/xel-ay-01-discovery origin/mainPrompt template:
Implement XEL ticket AY-0N from docs/23-auto-yield-implementation.md.Repo: /root/projects/XEL worktree only.Follow LOCKED-MODEL: stake off default; fee on yield only; no invented APY.Deliver: code + deterministic tests listed in ticket. Do not enable prod broadcast.I can fan out multiple agents (separate worktrees) once you say go on ticket creation + which waves to start.
13. Definition of done (MVP product)
Section titled “13. Definition of done (MVP product)”- Owner can enable Auto Yield and stake USDC on Suilend (testnet/staging).
- Permissionless harvest compounds residual and skims protocol fee on yield only.
- Survival auto-unstake works when storage due.
- UI shows real APY/staked or honest empty states.
- Discovery ranks venues by TVL/score but execution uses adapter truth.
- Default new characters: Auto Yield OFF, liquid endowment only.
14. Non-goals reminder
Section titled “14. Non-goals reminder”- Not a general multi-user ERC-4626 YieldRouter on Base in this epic.
- Not replacing XEL endowment with an external protocol brand.
- Not auto-staking every character by default (locked model).