Skip to content

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


NameWhat it is
Open YieldOpen protocol / capability brand for permissionless capital routing + harvest
Auto YieldOwner-facing mode: opt-in stake, permissionless harvest, compound residual
Auto PayRoute 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.

XEL piecePowered byRole
PersonaGEN (optional)Who the character is
MemoryWalrus + SealWhat they remember
MindInference providersHow they think
EndowmentOn-chain Sui objectThe money (one balance)
Open Yield → Auto YieldVenue adapters + keepersStake / harvest / compound under policy
Open Yield → Auto PayPayment rails + spend policyPay services/agents from yield waterfall
HeartbeatPermissionless keepersDaily 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 saysXEL reality
Start Base Solidity YieldRouterStart Sui — endowment + treasury already live; no .sol in repo
Adapters Morpho/Aave firstSuilend + NAVI already exist; Morpho/Aave = later Base expansion
Wormhole primaryMayan (Wormhole/CCTP rail) Base/Sol → Sui inbound already
x402 as yield data busx402 is payments; discovery = DefiLlama, truth = adapter read_apy
Auto-compound always onStake OFF by default; harvest permissionless; compound residual only
New vault shares UXPer-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.

  1. ONE balance — no keep_alive/storage/interaction allocation buckets.
  2. Stake is OPTIONAL, OFF by default, owner-initiated (manual stake/unstake). Character never auto-stakes by default.
  3. ONE auto exception: survival auto-unstake only (storage due, liquid insufficient).
  4. Fee on yield only (~5% skim at harvest) — never deposits, never withdrawals, never principal.
  5. Harvest is permissionless via treasury::heartbeat + atomic PTB claim (keeper cannot invent amounts).
  6. Owner withdrawal absolute down to zero except fan-credit liability / conditioned tranches.
  7. 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)”
TopicResourceXEL use
x402 paymentshttps://x402.org/ · https://github.com/x402-foundation/x402Auto Pay rails (already partial in runtime/payments/x402.mjs)
Coinbase AgentKithttps://github.com/coinbase/agentkitOptional EVM wallet actions later — not required for Sui MVP
Wormholehttps://wormhole.com/docsVia Mayan rail already, not raw integration first
Sui / Movehttps://docs.sui.io/Home chain for endowment
Discoveryhttps://yields.llama.fi/poolsOpen Yield map only
Existing XEL adaptersruntime/treasury/suilend-adapter.mjs, navi-adapter.mjsExecution truth

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 │
└─────────────────────────────────────────────────────────────┘
ConcernTrust
Principal custodyOn-chain endowment only
Venue choice for auto pathsOwner policy allowlist
APY for display rankingDefiLlama (discovery)
APY for execution / UI realizedAdapter read_apy only
Harvest amountsVenue PTB output, never keeper-supplied

2.1 Discovery (map) — DefiLlama + reputation agent

Section titled “2.1 Discovery (map) — DefiLlama + reputation agent”
GET https://yields.llama.fi/pools

Continuous 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):

RuleDefaultRationale
Min TVL$50,000,000 USD“Reputable” floor (configurable via XEL_OPEN_YIELD_MIN_TVL_USD)
Asset classstablecoin pools firstSurvival endowment denomination
Chainssui, base, ethereum, solanaDiscovery multi-chain; execute Sui allowlist only in MVP
Project allowlist modeoptional hard allowlistWhen set, only those projects
ExcludeilRisk extreme, outdated, no symbol, meme farmsFail closed on unknown risk flags when present
New opportunityfirst seen OR TVL crossed floorPersist snapshot; alert only on new/changed
// Pseudocode — runtime/treasury/yield-discovery.mjs + opportunity-agent.mjs
const 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 go

Reputation beyond TVL (Phase A light / Phase B full):

  1. TVL ≥ floor (hard gate)
  2. Listed on DefiLlama with non-null project
  3. Prefer audited / known families (Suilend, NAVI, Aave, Morpho, Scallop…) via allowlist boost
  4. Adapter exists + read_apy succeeds → executable else watch_only
  5. APY sanity: reject if gross APY absurd vs peers (e.g. > 3× median of same-chain stable pools) unless owner risk=high

