# Auto Yield Source: https://docs.openyield.pro/ # Auto Yield **Auto Yield** is the owner-facing product for opt-in stake, harvest, and compound. **Open Yield** is the underlying protocol (adapters, fee math, agent API). **Public site:** [openyield.pro](https://openyield.pro) · **These docs:** [docs.openyield.pro](https://docs.openyield.pro) ## What you get - Stake **OFF by default** — owner enables Auto Yield - Fee only on **harvested yield** (**500 bps / 5%**), never on deposits or withdrawals - Phase 1 execution homes: **Sui** (Suilend / NAVI) and **Solana** (Kamino / marginfi) - Permissionless harvest — keepers cannot invent amounts - No team custody of principal; owner can always withdraw ## Start here | Doc | Why | |---|---| | [Product Constitution](/constitution/) | Non-negotiable rules | | [Fee Model](/fee-model/) | Why 5% on yield only | | [Auto Yield](/auto-yield/) | Owner product surface | | [Phase 1: Sui + Solana](/phase-1/) | Launch homes | | [Agent API](/agent-api/) | Integrate as an agent | ## Protocol vs product | Name | Role | |---|---| | Open Yield | Protocol / capability (this docs set + open-yield repo) | | Auto Yield | Owner opt-in stake + harvest + compound | | Auto Pay | Post-fee residual → storage / inference / services | | XEL | Living Characters that can fund via Open Yield endowments | ## Status Backend and onchain Phase 1 work lives in the Open Yield repo. Public launch domain: **openyield.pro**. --- # Product Constitution Source: https://docs.openyield.pro/constitution/ **Version**: 1.0 **Date**: 2026-07-11 **Status**: Non-negotiable **Authority**: This file + XEL `LOCKED-MODEL` (where character endowments apply) **Plane**: XEL-152 Any design, PR, deploy, or ticket that violates these principles is **out of scope** until rewritten. --- ## One-liner **Open Yield provides intelligence and automation. It never takes custody. Contracts hold capital; agents instruct within owner policy; owners can always exit.** --- ## Core principles (cannot violate) | Rank | Principle | One-sentence rule | Severity if broken | |---|---|---|---| | **1** | **No custody of user funds** | Team/protocol/server never hold or control principal. Users and agents interact with **smart contracts** (and venue protocols under policy). | **Critical — product-killing** | | **2** | **Users can always withdraw** | Owner can withdraw principal anytime without team approval (subject only to onchain rules: fan-credit liability, conditioned tranches). | **Critical** | | **3** | **No hidden admin powers** | No secret keys that freeze, drain, or rewrite user positions. Upgrades only via explicit, transparent, owner-consent paths. | **Critical** | | **4** | **Transparent onchain fees** | Performance/routing fees are **calculable onchain**, disclosed in API/UI (gross vs net). Never offchain skim of principal. | **High** | | **5** | **Fee only when users earn** | Protocol earns primarily as **% of harvested yield** (~5% default skim), not on deposits, tips, top-ups, or withdrawals. | **High** | | **6** | **Permissionless harvest** | Anyone may trigger harvest/heartbeat (with bounty rules). Positions must not depend on the team to “maintain” yield. | **High** | | **7** | **Agent-native, owner-sovereign** | Optimized for autonomous agents/characters, but **owner root always overrides**. Agents cannot withdraw principal or widen their own envelope. | **High** | | **8** | **Open execution layer** | Core contracts, adapters interfaces, and orchestrator logic are open/verifiable. “Open Yield” is not a closed black-box optimizer. | **High** | | **9** | **Discovery ≠ execution truth** | DefiLlama (and similar) may rank opportunities; **venue adapter reads** decide executable APY and amounts. | **High** | | **10** | **Honest numbers only** | No invented APY, runway, or fees. Missing data → null / N/A / hide — never demo constants as live. | **High** | ### XEL character overlay (when Open Yield powers a Living Character) These **add** to the above; they do not replace them: | Rule | Detail | |---|---| | Stake **OFF by default** | Owner opt-in Auto Yield; no auto-stake on mint | | One automatic unstake only | Survival/storage shortfall — minimum amount only | | Auto Pay | Post-fee, post-survival **residual yield** only — not liquid principal by default | | Home chains Phase 1 | **Sui + Solana** execution homes; Base/Ethereum = bridge-in / discovery only | --- ## Decision guide | Decision area | Follow | Pass test | |---|---|---| | Smart contracts | No custody + always withdraw + no hidden admin | Owner `withdraw` works without team; no admin drain | | Fee model | Transparent + earn-when-user-earns | Fee only inside harvest/waterfall; shown as `feeBps` + net APY | | Agent permissions | Agent-native + owner-sovereign | Agent harvests/plans; cannot withdraw principal or change fee/venues | | Cross-chain | No custody | Bridge via protocol rails (e.g. Mayan); credit only after delivery; no team intermediate wallet as owner of funds | | Discovery / AI routing | Discovery ≠ truth + honest numbers | Propose/rank offchain; execute only allowlisted adapters with live `read_apy` | | Upgrades | No hidden admin | Immutable core where possible; otherwise timelock + owner consent / package migration | | Product copy | Honest numbers | Never “guaranteed APY” or fee-free yield if skim applies | --- ## What we learn from peers (and how we differ) | Protocol | Lesson we take | How Open Yield differs | |---|---|---| | **Superform** | Immutable core, modular routing, non-custodial cross-chain mental model | Agent-native + **Auto Pay**; Phase 1 homes **Sui + Solana** | | **Yearn** | Permissionless harvest; performance fees accepted | Simpler agent one-call surface; fee ~**5% of yield** (not 2%+20% stack by default) | | **Beefy** | Multi-chain auto-compound UX | Intelligent routing + character/agent policy, not vault farm list only | | **Giza / closed agent optimizers** | Agentic yield is real demand | Stay **open** — orchestrator anyone can run; no closed custody platform | | **OpenRouter (analogy)** | One high-level call that routes | Same UX metaphor for **capital**, not LLMs | **We do not copy:** custodial “send us USDC and we stake,” closed agent backends that hold keys to principal, or fee-on-deposit models. --- ## Fee constitution (detail) **Full fee model:** `docs/09-fee-model.md` ``` gross_yield → protocol_skim (~5% default, onchain bps) → survival bills (storage, etc.) → residual → compound and/or Auto Pay ``` | Allowed | Forbidden | |---|---| | % of harvested yield (~5% MVP default) | % of deposit / tip / top-up | | Optional **x402** pay-per-call on data/API only | OpenRouter-style % tax on “credits” as core yield model | | Disclosed routing fee on routed storage renewal | Hidden bridge markup as “fee” without disclosure | | Keeper bounty from policy | Second Auto Pay % that double-charges residual | | Pass-through venue/bridge gas costs | **Withdrawal fee** on principal (Beefy-style) | | | Management fee on idle principal as primary revenue | --- ## Anti-patterns (instant reject) 1. User sends funds to a team/ops wallet “to be staked.” 2. Admin key that can seize, freeze, or redirect principal. 3. Agent can `withdraw` principal or transfer aNFT. 4. Auto-stake every character on create. 5. Fee charged at deposit or withdraw of principal. 6. Harvest only callable by team. 7. UI shows demo APY as live. 8. DefiLlama APY used as sole input to size a live supply tx. 9. Bridge credit before destination delivery confirmed. 10. “Upgrade” that changes fee or withdraw rules without transparent process. --- ## Governance posture (MVP) - Prefer **immutable** fee + withdraw + harvest invariants on the execution path used by characters. - Venue allowlists and adapter sets may evolve under **owner policy** and transparent protocol config — not silent admin. - Token/governance for fee share is **not required** for MVP; do not block shipping on a token. --- ## How to use this document 1. Every Open Yield PR description must name which principles it upholds. 2. Code review: fail any PR that weakens rank 1–3. 3. Plane tickets XEL-153…168 must not contradict this constitution; Codex/review comments that cite custody/fee/harvest take priority over convenience. 4. Marketing: never claim non-custodial if any path holds user keys to principal without export/exit. --- ## Related | Doc | Role | |---|---| | `docs/01-implementation.md` | How we build | | `docs/02-agent-api.md` | API surface | | `docs/03-sui-move.md` | Move / XEL treasury map | | `docs/04-xel-integration.md` | Plug into Living Characters | | `docs/LOCKED-MODEL-XEL.md` | Character economy authority | --- **Constitution end.** If a principle must change, it requires an explicit product decision recorded here with date and owner — not a silent code change. --- # Fee Model Source: https://docs.openyield.pro/fee-model/ **Date**: 2026-07-11 **Authority**: `docs/00-product-constitution.md` + XEL `LOCKED-MODEL` **Plane**: XEL-152 / XEL-168 **Product decision 2026-07-11 (mav): performance skim = 5% (500 bps).** This freezes how Open Yield charges vs Superform / Yearn / Beefy / Giza / OpenRouter. --- ## 1. Market scan (how peers charge) | Protocol | What they charge | When | Notes | |---|---|---|---| | **Superform** | Performance + some management / vault skims | Realized yield / vault events | Fees often shared with strategists; modest protocol-kept $ | | **Yearn** | Classic ~2% mgmt + ~20% performance (vault-dependent) | On yield | Established, heavy performance cut | | **Beefy** | Performance + **small withdrawal fee** | Harvest / exit | Withdrawal fee is common but **we reject it** | | **Giza / agent optimizers** | Performance on yield | On yield | Agent-focused, often less open | | **OpenRouter** | **~5.5% on credit top-ups** | When buying credits | **Not** a yield model — flat tax on spend | **Lesson:** DeFi yield protocols earn mainly when users earn. Credit-platform % markups (OpenRouter) are a different business. --- ## 2. Open Yield fee model (MVP — locked) ### Two rails only | Rail | Rate | When charged | Purpose | Onchain? | |---|---|---|---|---| | **A. Yield performance skim** | **5% of gross harvested yield** (`protocol_yield_skim_bps = 500`) | Every harvest / waterfall | Main protocol revenue | **Yes** | | **B. Data / API (optional)** | **x402 pay-per-call** (market rate, e.g. ~$0.001–0.002 class if we host discovery) | When agent hits paid discovery/data endpoints | Cover data + spam | Via **x402**, not a hidden yield tax | ### Explicitly **not** in MVP | Rejected fee | Why | |---|---| | Fee on **deposits / tips / top-ups** | Constitution + XEL locked model | | Fee on **principal withdrawal** | “Always withdraw”; no Beefy-style exit tax | | **Management fee on idle AUM** as primary revenue | Prefer earn-when-user-earns | | **5.5% on credit purchases** as core model | OpenRouter-style; not yield-aligned | | **Second Auto Pay %** on residual (0.5–1%) | Double-charges after yield skim; Auto Pay is a **feature of residual**, not a new fee stack | | Ad-hoc **5–8%** marketing rate | MVP locked at **500 bps (5%)**; change only by explicit product decision | ### Waterfall (canonical) ``` gross_yield (venue claim) → protocol_skim (~5%) // Open Yield revenue — ONCHAIN → survival bills (storage, etc.) → residual → compound and/or Auto Pay (no extra protocol % by default) ``` Gas, bridge venue costs, and card processor fees are **pass-through costs**, not protocol performance fees. Disclose them in plans. --- ## 3. Why not the “hybrid $0.001 + 5–8%” as written | Proposal piece | Verdict | |---|---| | $0.001 per API call | **OK as x402 data pricing** if we host paid discovery — not a substitute for yield skim | | 5–8% band | MVP locked at **5% (500 bps)**; raise only with product decision unless policy overrides `protocol_yield_skim_bps` | | Hybrid as main story | Fine **if** framed as: (1) yield skim onchain + (2) optional x402 for data — not two competing “performance” fees | **Open Yield product default is 500 bps (5%).** Align XEL `DEFAULT_PROTOCOL_YIELD_SKIM_BPS` / policy config to 500 for Open Yield characters. --- ## 4. Comparison table (decision) | Aspect | Superform / Yearn / Beefy | Open Yield MVP | |---|---|---| | Main revenue | Performance on yield | **~5% harvest skim** | | Flat / usage | Rare (or withdraw fee) | **Optional x402** on data APIs only | | Withdraw principal | Sometimes small fee (Beefy) | **$0 protocol fee** | | Deposit | Usually full value | **Full value always** | | Agent cost to query | Medium | **Very low** if discovery free or $0.001-class x402 | | Token fee-share | Often ($UP, etc.) | **Not required for MVP** | | Transparency | High | **Gross + net APY + feeBps in every position API** | --- ## 5. Agent-friendly economics | Action | Cost to agent/character | |---|---| | Deposit USDC | **0** protocol fee | | previewRoute / public venues | Free or x402 dust | | Stake / unstake | Gas (sponsored path) only | | Harvest | **~5% of that harvest’s yield** (onchain) | | Withdraw principal | **0** protocol fee | | Auto Pay send | **0** extra protocol % (uses post-skim residual) | Agents can poll often; we don’t need OpenRouter-style markup on every thought. --- ## 6. Optional later (not MVP) | Idea | When to consider | |---|---| | Lower skim to 5–8% for growth | Explicit product decision + update constitution | | Fee share to $TOKEN buyback | After product-market fit | | Tiny Auto Pay routing fee | Only if x402 facilitator costs demand it — disclose separately | | Management fee on staked AUM | Generally avoid; conflicts with pure performance alignment | --- ## 7. Implementation checklist (XEL-168) - [ ] Harvest path always applies `protocol_yield_skim_bps` - [ ] top_up / withdraw unit tests: **zero** protocol skim - [ ] API: `feeBps`, `grossApyBps`, `netApyBps` always paired - [ ] UI: never “fee-free yield” if skim > 0 - [ ] x402 discovery (if paid): separate line item, not mixed into yield skim - [ ] No withdrawal fee parameter in product API --- ## 8. One-sentence product fee story **Open Yield is free to fund and free to exit; it takes about 5% of yield when your capital earns, and may charge tiny x402 fees only for optional paid data calls.** --- ## Related - Constitution: `docs/00-product-constitution.md` - Superform revenue: `docs/08-superform-github-sdk-revenue.md` - Steal list: `docs/07-superform-steal-list.md` - Agent API harvest response: `docs/02-agent-api.md` --- # Auto Yield product Source: https://docs.openyield.pro/auto-yield/ **Protocol:** Open Yield **Surfaces:** Auto Yield (opt-in stake/harvest) · Auto Pay (post-fee residual) **Authority:** `docs/00-product-constitution.md` + `LOCKED-MODEL-XEL.md` **Phase 1 homes:** Sui + Solana ## User line *Characters on XEL: persona by GEN, capital by Open Yield.* ## Locked rules 1. Stake **OFF by default** — owner enables Auto Yield 2. Fee on **yield only** — **500 bps** (5%) 3. No invented APY — adapter `read_apy` or N/A 4. Discovery (DefiLlama) ≠ execution truth 5. Survival auto-unstake only for storage shortfall ## API routes (parity with implementation) | Method | Path | |---|---| | GET | `/api/open-yield/readiness` | | GET | `/api/open-yield/enablement` | | GET | `/api/open-yield/venues?chain=sui\|solana` | | GET | `/api/open-yield/solana/apys` | | GET/PUT | `/api/characters/{id}/auto-yield` | | POST | `/api/characters/{id}/auto-yield/discover` | | POST | `/api/characters/{id}/auto-yield/stake` | | POST | `/api/characters/{id}/auto-yield/unstake` | | POST | `/api/characters/{id}/auto-yield/survival` | | GET | `/api/characters/{id}/open-yield/position` | | POST | `/api/characters/{id}/open-yield/harvest` | | POST | `/api/characters/{id}/open-yield/withdraw` | | POST | `/api/characters/{id}/open-yield/auto-pay` | | POST | `/api/open-yield/bridge/plan` | Deprecated product naming: do not brand this capability as an external yield-router product name. --- # Phase 1: Sui + Solana Source: https://docs.openyield.pro/phase-1/ **Date:** 2026-07-11 **Status:** Locked for first public phase **Repo:** open-yield (local) ## Decision Phase 1 launches on **two execution homes**: | Chain | Role | Default venue | Fallback | |---|---|---|---| | **Sui** | Execution home | Suilend (USDC) | NAVI | | **Solana** | Execution home | Kamino (USDC) | marginfi | **Not** Phase 1 execution homes: Base, Ethereum (still OK for DefiLlama map + Mayan bridge-in to a home). ## Product rules (unchanged) - Stake **OFF** by default - Fee **500 bps** on harvested yield only - Discovery ≠ execution truth (`read_apy` per venue/chain) - No team custody of principal - Owner can always withdraw ## Cross-home funding | Path | Phase 1 | |---|---| | Base/Eth → Sui | Mayan plan | | Base/Eth → Solana | Mayan plan | | Solana ↔ Sui | Mayan plan (owner-initiated) | | In-chain Sui swap | Turbos plan | | In-chain Solana swap | Jupiter plan (stub) | ## API surface - `GET /api/open-yield/readiness` → `launchHomes: ["sui","solana"]` - `GET /api/open-yield/venues?chain=sui|solana` - Policy: `targetChain`, `targetVenue`, `allowedChains` - Stake/unstake body may set `chain` + `venue` ## Code map | Module | Path | |---|---| | Chain registry | `runtime/chains/phase1.mjs` | | Adapters | `runtime/adapters/venue-adapters.mjs` | | Bridge | `runtime/bridge/mayan-plan.mjs` | | Policy | `runtime/policy/auto-yield-policy.mjs` | | Scorer | `runtime/scorer/auto-yield-scorer.mjs` | ## XEL integration note XEL character endowments remain Sui-native objects today. Open Yield Phase 1 still serves: 1. **Sui path** — plug into XEL treasury / Suilend (proven live $0.10 smoke). 2. **Solana path** — standalone Open Yield positions + agent API (Kamino/marginfi adapters); bridge residual to Sui endowment when owner wants XEL survival home. ## Live smoke (Sui) See `docs/12-live-smoke-treasury-2026-07-11.md` — mainnet Suilend supply/withdraw via XEL yield signer. --- # Launch: openyield.pro Source: https://docs.openyield.pro/launch/ **Decision (mav, 2026-07-11):** public launch host is **https://openyield.pro**. | Layer | Name / host | |---|---| | Protocol | **Open Yield** (this repo: adapters, fee math, agent API) | | Owner product | **Auto Yield** (opt-in stake / harvest / compound) | | Residual pay | **Auto Pay** | | Public domain | **openyield.pro** | ## Implications 1. Marketing, docs links, and any public API base URL for the product should prefer `openyield.pro` (or `api.openyield.pro` if split later). 2. XEL remains the Living Character host; Open Yield stays the capital rail. Do not couple launch ops to XEL deploys unless explicitly requested. 3. Phase 1 execution homes stay **Sui + Solana**; domain does not change chain scope. 4. Fee model unchanged: **500 bps on yield only**; stake **OFF** by default. ## DNS note (observed 2026-07-11) - Registrar NS: Porkbun - Apex A records present (AWS-looking) - HTTPS not serving a finished app yet (`openyield.pro` not ready for traffic) ## Repo Canonical BE/onchain work: `/root/projects/open-yield` Untracked `site/` scaffold may become the openyield.pro frontend later — only when FE is in scope. --- # Agent API Source: https://docs.openyield.pro/agent-api/ **Version**: 1.0 **Date**: 2026-07-11 **Parent**: `docs/23-auto-yield-implementation.md` **Plane**: XEL-152 epic · overview page **Authority**: `docs/LOCKED-MODEL-2026-07-10.md` High-level **orchestrator / agent skills** over Open Yield. Contracts hold funds; agents only instruct within owner policy. --- ## 0. XEL mapping (read first) | Generic Open Yield name | XEL implementation | |---|---| | `positionId` | `characterId` + shared `endowmentObjectId` (not multi-user vault shares) | | `POST /open-yield/route` | Discover + (if enabled) owner stake via adapters; deposit = `top_up_endowment` | | `POST /open-yield/withdraw` | `withdraw_endowment_principal` — **owner only**, never agent for full principal | | `POST /open-yield/harvest` | Permissionless: adapter `claim_yield` + `treasury::heartbeat` | | Fee 5% in sketches | **~5%** `protocol_yield_skim_bps = 500` (configurable) | | Base Solidity first | **Sui first** | | Auto stake on route | **OFF by default** — route may deposit liquid only; stake needs Auto Yield enable | **Public path prefix (XEL product):** `/api/characters/{character_id}/auto-yield/*` and `/api/open-yield/*` (aliases). **Agent skill names** (orchestrator tools) stay short: `routeYield`, `harvest`, `withdraw`, `getPosition`, `enableAutoPay`, `previewRoute`, `rebalance`. --- ## 1. Core high-level API calls | # | Skill | HTTP | Purpose | MVP | |---|---|---|---|---| | 1 | `routeYield` | `POST .../route` | Deposit + optional intelligent stake plan | Yes | | 2 | `harvest` | `POST .../harvest` | Claim yield, fee onchain, compound residual | Yes | | 3 | `withdraw` | `POST .../withdraw` | Owner withdraw principal | Yes | | 4 | `getPosition` | `GET .../position` | Liquid / staked / estimates / last harvest | Yes | | 5 | `enableAutoPay` | `POST .../auto-pay` | Configure pay from post-fee residual | Yes | | 6 | `previewRoute` | `POST .../preview` | Simulate route without broadcast | Yes | | 7 | `rebalance` | `POST .../rebalance` | Move venue within allowlist | Phase 2 | | 8 | `listOpportunities` | `GET .../opportunities` | Reputable pools (TVL ≥ $50M) | Yes (AY-12) | --- ## 2. Schemas ### 2.1 `routeYield` — `POST /api/characters/{character_id}/open-yield/route` **Auth:** owner or scoped agent (cannot withdraw principal). ```json { "amount": 10000, "token": "USDC", "goal": "stable_agent_budget", "riskTolerance": "low", "preferredChains": ["sui"], "autoBridge": false, "autoCompound": true, "stake": false, "autoPayConfig": { "enabled": false, "percentage": 30, "targets": ["storage", "inference"] } } ``` | Field | Required | Notes | |---|---|---| | `amount` | yes | Human USDC units or micros — pick one contract; MVP = micros integer preferred | | `token` | yes | USDC only MVP | | `goal` | yes | Scoring hint only | | `riskTolerance` | no | low/medium/high — affects APY sanity / venue filter | | `preferredChains` | no | Discovery only; **execute Sui** unless policy expands | | `autoBridge` | no | If true and funds on Base/Sol, Mayan plan first | | `autoCompound` | no | Residual after harvest compounds (default true) | | `stake` | no | If true, requires Auto Yield **enabled**; else liquid deposit only | | `autoPayConfig` | no | Same as enableAutoPay | **Success:** ```json { "success": true, "characterId": "char_...", "endowmentObjectId": "0x...", "chain": "sui", "protocol": "suilend", "depositedAmountMicros": "10000000000", "staked": false, "estimates": { "grossApyBps": 381, "netApyBps": 343, "feeBps": 1000, "label": "estimate" }, "autoPayEnabled": false, "txDigest": null, "planStatus": "prepared" } ``` --- ### 2.2 `harvest` — `POST /api/characters/{character_id}/open-yield/harvest` **Auth:** anyone (permissionless) for poke; gas/sponsor may apply. ```json { "compound": true, "payPercentage": 0, "execute": false } ``` **Success:** ```json { "success": true, "grossYieldMicros": "245000000", "protocolFeeMicros": "24500000", "netYieldMicros": "220500000", "paidToAutoPayMicros": "0", "compoundedMicros": "220500000", "feeBps": 1000, "txDigest": null, "planStatus": "prepared" } ``` Fee = onchain skim (~5%), **not** a second offchain fee. --- ### 2.3 `withdraw` — `POST /api/characters/{character_id}/open-yield/withdraw` **Auth:** **owner only** (root). Agent returns `UNAUTHORIZED`. ```json { "amountMicros": "5000000000", "token": "USDC", "unstakeFirst": true } ``` **Success:** ```json { "success": true, "withdrawnMicros": "5000000000", "liquidMicros": "...", "stakedMicros": "...", "txDigest": "..." } ``` No fee on withdraw. --- ### 2.4 `getPosition` — `GET /api/characters/{character_id}/open-yield/position` ```json { "characterId": "char_...", "owner": "0x...", "token": "USDC", "chain": "sui", "protocol": "suilend", "liquidMicros": "1000000000", "stakedMicros": "9000000000", "currentValueMicros": "10000000000", "pendingYieldMicros": "187000000", "autoYieldEnabled": false, "autoPayEnabled": false, "autoPayPercentage": 0, "estimates": { "grossApyBps": 381, "netApyBps": 343, "estDailyYieldMicros": "...", "estMonthlyYieldMicros": "...", "runwayDays": null, "label": "estimate" }, "feeBps": 1000, "lastHarvestedAt": null, "createdAt": "..." } ``` Missing APY → `null`, never demo 5%. --- ### 2.5 `enableAutoPay` — `POST /api/characters/{character_id}/open-yield/auto-pay` **Auth:** owner (or agent with spend-policy scope). ```json { "enabled": true, "percentage": 30, "targets": ["storage", "inference"], "frequency": "after_harvest" } ``` Pays only from **post-fee, post-survival residual**. Never fan-credit liability. --- ### 2.6 `previewRoute` — `POST /api/characters/{character_id}/open-yield/preview` Same body as `routeYield`; **never broadcasts**. Returns scored venues, fees, bridge/gas estimates. --- ### 2.7 `rebalance` — Phase 2 Owner/agent within allowlist only; not free-form strategy bytes. --- ## 3. Error codes | Code | HTTP | When | |---|---|---| | `INVALID_AMOUNT` | 400 | amount ≤ 0 | | `INSUFFICIENT_BALANCE` | 400 | not enough liquid/stakeable | | `POSITION_NOT_FOUND` / `CHARACTER_NOT_FOUND` | 404 | unknown character | | `UNAUTHORIZED` | 403 | not owner/agent; agent trying principal withdraw | | `AUTO_YIELD_DISABLED` | 403 | stake requested but toggle off | | `VENUE_NOT_ALLOWED` | 400 | not in allowlist | | `ADAPTER_NOT_READY` | 503 | SDK/readiness fail | | `HARVEST_FAILED` | 500 | claim/heartbeat failed | | `FEE_SINK_MISSING` | 503 | cannot take protocol skim | | `GAS_SPONSOR_MISSING` | 503 | execute plan needs sponsor | | `BRIDGE_NOT_ALLOWED` | 400 | autoBridge true but policy forbids | | `OPPORTUNITY_BELOW_TVL` | 400 | below $50M reputation floor | | `RATE_LIMIT` | 429 | abuse | --- ## 4. Agent prompt templates ### 4.1 Route (liquid first) ``` You are the Open Yield orchestrator for an XEL character. Goal: fund stable runway. Do NOT stake unless autoYieldEnabled is true. 1. getPosition(characterId) 2. previewRoute({ amount, token: USDC, goal: stable_agent_budget, riskTolerance: low, stake: false }) 3. If owner confirmed, routeYield with stake:false unless policy says stake:true 4. Report estimates.netApyBps and feeBps. Never invent APY. ``` ### 4.2 Harvest + optional Auto Pay ``` Harvest character {characterId}. 1. harvest({ compound: true, payPercentage: 0, execute: false }) 2. Show grossYield, protocolFee (~5%), netYield 3. Only execute=true if gas sponsor ready and policy allows ``` ### 4.3 Opportunity scan ``` List Open Yield opportunities with TVL >= 50_000_000 USD, stablecoin, chains sui|base|ethereum|solana. Mark executable only if Sui adapter ready + allowlisted. Propose only — never stake. ``` --- ## 5. Deterministic tests (CI) Use fixed fixtures; no live mainnet required for unit tests. Fee default **500 bps (5%)** in XEL fixtures (Open Yield product default). ### 5.1 Deposit / route | ID | Case | Expected | |---|---|---| | DEP-001 | Valid deposit amount | top_up recorded, no fee | | DEP-002 | amount 0 | `INVALID_AMOUNT` | | DEP-003 | insufficient balance | `INSUFFICIENT_BALANCE` | | DEP-004 | multiple top_ups | principal accumulates | | ROUTE-001 | stake:true while Auto Yield off | `AUTO_YIELD_DISABLED` | | ROUTE-002 | preview never broadcasts | no txDigest submitted | | ROUTE-003 | autoBridge with Base USDC mock | Mayan plan in response | ### 5.2 Withdraw | ID | Case | Expected | |---|---|---| | WIT-001 | owner partial withdraw | amount out, no fee | | WIT-002 | over liquid without unstake | fail or unstakeFirst path | | WIT-003 | amount 0 | `INVALID_AMOUNT` | | WIT-004 | agent withdraw principal | `UNAUTHORIZED` | | WIT-005 | after harvest | value reflects residual compound | ### 5.3 Harvest + fee | ID | Case | Expected | |---|---|---| | HAR-001 | pending yield fixture 10000 | gross 10000 | | HAR-002 | skim 5% | fee 500, net 9500 | | HAR-003 | compound true | residual to principal | | HAR-004 | non-owner harvest poke | allowed (permissionless) | | HAR-005 | zero pending | no fee, no state change | | FEE-001 | top_up 10000 | no skim | | FEE-002 | withdraw | no fee | | FEE-003 | fee sink missing | `FEE_SINK_MISSING` | | FEE-004 | estimate surface | grossApy + netApy both present | ### 5.4 Auto Pay | ID | Case | Expected | |---|---|---| | PAY-001 | enable valid targets | saved | | PAY-002 | harvest payPercentage 30 | ≤ 30% of **post-fee** residual | | PAY-003 | percentage 0 | no pay | | PAY-004 | invalid target | error | | PAY-005 | fan credit liability | never paid from | ### 5.5 Opportunities / reputation | ID | Case | Expected | |---|---|---| | OPP-001 | TVL 49.9M | excluded | | OPP-002 | TVL 50M | included | | OPP-003 | non-stablecoin | excluded MVP | | OPP-004 | new vs snapshot | one new event | | OPP-005 | no adapter | watch_only | | OPP-006 | agent never supply without enable | assert | ### 5.6 Bridge / swap / gas | ID | Case | Expected | |---|---|---| | BR-001 | Mayan quote mock | normalized | | BR-002 | dest not Sui | blocked | | BR-003 | execution off | prepared only | | SW-001 | pool not allowlisted | blocked | | GAS-001 | sponsor missing | plan blocked | | GAS-002 | overage stays character | not markup | ### 5.7 Security / access | ID | Case | Expected | |---|---|---| | SEC-001 | agent cannot withdraw principal | `UNAUTHORIZED` | | SEC-002 | agent cannot widen fee bps | blocked | | SEC-003 | rebalance non-allowlisted venue | blocked | | SEC-004 | secrets redacted in responses | no keypair fields | | SEC-005 | idempotent harvest poke | single effect | ### 5.8 E2E (integration, still deterministic with mocks) | ID | Flow | |---|---| | E2E-001 | top_up → enable Auto Yield → stake mock → harvest fee → compound | | E2E-002 | top_up → harvest 0 → withdraw | | E2E-003 | enable Auto Pay → harvest → pay storage mock | | E2E-004 | Base USDC mock → Mayan plan → credit → getPosition | | E2E-005 | opportunity agent propose only (no stake) | --- ## 6. Fixed CI test data | Variable | Value | |---|---| | Deposit | 10_000_000_000 micros (10_000 USDC) | | Fee | 500 bps (5%) | | Auto Pay % | 30 | | Harvest gross fixture | 245_000_000 micros | | Expected fee | 12_250_000 | | Expected net | 232_750_000 | | Min TVL | 50_000_000 USD | | Default venue | suilend | --- ## 7. Suggested test layout (XEL repo) ``` tests/ open-yield-route.test.mjs open-yield-harvest-fee.test.mjs open-yield-withdraw-auth.test.mjs open-yield-auto-pay.test.mjs open-yield-opportunities.test.mjs open-yield-bridge-gas.test.mjs backend_open_yield_api_test.py contracts/v2/tests/ # fee waterfall already partial ``` Map to tickets: AY-01…16 (XEL-153…168) — each ticket already has its T1…Tn; this doc is the **union master list**. --- ## 8. MVP must-have skills only 1. `previewRoute` 2. `routeYield` (deposit ± opt-in stake) 3. `getPosition` 4. `harvest` (fee onchain) 5. `withdraw` (owner) 6. `enableAutoPay` 7. `listOpportunities` `rebalance` = Phase 2. --- # Implementation Source: https://docs.openyield.pro/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) | 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 | 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) 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) 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) | 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) ``` 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 | 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 ### 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):** | 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 | ```js // 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 ### 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 ``` candidate = discovery ∩ policy.allowed_venues ∩ adapter_ready score = 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) **File (new):** `runtime/treasury/auto-yield-scorer.mjs` ### 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 ``` net_apy_bps = gross_apy_bps * (10_000 - protocol_yield_skim_bps) / 10_000 ``` ### 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 ```json { "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) ### 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):** 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. ### 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) Existing under `/api/treasury/sui/stablecoin-yield/*`: - `GET .../readiness` - `POST .../plan` - `POST .../execute` - `POST .../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 | 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):** 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) 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:** 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 ``` --- ## 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 ### 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) | 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) 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) | 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 | 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 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. --- ## 8. MVP scope vs later ### 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). ### 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.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) ### 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) **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) ### 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) | 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 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) #### 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 | - [x] Endowment shared object + top_up / withdraw - [x] Fee on yield skim path - [x] Suilend + NAVI adapters + `read_apy` - [x] Heartbeat keeper skeleton - [x] 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) - [ ] 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 - [ ] 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 ``` [ 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 | 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 | 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) ### Shared acceptance (every ticket) - 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-/` from `main`. - Do not flip prod broadcast flags without explicit mav go. --- ### 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 **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`): | # | 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 **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`): | # | 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 **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 **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): | # | 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 **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:** | # | 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 **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 **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) **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:** | # | 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 **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:** | # | 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 **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) Each agent gets **one ticket**, one nested worktree: ```bash 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. --- ## 13. Definition of done (MVP product) 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. --- ## 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). --- # Sui Move Source: https://docs.openyield.pro/sui-move/ **Version**: 1.0 **Date**: 2026-07-11 **Parent**: `docs/23-auto-yield-implementation.md`, `docs/24-open-yield-api.md` **Authority**: `docs/LOCKED-MODEL-2026-07-10.md` **Plane**: XEL-152 **Do not greenfield a new `YieldRouter` package for MVP.** Open Yield on Sui = **extend / complete** existing XEL Move modules + offchain adapters/agents. --- ## 1. Strategy | Choice | Decision | |---|---| | Home chain | **Sui** | | Language | **Move** (existing packages) | | Fee | **~5%** `protocol_yield_skim_bps = 500` (Open Yield product default) | | Stake default | **OFF** — owner opt-in Auto Yield | | First venues | **Suilend + NAVI** (adapters exist) — Scallop/Haedal next | | Cross-chain | Not in Move core; Mayan offchain + credit endowment | | Orchestration | Offchain agents | --- ## 2. Name map (generic → XEL Move) | Generic Open Yield | XEL Move / runtime | |---|---| | YieldRouter (shared) | `treasury::Endowment` (shared) + `TreasuryPolicy` | | UserPosition (owned) | Position refs offchain + venue AccountCap / obligation objects; character aNFT is **owned** | | FeeModule | `TreasuryPolicy.protocol_yield_skim_bps` + waterfall in `heartbeat` / `record_epoch_waterfall` | | AdapterRegistry | `set_yield_venue_allowed` + backend allowlist | | deposit | `top_up_endowment` — **no fee** | | withdraw | `withdraw_endowment_principal` — owner, **no fee** | | harvest | adapter `claim_yield` PTB + `heartbeat` — **fee here** | | Auto Pay | offchain + payment/spend policy (not new vault module first) | ### Package layout (existing) ``` contracts/v2/ Move.toml # package XelV2 sources/ treasury.move # Endowment, policy, harvest waterfall, fee skim identity.move # aNFT / LivingCharacter authority.move # AgentCap wallets.move payment.move storage_policy.move ... runtime/treasury/ suilend-adapter.mjs navi-adapter.mjs gas-sponsor.mjs mayan-executor.mjs ``` MVP work is mostly **runtime + API + wiring**, plus small Move fixes if single-balance / survival-unstake gaps remain vs locked model. --- ## 3. Object model | Object | Shared / owned | Who mutates | Open Yield role | |---|---|---|---| | `LivingCharacter` (aNFT) | **Owned** | owner transfer | Identity root | | `Endowment` | **Shared** | owner top_up/withdraw; **anyone** heartbeat | Capital vault | | `TreasuryPolicy` | Shared / bound | owner configure | Fee bps, venues, harvest flags | | Venue position (Suilend/NAVI) | Protocol objects | adapter PTB under policy | Yield generation | | `AgentCap` | Owned by operator | owner issue/revoke | Scoped automation | **Why Endowment is shared:** permissionless harvest/keeper without holding the aNFT. --- ## 4. Core entrypoints (already in `treasury.move`) | Function | Caller | Fee? | |---|---|---| | `create_endowment` | owner path | no | | `top_up_endowment` | anyone paying in | **no** | | `withdraw_endowment_principal` | **owner** | **no** | | `heartbeat` | **permissionless** | **yes** (skim on `net_yield` / waterfall) | | `configure_yield_harvest` | policy owner | — | | `set_yield_venue_allowed` | policy owner | — | | `pay_storage_renewal` | survival path | routing fee family | ### Fee at harvest (conceptual) ``` // already: DEFAULT_PROTOCOL_YIELD_SKIM_BPS = 500 // 5% // waterfall: // protocol_skim = mul_bps(gross_yield, protocol_yield_skim_bps) // net = gross - protocol_skim // residual compounds / bills ``` ### Agent authorization - **Not** `msg.sender` free-for-all on withdraw. - Owner = root on aNFT. - Agent = `AgentCap` + spend policy: may trigger stake/harvest/Auto Pay within caps; **cannot** withdraw principal or transfer character. --- ## 5. MVP build order (Sui) — realistic | Step | Work | New Move? | Ticket | |---|---|---|---| | 1 | Confirm package IDs / testnet-mainnet targets | no | ops | | 2 | Opportunity agent + discovery (TVL ≥ $50M) | no | XEL-164 / 153 | | 3 | Scorer + estimates (gross/net after 5% fee) | no | XEL-154 / 165 | | 4 | Owner Auto Yield toggle OFF default | no | XEL-156 | | 5 | Stake/unstake via **Suilend** then NAVI | no (adapters exist) | XEL-157 | | 6 | Harvest PTB: claim → heartbeat fee skim | polish | XEL-158 / 168 | | 7 | Gas sponsor on all plans | no | XEL-167 | | 8 | Bridge in Mayan | no | XEL-166 | | 9 | Survival auto-unstake only | maybe small Move event | XEL-159 | | 10 | Auto Pay post-fee residual | no | XEL-163 | | 11 | Align Endowment to **single balance** if v2 still has allocation buckets | **yes if needed** | onchain gap | | 12 | Scallop adapter (optional 3rd venue) | runtime | later | **Not step 1:** new package `OpenYieldRouter` + Scallop+Haedal from zero. --- ## 6. If you add Move modules later (Phase B) Only if product needs a **standalone multi-user** Open Yield vault separate from character endowments: ``` open_yield/ sources/ router.move # optional shared router position.move # optional owned position receipt fee.move # if split from treasury registry.move # venue registry ``` XEL characters should still prefer **endowment-native** Open Yield. --- ## 7. Move.toml note Existing: ```toml [package] name = "XelV2" edition = "2024.beta" [addresses] xel = "0x0" ``` Open Yield does **not** need a second package for MVP; ship under current XEL package lineage / v3 upgrade path per deploy discipline. --- ## 8. Example: how agent calls map to Move + adapters | Agent skill | Onchain | Offchain | |---|---|---| | `routeYield` deposit | `top_up_endowment` | optional Mayan if autoBridge | | `routeYield` stake | — | Suilend/NAVI `supply` if Auto Yield on | | `harvest` | `heartbeat` (+ claim in same PTB) | keeper / anyone | | `withdraw` | `withdraw_endowment_principal` | unstake first if needed | | `getPosition` | views / indexer | `read_apy`, estimates | | `enableAutoPay` | policy/spend config | x402 / provider pay | --- ## 9. Sui-specific tests (Move + runtime) | ID | Case | Expected | |---|---|---| | MV-001 | top_up 10000 | principal +10000, no skim | | MV-002 | harvest gross 10000 skim 5% | protocol_skim 500 | | MV-003 | withdraw owner | ok, no fee | | MV-004 | withdraw non-owner | abort | | MV-005 | heartbeat permissionless | succeeds with bounty rules | | MV-006 | venue not allowed | stake blocked offchain + policy | | RT-001 | Suilend supply mock | digest, no secrets | | RT-002 | harvest PTB order | claim before heartbeat | --- ## 10. Immediate recommendation 1. **Do not** start a new Sui package from Option A greenfield. 2. **Do** implement agent API + adapters wiring on top of `treasury.move` (docs 23–24). 3. **Do** add Move only for locked-model gaps (single balance, survival unstake event) after inventory. 4. Venues: **Suilend + NAVI** now; Scallop when ready — not Haedal-first. ### Deliverables already available | Doc | Content | |---|---| | `23-auto-yield-implementation.md` | E2E product + tickets | | `24-open-yield-api.md` | Agent API + deterministic tests | | **This file** | Sui Move architecture map | ### Plane - Overview: page `5bfcc7e7-c5d0-49bd-a7fa-6f7b6704f381` - API: page `9971f85c-cdec-41d1-be31-35d3f108c132` - Epic: XEL-152 --- # XEL Integration Source: https://docs.openyield.pro/xel-integration/ **Version**: 1.0 **Date**: 2026-07-11 **Parent**: `docs/23-auto-yield-implementation.md`, `docs/24-open-yield-api.md`, `docs/25-open-yield-sui-move.md` **Plane**: XEL-152 **Reuse scan**: Codex comments on XEL-152…168 Once Open Yield is built, it is **not** a separate product next to XEL. It is the **treasury capability** every Living Character can use. --- ## 1. Integration model (one sentence) **Each XEL character’s shared Endowment becomes an Open Yield position; persona stays GEN-optional; keepers + owner APIs drive stake/harvest/Auto Pay under policy.** ``` Persona (GEN optional) Memory (Walrus/Seal) Mind (inference providers) │ ▼ LivingCharacter aNFT (owned) │ ▼ Endowment + TreasuryPolicy ── Open Yield execution home (Sui) │ ├── Auto Yield (opt-in stake / harvest / compound) ├── Auto Pay (post-fee residual → storage/inference/x402) ├── Bridge-in (Mayan Base/Sol → Sui) ├── Gas sponsor (Turbos USDC→SUI) └── Opportunity agent (DefiLlama map, propose only) ``` --- ## 2. What already plugs in (do not rebuild) | XEL subsystem | Path | Open Yield role after build | |---|---|---| | Endowment / fee / harvest | `contracts/v2/sources/treasury.move` | Onchain capital + ~5% yield skim | | Venue adapters | `runtime/treasury/suilend-adapter.mjs`, `navi-adapter.mjs` | supply/withdraw/claim/read_apy | | Lifecycle / waterfall math | `runtime/treasury/lifecycle.mjs` | plans, net-yield receipts, allowlist blockers | | Heartbeat keepers | `runtime/keepers/heartbeat-runner.mjs`, `talus-leader-heartbeat.mjs` | permissionless harvest poke | | Bridge-in | `runtime/treasury/mayan-executor.mjs` + backend bridge-to-sui | fund endowment from Base/Sol | | Gas | `runtime/treasury/gas-sponsor.mjs` | sponsor stake/harvest/sweep PTBs | | Payments | `runtime/payments/x402.mjs` + `payment.move` | Auto Pay rail | | Operator yield API | `backend/main.py` stablecoin-yield routes | internals under product API | | Honest FE pattern | `frontend/components/settings/YieldVenueStatus.tsx` | template for Auto Yield UI | | Funding snapshots | `backend/main.py` funding budget/snapshot | estimates + runway inputs | | Policy schema | `schemas/treasury-policy.v1.json` | extend for auto_yield / auto_pay fields | | Public agent surface | `site/skill.md` / `skill.json` | document Open Yield capability | --- ## 3. Integration layers (after build) ### 3.1 Onchain (Sui) | Step | How | |---|---| | Character mint | Create aNFT + shared Endowment + TreasuryPolicy (`auto_yield_enabled=false`) | | Deposit | Anyone/owner `top_up_endowment` — **no fee** | | Enable Auto Yield | Owner sets `auto_yield_enabled=true` (+ optional max stake / venue) | | Stake | Owner (or narrow stake envelope) → Suilend/NAVI supply under allowlist | | Harvest | Permissionless heartbeat: claim PTB → fee skim → residual compound | | Withdraw | Owner `withdraw_endowment_principal` — **no fee**; unstake first if needed | | Survival | Only auto-unstake when storage due + liquid short | **Integration rule:** Open Yield never replaces the aNFT; it **operates the endowment**. ### 3.2 Backend API (product surface) Wrap existing operator treasury into **character-scoped Open Yield APIs**: | Product route | Calls into | |---|---| | `GET/PUT /api/characters/{id}/auto-yield` | policy + app mirror of `auto_yield_enabled` | | `POST .../open-yield/preview` · `route` | discovery + scorer + optional top_up/stake plan | | `POST .../open-yield/harvest` | keeper/plan builders + adapters | | `POST .../open-yield/withdraw` | owner auth → unstake + withdraw | | `GET .../open-yield/position` | funding snapshot + read_apy + estimates | | `POST .../open-yield/auto-pay` | spend policy + x402 targets | | `GET /api/open-yield/opportunities` | discovery agent snapshots | | `GET /api/open-yield/venues` | stablecoin-yield readiness | Keep `/api/treasury/sui/stablecoin-yield/*` as **internal/operator** aliases. ### 3.3 Runtime / keepers | Job | Integration | |---|---| | Heartbeat cron / Talus | Load character roster of shared endowments; claim→heartbeat; gas-sponsor PTB | | Opportunity agent | Cron: DefiLlama → filter TVL≥$50M → propose; never stake | | Wallet watcher | Base/Sol USDC → Mayan → credit endowment → optional notify owner to stake | | Auto Pay | After harvest residual: pay storage/inference within policy via x402 | ### 3.4 Frontend (xel.xyz) | Surface | Change | |---|---| | Settings / Treasury | Auto Yield toggle OFF default; venue; staked/liquid | | Endowment / Funding screens | Real estimates from API; kill demo APY (`funding-model.ts` stubs) | | YieldVenueStatus | Extend for Open Yield status + blockers | | Character profile (public) | Optional: net APY estimate / runway only if honest data exists | | Fan credits | Unchanged ring-fence — never Auto Pay source | ### 3.5 Public protocol / agents | Surface | Integration | |---|---| | `site/skill.md` | Document Open Yield tools: preview, route, harvest, position | | MCP / GEN agents | Call XEL open-yield routes with character scope + AgentCap limits | | Provider registry | Optional capability `treasury.yield.v1` / `open_yield.v1` | ### 3.6 GEN boundary (important) | GEN | XEL Open Yield | |---|---| | Optional persona synthesis | Does **not** control funds | | GEN credits / x402 purchase | Separate from endowment principal | | Publish to XEL | Character may arrive unfunded; Open Yield starts when endowment funded | **Line:** persona by GEN, capital by Open Yield — both optional; character valid with neither. --- ## 4. Lifecycle once integrated ``` 1. Mint character → Endowment liquid, Auto Yield OFF 2. Fund (top_up and/or Mayan bridge-in) 3. Owner enables Auto Yield (optional) 4. Owner stakes amount/venue (or owner-signed stake envelope) 5. Keepers harvest permissionlessly → fee skim ~5% → residual compounds 6. Storage due → survival auto-unstake min if needed 7. Auto Pay (if on) → residual → storage/inference/x402 8. Owner can withdraw anytime (after unstake if needed) ``` --- ## 5. Auth matrix (integration-critical) | Action | Owner | AgentCap | Anyone | |---|---|---|---| | top_up | yes | if funded path allows | yes (pay in) | | enable Auto Yield | **yes** | no | no | | stake principal | **yes** (or narrow envelope) | only if explicit stake scope | no | | harvest poke | yes | yes | **yes** | | Auto Pay config | **yes** | no widen | no | | Auto Pay execute residual | policy | within spend policy | no | | withdraw principal | **yes** | **no** | no | --- ## 6. Data that must flow character-scoped | Field | Source after integration | |---|---| | `auto_yield_enabled` | onchain TreasuryPolicy + app mirror | | `liquid_micros` / `staked_micros` | indexer + venue position | | `gross_apy_bps` / `net_apy_bps` | adapter read_apy + fee bps | | `last_harvested_at` | heartbeat events | | `opportunities[]` | discovery agent DB/snapshots | | `auto_pay` config | app policy + spend-policy | | `fee_bps` | protocol_yield_skim_bps | All APIs keyed by **character_id** (and endowment object id), not a global vault share id. --- ## 7. Enablement order (integrate safely) 1. Ship discovery/scorer/API/toggle **without** broadcast 2. Wire FE honesty to real status 3. Staging: owner stake mock → harvest fee path 4. Close XEL-70 NO-GO (SDK roots, flags, keeper roster, prepare smoke) 5. Staging live Suilend with small amount 6. Prod flags only with mav go 7. Auto Pay last (post-fee residual only) --- ## 8. What “done integrated” looks like - New character: Auto Yield **OFF**, liquid endowment only - Owner can enable, stake USDC on Suilend, see real net APY - Permissionless harvest takes **~5% fee**, compounds residual - Bridge-in from Base/Sol credits **that** character’s endowment - Gas-sponsored PTBs for stake/harvest - FE never shows demo 5% APY - Agent cannot withdraw principal - skill.md lists Open Yield capabilities for external agents --- ## 9. Non-integration (keep out of XEL core) - Standalone Base `YieldRouter.sol` as character home - Multi-user ERC-4626 share vault replacing per-character endowment - Auto-stake on mint - DefiLlama as execution oracle - GEN holding character principal --- ## 10. Ticket → integration seam (quick) | Ticket | Integration point | |---|---| | 153–154, 164 | runtime discovery/scorer → opportunities API → skill/orchestrator | | 155–157 | character open-yield API → backend stablecoin-yield internals | | 156 | TreasuryPolicy + FE toggle | | 158, 167–168 | keepers + gas-sponsor + fee waterfall | | 159 | keeper survival path | | 160 | Endowment/Funding/Settings FE | | 161 | prod env / readiness | | 163 | x402 + residual after harvest | | 165 | funding snapshot estimates block | | 166 | wallet-watcher + Mayan + optional Turbos | | 162 | skill.md + docs + Plane pages | --- # Enablement Runbook Source: https://docs.openyield.pro/enablement/ **Status:** NO-GO for prod broadcast until explicit mav go. **Workspace:** `openyieldgork` (local) ## Defaults (safe) ```bash OPEN_YIELD_EXECUTION_ENABLED=false # or unset OPEN_YIELD_ALLOW_BROADCAST=false # kill switch — keep false ``` Staging prepare may set `EXECUTION_ENABLED=true` with **broadcast still false**. ## Pre-enable checklist 1. `npm test` green 2. `npm run test:e2e` green 3. `GET /api/open-yield/readiness` → `launchHomes: ["sui","solana"]`, `broadcast_enabled: false` 4. `GET /api/open-yield/enablement` → `goNoGo` ≠ live GO without go 5. Sui: Suilend prepare smoke (see `docs/12-live-smoke-treasury-2026-07-11.md`) — already proven $0.10 6. Solana: Kamino live SDK root configured before any broadcast ## Owner opt-in language - Stake is **OFF by default** - Supersedes any “auto-stake on mint / character always stakes” copy - Survival auto-unstake is the **only** automatic principal move ## Kill switch Set `OPEN_YIELD_ALLOW_BROADCAST=false` (or unset) to stop all submits. One flag. ## Claim cap (ops default 2026-07-11) Grok set production: ``` XEL_STABLECOIN_YIELD_MAX_CLAIM_MICROS=100000 # $0.10 USDC-equivalent policy bound ``` on **xel-ash-1** (`/opt/xel-api/.env` + pm2 env). Live Suilend **incentive** claim succeeded: - digest: `CndwuoFZPkVQivpKACDudKuQuvM4QAveJQSK4cEpmURL` - SPRING_SUI claimed: 170310 base units - USDC principal unchanged Raise only with explicit go. Interest on USDC still realizes via cToken/withdraw, not claimRewards. --- # Open Core Source: https://docs.openyield.pro/open-core/ **Date**: 2026-07-11 **Status**: Locked strategy **Authority**: `docs/00-product-constitution.md` ## Strategy: Open Core + Closed Intelligence | Layer | Public (OSS) | Private (XEL / GEN) | |---|---|---| | **Execution** | Move contracts (when extracted), fee/withdraw/harvest invariants | Deploy keys, upgrade ops | | **Specs** | Constitution, fee model, API schemas, architecture docs | Internal runbooks with secrets | | **Basic SDK** | `@open-yield/sdk` — typed client for public/product APIs | Hosted keys, rate limits | | **Adapters (interfaces)** | Adapter interface + mock adapters | Live mainnet signer wiring | | **Orchestrator** | Open reference agent (propose/score with public rules) | Advanced multi-goal reasoning, GEN models | | **Discovery** | DefiLlama client + filters (TVL floor) | Proprietary ranking weights (optional later) | | **Auto Pay** | Basic residual → allowlisted targets schema | Advanced multi-agent routing / GEN billing glue | | **XEL character** | Integration docs | Mint, persona, hosted wallet, Infisical | | **Hosted services** | — | xel.xyz API, keepers, gas sponsor keys | ## Why 1. **Trust** — contracts + fees auditable (constitution: open execution). 2. **Adoption** — SDK + examples lower agent integration cost (Superform has **no** agent SDK — we do). 3. **Moat** — intelligence + character economy stay with XEL/GEN. 4. **Name honesty** — “Open Yield” means open execution + tools, not open every ranking model. ## Publish order 1. Docs + constitution (done in this repo) 2. **SDK package** (public API client) 3. Runtime discovery/scorer modules (OSS) 4. Contract packages when split from XEL 5. Keep advanced agent + XEL product private until deliberate open ## SDK must ship See `packages/sdk/` — public methods mirror `docs/02-agent-api.md`: `previewRoute` · `routeYield` · `harvest` · `withdraw` · `getPosition` · `enableAutoPay` · `listOpportunities` · `listVenues` Auth: character-scoped session / agent key — never protocol admin keys in SDK.