Existing:

  • runtime/treasury/suilend-adapter.mjsaction: "read_apy"
  • runtime/treasury/navi-adapter.mjsaction: "read_apy"

Rule: Never stake/unstake on DefiLlama APY alone. Always re-read adapter APY at plan time.

candidate = discovery ∩ policy.allowed_venues ∩ adapter_ready
score = f(net_apy_bps, tvl_usd, withdraw_liquidity, adapter_health)

Default MVP venues (Sui USDC):

VenueRoleAdapter
SuilendDefault stable lendingsuilend-adapter.mjs
NAVIFallbacknavi-adapter.mjs
ScallopListed, adapter laterTBD

File (new): runtime/treasury/auto-yield-scorer.mjs

FieldSourceRequired
gross_apy_bpsadapter read_apyyes for executable
protocol_yield_skim_bpsTreasuryPolicy (default 500)yes
tvl_usdDefiLlamadiscovery only
venue_idallowlistyes
adapter_readyreadiness endpointyes for execute
max_allocation_microspolicyyes
stakeable_microsbalance − fan_credit − conditioned non-stakeableyes
net_apy_bps = gross_apy_bps * (10_000 - protocol_yield_skim_bps) / 10_000
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.

{
"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”
EntryPurposeStatus
treasury::create_endowmentShared endowment objectexists
treasury::top_up_endowmentDeposit (no fee)exists
treasury::withdraw_endowment_principalOwner withdrawexists
treasury::heartbeatPermissionless harvest cycleexists
treasury::configure_yield_harvestInterval / claim flagsexists
treasury::set_yield_venue_allowedOn-chain allowlist hashesexists
record_stake_intent / record_claim_yield_intentIntent auditexists

MVP contract work (if not already on v3):

  1. Align endowment to single balance (delete allocation buckets if still present in deployed code path).
  2. Emit SurvivalUnstake event path for auto-unstake-only exception.
  3. Ensure heartbeat residual compounds into principal; fee skim only from gross yield.
ActionSuilendNAVI
read_apyyesyes
supply / deposityesyes
withdrawyesyes
claim_yieldyesyes

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)

Existing under /api/treasury/sui/stablecoin-yield/*:

  • GET .../readiness
  • POST .../plan
  • POST .../execute
  • POST .../auto-stakerename product semantics: only runs when owner Auto Yield enabled

New / rename for product clarity:

EndpointPurpose
GET /api/characters/{id}/auto-yieldStatus: enabled, venue, staked, apy, last harvest
PUT /api/characters/{id}/auto-yieldOwner toggle + amount/risk envelope
POST /api/characters/{id}/auto-yield/discoverDefiLlama ∩ allowlist ∩ scores
POST /api/characters/{id}/auto-yield/stakeOwner-initiated stake plan+execute
POST /api/characters/{id}/auto-yield/unstakeOwner-initiated unstake
POST /api/characters/{id}/auto-yield/harvest/planDry-run harvest PTB (keeper/owner)
GET /api/auto-yield/venuesPublic readiness + live APYs

Keep old stablecoin-yield routes as aliases during migration.

ComponentFileRole
Heartbeat runnerruntime/keepers/heartbeat-runner.mjsPermissionless harvest poke
Lifecycleruntime/treasury/lifecycle.mjsstake / claim_yield / sweep plans
Gas sponsorruntime/treasury/gas-sponsor.mjsSponsored PTB bundle
Wallet watcherruntime/treasury/wallet-watcher*.mjsInbound fund recognition

Harvest flow (permissionless):

  1. Keeper loads shared endowment + position refs.
  2. Adapter builds claim_yield PTB command producing netYield coin.
  3. Same PTB calls treasury::heartbeat (fee skim + residual compound + storage path if due).
  4. Bounty paid from policy; no keeper-supplied amounts.

Survival auto-unstake only:

  1. Storage renewal due; liquid < bill.
  2. Unstake minimum micros from venue.
  3. Never for chat/interaction; never below fan-credit liability.
  4. Emit event.

5. Fees (required — already partially onchain)

Section titled “5. Fees (required — already partially onchain)”

Open Yield takes protocol fees. Locked rules:

FeeDefaultWhenNever
Yield skim~5% (protocol_yield_skim_bps = 500)At harvest, on gross yield onlyDeposits, tips, top-ups, withdrawals, principal
Storage routing feesame ~5% familyOn storage renewal when XEL routesSelf-provisioned storage
Interaction feeon post-compute marginAt settlementUnspent fan credit refunds
Harvest keeper bountypolicy heartbeat_bountyPermissionless poke incentiveNot a second % tip on top of skim unless product says so
Bridge / swappass-through venue costMayan/Turbos quotesHidden 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:

  1. Estimates always show gross APY → net APY after fee.
  2. Harvest path always skims fee to protocol treasury (fail if fee sink misconfigured).
  3. UI/API never imply “fee-free yield.”
  4. Fee rate configurable via policy; default ~5% (500 bps); policy-configurable forever (LOCKED-MODEL).
  5. 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 Pay

Every opportunity + character position must expose honest estimates (no invented APY).

EstimateFormula / sourceSurface
gross_apy_bpsadapter read_apy (preferred) or DefiLlama for watch_onlyopportunities, venues
net_apy_bpsgross * (10000 - skim_bps) / 10000always
est_daily_yield_microsstaked_micros * net_apy_bps / 10000 / 365funding snapshot
est_monthly_yield_microsdaily * 30funding / Auto Pay
runway_daysexisting total-bill / net yield coverage mathcharacter health
bridge_cost_estimateMayan quotebefore inbound sweep
swap_cost_estimateTurbos (or route) quotein-chain USDC↔SUI gas path
gas_sponsor_budgetgas-sponsor sizingstake/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”
DirectionProviderStatusOpen Yield use
Base / Solana USDC → SuiMayan (mayan-executor.mjs, CCTP/Wormhole rails)Built, execution gatedFund endowment before stake
Sui → Base/SolNot MVPNo principal rebalance off-home in MVP
Arbitrary rebalance to foreign yieldPhase 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.

SwapVenueFileUse
USDC → SUI (gas)Turbos (default)turbos-route.mjs + gas-sponsor.mjsSponsored ops without holding SUI float
Other stables / LSTlaterOnly allowlisted pools

Swaps are policy-capped (max slippage, allowlisted venues). Lifecycle already has swap action class.


Characters often hold USDC only. Open Yield ops (stake, harvest, bridge repay, unstake) need native gas.

PieceFileBehavior
Sui gas sponsorruntime/treasury/gas-sponsor.mjsCharacter sender, XEL gasOwner, one USDC→SUI swap, repay sponsor in SUI, overage stays with character
Base/Sol gas frontwallet-watcher + Mayan pathFront 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).


CapabilityMVPLater
Bridge in (Base/Sol → Sui)Yes (Mayan)
In-chain swap for gasYes (Turbos + sponsor)more venues
Fees on yieldYes (~5% skim)rate governance
Yield estimatesYes (gross/net/runway)multi-venue portfolio
Bridge out / multi-home yieldNoPhase B+
Auto rebalance across chainsNoPhase B+

SurfaceChange
Public skill / docsDocument Auto Yield capability in skill.md / treasury docs
Character funding UIShow liquid / staked / net APY / last harvest; toggle OFF default
Settings”Auto Yield” card: off by default; owner sets amount
Funding snapshot APIInclude auto_yield block (no invented APY if missing)
Provider registryOptional treasury.yield.v1 capability entry
Persona pathUnchanged — GEN still optional persona only
x402Pay for inference/services; not primary yield data bus

  1. No broadcast without dual flags + signer + allowlist.
  2. Secrets never in API responses (existing redaction).
  3. Fan credit ring-fence before any stake math.
  4. Conditioned tranches: stakeable-during-hold respected.
  5. Mainnet disabled until XEL-70 NO-GO seams closed.
  6. DefiLlama outage → discovery empty; existing positions still harvest via adapters.

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”
  1. Discovery module + scorer + API.
  2. Owner Auto Yield toggle (OFF default).
  3. Owner stake/unstake via Suilend (NAVI fallback).
  4. Permissionless harvest via heartbeat (already mostly built).
  5. Survival auto-unstake path.
  6. Auto Pay v0: after harvest/survival, residual may fund storage renewal + inference within spend policy (x402 / provider claims already partial).
  7. Honest UI (real APY only).
  8. Close XEL-70 enablement seams for opt-in stake (not default auto).
  • Scallop adapter
  • Auto Pay multi-agent splits (pay other agents from yield)
  • Base Morpho/Aave adapters (standalone Open Yield expansion)
  • Optional Base YieldRouter.sol only if product expands beyond character endowments
  • x402 yield-data provider (optional alternative to DefiLlama)
  • Multi-venue rebalance proposals (advisor only until owner signs)
  • 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

Decision making, scoring, goal interpretation = OFFCHAIN open agents.
Does not need to run fully on Sui / Base / Solana.

LayerWhereWhy
Score / discover / choose venueOffchain orchestratorGameable if “onchain oracle of best APY”
Goal parse (“stable runway”)OffchainNatural language + policy
deposit / withdraw / claim / harvest PTBOnchain + adaptersMoney trust
Heartbeat pokeOffchain keepers, permissionlessAnyone can fire; amounts still onchain
Auto Pay routing planOffchain within spend policyExecution 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)”
GoalChoice for this product
Fastest path that ships on XEL charactersSui first (execution already ~70% built)
Standalone multi-chain Open Yield laterChain-agnostic interface; add Base/Sol after Sui Auto Yield works
OrchestrationAlways offchain (all chains)
Cross-chain for XEL MVPInbound 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.

ComponentBaseSolanaSui (MVP home)
Execution contractsSolidity YieldRouter later (Phase B+)Anchor vault later (Phase C)Now: endowment + treasury + venue allowlist
Yield adaptersMorpho / Aave laterKamino / etc laterNow: Suilend + NAVI (Scallop next)
x402Mature (payments)SupportedNow: payments via existing XEL x402 runtime
Agent walletsAgentKit later if EVM operatorOptionalNow: hosted Sui + AgentCap policies
Bridge roleSource of USDC into XELSource of USDC into XELDestination endowment (home)
OrchestratorSame offchain agentSameSame
Auto HarvestLater per-chain routerLaterNow: heartbeat + claim PTB
Auto PayLaterLaterNow: 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
PhaseChains live for executionOrchestrator
A (MVP)Sui onlyOffchain; DefiLlama multi-chain discovery OK for ranking but execute only Sui allowlist
BSui + optional Base adapterSame agent; can deposit on Base only if character policy allows non-home (default: no)
C+ SolanaSame

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 nameXEL already / build as
YieldRouter (Move)treasury + shared Endowment (not new vault package first)
FeeModuleprotocol_yield_skim_bps + waterfall in treasury
AdapterRegistryset_yield_venue_allowed + backend allowlist env
Protocol adapters firstSuilend + NAVI (done) — Scallop/Haedal/Kai = next, not first
harvest()treasury::heartbeat + adapter claim_yield PTB
Auto Payspend policy + x402 + AY-11
Orchestratoroffchain keepers + new discovery/scorer (AY-01/02)
x402 yield dataNo — DefiLlama discovery; x402 = Auto Pay payments
Turnkey walletsoptional 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
  • 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
[ 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.
QuestionAnswer
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

LaneTicketRepo areaDepends on
AAY-01 Discoveryruntime/treasury/yield-discovery.mjsnone
BAY-02 Scorerruntime/treasury/auto-yield-scorer.mjsAY-01 schema only
CAY-03 API surfacebackend/main.pyAY-01/02
DAY-04 Owner toggle + policybackend + schemanone
EAY-05 Stake/unstake executoradapters + backendAY-04
FAY-06 Harvest PTB integrationkeepers + lifecycleAY-05 optional
GAY-07 Survival auto-unstakekeepers + contractsAY-05
HAY-08 FE Auto Yield settingsfrontend/AY-03
IAY-09 Enablement / prod gatesops + runbookA–G
JAY-10 Docs + skill re-integrationdocs/, site/parallel
KAY-12 Opportunity agent (TVL≥$50M)discovery + cronAY-01
LAY-13 Yield estimatesscorer + APIAY-02
MAY-14 Bridge + in-chain swapmayan + turbosparallel
NAY-15 Gas sponsorship on plansgas-sponsor.mjsAY-05/06
OAY-16 Protocol fee take E2Etreasury skim + UIAY-06
PAY-11 Auto Payspend policy + x402AY-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.


  • No invented money numbers in UI/API when data missing (null / omit / $0 only 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>/ from main.
  • Do not flip prod broadcast flags without explicit mav go.

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):

#CaseInputExpected
T1TVL sort desc3 pools TVL 5M, 1M, 10Morder 10M, 5M, 1M
T2Min TVL floorpool TVL 999_999excluded
T3Non-stablecoin excludedstablecoin: falseexcluded
T4Chain filterchain aptosexcluded when only sui/base/eth/sol allowed
T5Empty upstream{data:[]}{pools:[], blockers:["empty_upstream"]}
T6Fetch failurefetch throwsfail-closed status blocked, no throw uncaught
T7Schema versionany successschemaVersion === "auto-yield-discovery.v1"
T8Idempotent normalizesame fixture twicedeep-equal outputs

Done when: npm run test:yield-discovery (or suite include) green.


Title: Auto Yield: scoring engine (net APY after fee, allowlist, adapter readiness)

Scope:

  • runtime/treasury/auto-yield-scorer.mjs
  • net_apy_bps = gross * (10000 - skim) / 10000
  • Intersect discovery + allowlist + readiness
  • Produce auto-yield-plan.v1

Deterministic tests (tests/auto-yield-scorer.test.mjs):

#CaseInputExpected
T1Net APY mathgross 500 bps, skim 500net 950 bps
T2Skim 0gross 500, skim 0net 500
T3Reject not allowlistedvenue morphoblockers includes venue_not_allowed
T4Reject adapter not readyready=falsenot selected
T5Prefer higher net APYtwo ready venueshigher net wins
T6Zero stakeablestakeable_micros=0plan mode no_op, selected null
T7Cap amountstakeable 10e6, max 3e6amountMicros 3e6
T8Fan credit excludedbalance 10, liability 4stakeable ≤ 6 (same units)
T9Tie-breakequal scorestable sort by venue id asc
T10Missing gross APYno adapter apycannot be executable selected

Done when: scorer pure functions covered; no network I/O in unit tests.


Title: Auto Yield: character + public API surface (discover/status/plan)

Scope:

  • GET /api/characters/{id}/auto-yield
  • POST /api/characters/{id}/auto-yield/discover
  • GET /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):

#CaseExpected
T1Status when never enabledenabled=false, staked=0, apy null/omit
T2Discover with fixture monkeypatchreturns scored pools, no secrets
T3Non-owner discover401/403
T4Venues readiness shapeeach venue has ready, gross_apy_bps nullable
T5Missing character404
T6DefiLlama down200 with empty pools + blocker, not 500
T7No invented APYwhen adapter fails, apy fields null not “5%“
T8Response redactionno 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:

#CaseExpected
T1Fresh characterenabled false
T2Owner enablesenabled true, audit row
T3Owner disablesenabled false; does not force unstake in this ticket (document follow-up)
T4Non-owner PUT403
T5Invalid max_stake negative400
T6Enable without venue when only one allowlisteddefaults to suilend or explicit required field
T7Policy reloadGET matches last PUT

Done when: DB + API round-trip tests pass.


Title: Auto Yield: owner-initiated stake/unstake via Suilend/NAVI adapters

Scope:

  • POST .../auto-yield/stake and .../unstake
  • Reuse stablecoin-yield plan/execute
  • Require auto_yield_enabled OR 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):

#CaseExpected
T1Stake while disabled403/blocked auto_yield_disabled
T2Stake while enabled, mock adapteraudit supply, digest mock, no secret leak
T3Amount above stakeablecapped or 400 per contract (pick one; document)
T4Venue not allowlistedblocked
T5Unstake partialwithdraw amount respected
T6Unstake more than positionfail-closed
T7Broadcast flags offstatus prepared/blocked not submitted
T8NAVI fallback when Suilend not readyselects navi if policy allows
T9Idempotency key replayno double supply
T10Conditioned non-stakeable trancheexcluded 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_yield into 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:

#CaseExpected
T1Plan without positionno-op harvest, heartbeat still valid if policy allows zero yield
T2Mock claim produces coinheartbeat consumes same PTB result ref
T3Fee skim 5%net to principal 90% of gross fixture
T4Fee never on principal top-upseparate top_up test unchanged amount
T5Missing endowment objectfail-closed skip
T6Shared object model requiredowned-only state blocked for external keeper
T7Bounty ≤ yield ruleif bounty > yield, contract/sim error
T8Multi-command plan orderclaim before heartbeat

Done when: unit/integration with mocked PTB builder green.


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:

#CaseExpected
T1Storage due, liquid enoughno unstake
T2Storage due, liquid short 2, staked 10unstake 2
T3Shortfall 10, staked 3unstake 3
T4Interaction shortfall onlyno unstake
T5Fan credit would be breachedunstake reduced/blocked per rule
T6No stake positionno-op
T7Broadcast offplan only
T8Event payloadincludes reason survival_storage

Done when: pure policy tests + mocked executor tests pass.


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):

#CaseExpected
T1enabled false default copyshows Off / enable CTA
T2apy nullshows N/A now 5% product default
T3staked + liquid rendercorrect split from fixture
T4toggle calls PUTmock fetch once
T5error stateno 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_disabled for approved owner stake only
  • Keeper roster with shared endowment ids
  • Pre-broadcast prepare smoke

Deterministic / checklist tests:

#CaseExpected
T1readiness endpointsuilend ready true only if SDK root resolves
T2flags defaultexecution false, broadcast false in staging template
T3prepare smoke fixturestatus prepared without submit
T4runbook updatedno “character NEVER auto-stakes” contradiction with opt-in cron
T5kill switchsingle env disables all broadcast

Done when: mav-approved staging prepare smoke; prod flags still false until explicit go.


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.md Phase 4 language
  • Update site/skill.md treasury capability blurb
  • Link locked model rules
  • Deprecate “GEN Yield Router” external naming in XEL docs

Deterministic checks:

#CaseExpected
T1Doc existsdocs/23-auto-yield.md present
T2Locked rules citedstake off default, fee on yield only
T3No OpenYield product namegrep clean in new docs
T4API routes listed match AY-03path parity

Done when: docs merged; validate script if any link check exists.


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:

Terminal window
cd /root/projects/XEL
git fetch origin
git worktree add .claude/worktrees/xel-ay-01 -b feat/xel-ay-01-discovery origin/main

Prompt 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.


  1. Owner can enable Auto Yield and stake USDC on Suilend (testnet/staging).
  2. Permissionless harvest compounds residual and skims protocol fee on yield only.
  3. Survival auto-unstake works when storage due.
  4. UI shows real APY/staked or honest empty states.
  5. Discovery ranks venues by TVL/score but execution uses adapter truth.
  6. Default new characters: Auto Yield OFF, liquid endowment only.

  • 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).