From e8085fd896f22f3d1a318aee7acea841ce05ead4 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 8 Jul 2026 13:06:47 -0600 Subject: [PATCH 1/6] Add design spec for Blend DeFi playbook skill Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-08-blend-defi-playbook-design.md | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md diff --git a/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md b/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md new file mode 100644 index 0000000..6e761a3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md @@ -0,0 +1,115 @@ +# Blend DeFi Playbook Skill — Design + +**Date:** 2026-07-08 +**Issue:** [stellar/stellar-cli#2625](https://github.com/stellar/stellar-cli/issues/2625) — "DeFi skill: Blend playbook" +**Branch:** `feat/defi-blend-skill` (branched from `feat/wallet-skills-cli`) +**Epic:** Improve Agent UX for Wallets ([stellar-cli#2628](https://github.com/stellar/stellar-cli/issues/2628)) + +## Goal + +Add a playbook skill that lets an AI agent interact with the **Blend** lending +protocol on Stellar/Soroban using existing CLI commands (`contract invoke`, +`stellar token`) composed with the wallet skill. The playbook bridges raw ledger +data and protocol architecture: an agent should be able to locate Blend pools, +read live pool/backstop state over RPC, and construct the core lending and +backstop transactions. + +### Acceptance criteria (from the issue) + +1. The playbook must let agents locate Blend pools and execute core + lending/backstop transactions using `stellar token` / `contract invoke` + + wallet skills. +2. All dynamic-data scripts must operate using **RPC calls only** — no indexer. + +## Decisions + +- **Structure:** Blend is its own package under a DeFi namespace. A thin + `skills/defi/SKILL.md` index is added so the wallet skill's existing + `../defi/SKILL.md` forward-link resolves and so future protocols have a home. +- **Verification:** Documented from architecture knowledge. Function signatures, + the `RequestType` enum numbering, the Q4W timelock duration, and the emission + split carry explicit `[VERIFY]` flags pointing at the `blend-contracts` / + `blend-contracts-v2` source. Scripts are structurally correct RPC recipes to be + confirmed against live contracts before signing a mainnet transaction. +- **Networks:** Mainnet and testnet get equal-weight treatment; addresses are + tabled per network. + +## File layout + +``` +skills/defi/ + SKILL.md # DeFi index/router; wallet<->protocol boundary; links to blend/ + blend/ + SKILL.md # Blend entry: architecture, contract refs, routing, deps, [VERIFY] notes + lending.md # supply / withdraw / borrow / repay; reading reserves & positions + backstop.md # deposit / Q4W / withdraw / claim; backstop APR + ranking script +``` + +## Contract references + +Only three addresses per network are authoritative from the Stellar protocol +registry; everything else is derived on-chain (RPC-only, satisfying criterion 2). + +| Role | Mainnet | Testnet (v2) | +|------|---------|--------------| +| Pool Factory | `CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB` | `CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6` | +| Backstop | `CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3` | `CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA` | +| Emitter | `CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K` | `CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6` | + +Derived on-chain (not hardcoded): +- **BLND token** — from the Emitter getter. +- **Backstop token** (Comet 80/20 BLND:USDC LP) — from `Backstop.backstop_token()`. +- **Oracle** — per-pool, from `PoolConfig.oracle`. +- **Individual pools** — enumerated from the Pool Factory. + +## Component detail + +### `skills/defi/SKILL.md` (index) +~15 lines. What DeFi skills cover, the wallet-primitives vs protocol-logic +boundary (mirrors the wallet skill's "Working with Soroban protocols" section), +and a link into `blend/`. + +### `skills/defi/blend/SKILL.md` (entry) +- **Architecture:** pools, reserves (bToken/dToken accounting), backstop module, + BLND emissions, emitter, oracle, pool factory. +- **Contract references:** the table above; derive-on-chain getters for the rest. +- **Routing:** lend/borrow → `lending.md`; earn on backstop → `backstop.md`. +- **Dependency notes:** `contract invoke` works today; `stellar token` (#2620) + and structured output (#2622) are the ergonomic path once shipped; keys, + signing, and submission defer to the wallet skill (`../../wallet/`). +- **Scope/`[VERIFY]` banner** stated prominently near the top. + +### `skills/defi/blend/lending.md` +- Pool `submit(from, spender, to, requests)` model. `[VERIFY]` arg order. +- **RequestType table:** Supply 0, Withdraw 1, SupplyCollateral 2, + WithdrawCollateral 3, Borrow 4, Repay 5, plus auction fill types. `[VERIFY]` + against source; note V1/V2 numbering may differ. +- Worked `contract invoke` examples (both networks): supply-collateral, borrow, + repay, withdraw; batching multiple `Request`s atomically. +- Reading reserve list / reserve data / pool config / user positions via RPC + (`simulateTransaction` on read getters). Claiming lender emissions. + +### `skills/defi/blend/backstop.md` +- `deposit`, `queue_withdrawal` (Q4W), `dequeue_withdrawal`, `withdraw`, `claim`. +- Q4W timelock mechanics and duration (`[VERIFY]` — 21 days believed; confirm + `Q4W_LOCK_TIME`). Funds remain at risk while queued. +- **Dynamic-data scripts (embedded bash, RPC-only):** + 1. **List available pools** — enumerate factory-created pools from the Pool + Factory. + 2. **Rank backstop opportunities** — per pool read backstop size + + `bstop_rate` interest share + BLND emissions, compute an approximate + backstop APR, rank. Assumptions/`[VERIFY]` inline. + +## Site registration + +- `site/src/data/skills.ts`: add `"DeFi"` to `FilterType` + `FILTERS`; add a + `SKILL_CARD_SOURCES` entry pointing at `skills/defi/blend/SKILL.md`. +- `site/src/app/styles.scss`: add `DeFi` to the `@each` filter whitelist so the + card renders when the filter is selected (same wiring the wallet card needed). + +## Out of scope + +- Live on-chain verification of signatures/enums (deferred by the + document-from-knowledge decision; `[VERIFY]` flags mark what to confirm). +- Protocols other than Blend (the DeFi index is structured to add them later). +- Liquidation/auction bot logic beyond documenting the relevant RequestTypes. From adeb4f100f5e4e35b3e10d44e0cea26402c4f8c5 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 8 Jul 2026 13:12:53 -0600 Subject: [PATCH 2/6] Add Blend DeFi playbook skill Add a DeFi skill package with the Blend lending playbook, closing the Blend portion of the Agentic Wallet CLI epic (stellar/stellar-cli#2625, #2628). Composes the wallet skill for keys/signing/submission. - skills/defi/SKILL.md: DeFi index/router; draws the wallet-primitives vs protocol-logic boundary and resolves the wallet skill's ../defi/ link - skills/defi/blend/SKILL.md: architecture, mainnet/testnet contract refs (3 registry-authoritative addresses; the rest derived on-chain via RPC), routing, and dependency/[VERIFY] notes - skills/defi/blend/lending.md: the Pool `submit` model + RequestType table, supply/borrow/repay/withdraw examples, pool discovery, reading state - skills/defi/blend/backstop.md: deposit/Q4W/withdraw/claim and an RPC-only "rank backstops by APR" script Wire the site: new "DeFi" filter category, a SKILL_CARD_SOURCES entry, and the styles.scss filter whitelist so the card renders. All dynamic-data scripts use RPC only (no indexer). Written from Blend's contract architecture; signatures, RequestType numbering, the Q4W timelock, and the emission split are flagged [VERIFY] against blend-contracts source. Co-Authored-By: Claude Opus 4.8 (1M context) --- site/src/app/styles.scss | 2 +- site/src/data/skills.ts | 9 ++ skills/defi/SKILL.md | 32 +++++++ skills/defi/blend/SKILL.md | 101 ++++++++++++++++++++ skills/defi/blend/backstop.md | 148 +++++++++++++++++++++++++++++ skills/defi/blend/lending.md | 174 ++++++++++++++++++++++++++++++++++ 6 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 skills/defi/SKILL.md create mode 100644 skills/defi/blend/SKILL.md create mode 100644 skills/defi/blend/backstop.md create mode 100644 skills/defi/blend/lending.md diff --git a/site/src/app/styles.scss b/site/src/app/styles.scss index 0fc30cf..ac901e1 100644 --- a/site/src/app/styles.scss +++ b/site/src/app/styles.scss @@ -314,7 +314,7 @@ } @each $filter - in ("Smart Contracts", "Agentic Payments", Frontend, Assets, APIs, ZK, Wallets, Ecosystem) + in ("Smart Contracts", "Agentic Payments", Frontend, Assets, APIs, ZK, Wallets, DeFi, Ecosystem) { .SkillsLanding__filterPanel[data-active-filter="#{$filter}"] .SkillsLanding__filterItem[data-category="#{$filter}"] { diff --git a/site/src/data/skills.ts b/site/src/data/skills.ts index d000487..de899a6 100644 --- a/site/src/data/skills.ts +++ b/site/src/data/skills.ts @@ -23,6 +23,7 @@ type FilterType = | "APIs" | "ZK" | "Wallets" + | "DeFi" | "Ecosystem"; /** @@ -38,6 +39,7 @@ export const FILTERS: readonly FilterType[] = [ "APIs", "ZK", "Wallets", + "DeFi", "Ecosystem", ] as const; @@ -122,6 +124,13 @@ export const SKILL_CARD_SOURCES: readonly SkillCardSource[] = [ description: "Use the Stellar CLI as a wallet: manage keys, fund accounts, check balances, transfer tokens, handle trustlines, and diagnose transactions.", }, + { + source: "skills/defi/SKILL.md", + category: "DeFi", + title: "DeFi Protocols (Blend)", + description: + "Act on Soroban DeFi protocols from the CLI. Includes the Blend lending playbook: discover pools, read pool and backstop state over RPC, supply, borrow, repay, and provide backstop capital.", + }, { source: "skills/zk-proofs/SKILL.md", category: "ZK", diff --git a/skills/defi/SKILL.md b/skills/defi/SKILL.md new file mode 100644 index 0000000..7dafdd6 --- /dev/null +++ b/skills/defi/SKILL.md @@ -0,0 +1,32 @@ +--- +name: defi +description: Interact with Stellar/Soroban DeFi protocols from the CLI: discover pools and markets, read live protocol state over RPC, and compose the transactions that act on them (lend, borrow, provide backstop/liquidity). Per-protocol playbooks map each protocol's operations to concrete `contract invoke` / `stellar token` calls. Use when acting on a Soroban DeFi protocol rather than moving value between accounts. +user-invocable: true +argument-hint: "[defi task]" +--- + +# DeFi: acting on Soroban protocols + +The [wallet skill](../wallet/SKILL.md) covers wallet **primitives** — moving value, trustlines, transaction mechanics. It deliberately does *not* teach how any specific Soroban protocol works. + +These DeFi playbooks pick up where the wallet skill stops. Each one maps a protocol's operations to concrete `contract invoke` / `stellar token` calls, carries the current contract references, and shows how to read the protocol's live state over RPC — so you act on the protocol instead of reverse-engineering it from raw ledger data. + +The division of labor is constant: + +- **This skill** — what the protocol is, which contract/function each operation maps to, and how to read its state. +- **The wallet skill** — keys, signing, fees, submission, and reading account/token balances. Every transaction here is signed and submitted with the wallet pipeline. + +## Playbooks + +| Protocol | What it is | Playbook | +|----------|------------|----------| +| Blend | Permissionless lending pools + a backstop insurance module | [blend/SKILL.md](blend/SKILL.md) | + +More protocols will be added here. When acting on a protocol not yet covered, use the [smart-contracts skill](../smart-contracts/SKILL.md) to inspect its interface and the [data skill](../data/SKILL.md) to read its state over RPC. + +## Related skills + +- Keys, signing, fees, submission, balances → [`../wallet/SKILL.md`](../wallet/SKILL.md) +- SEP-41 tokens and SACs these protocols call → [`../smart-contracts/SKILL.md`](../smart-contracts/SKILL.md) +- Reading chain data over RPC → [`../data/SKILL.md`](../data/SKILL.md) +- Classic assets and trustlines → [`../assets/SKILL.md`](../assets/SKILL.md) diff --git a/skills/defi/blend/SKILL.md b/skills/defi/blend/SKILL.md new file mode 100644 index 0000000..8fe7a55 --- /dev/null +++ b/skills/defi/blend/SKILL.md @@ -0,0 +1,101 @@ +--- +name: blend +description: Blend lending protocol playbook for the Stellar CLI. Covers Blend's architecture (permissionless pools, reserves, the backstop insurance module, BLND emissions), the mainnet/testnet contract references, how to discover pools and read live pool/backstop state over RPC, and how to build the core lending and backstop transactions with `contract invoke` + `stellar token`. Use when supplying, borrowing, repaying, withdrawing, or providing backstop capital on Blend from the command line. +user-invocable: true +argument-hint: "[blend task]" +--- + +# Blend: lending & backstop playbook + +Blend is a permissionless, over-collateralized lending protocol on Soroban. Anyone can create an isolated lending **pool**; each pool holds one or more **reserves** (assets you can supply and borrow) and is insured by a **backstop** of first-loss capital that earns a share of interest plus BLND emissions. + +This playbook maps each Blend operation to the `contract invoke` (or `stellar token`) call that performs it, and shows how to read live pool/backstop state over RPC. Keys, signing, fees, and submission are handled by the [wallet skill](../../wallet/SKILL.md) — every transaction below is signed and submitted with that pipeline. + +> **Scope and verification.** This playbook is written from Blend's contract architecture, not regenerated from live contract metadata. Items marked **`[VERIFY]`** — function signatures, the `RequestType` numbering, the Q4W timelock, and the emission split — should be confirmed against the [`blend-contracts`](https://github.com/blend-capital/blend-contracts) / [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2) source (or by simulating the call) before you sign a mainnet transaction. Confirm the deployed interface with `stellar contract info interface --id ` — see [reading state](#reading-blend-state-over-rpc). Mainnet and testnet run different contract versions; the ABI can differ. + +## Read the file that matches the task + +| Task | File | +|------|------| +| Supply, borrow, repay, withdraw; read reserves, positions, pool config; claim lender emissions | [lending.md](lending.md) | +| Deposit to a backstop, queue/withdraw (Q4W), claim backstop emissions; find the most profitable backstop | [backstop.md](backstop.md) | + +## Architecture + +| Entity | What it is | +|--------|------------| +| **Pool** | One isolated lending market. Holds all reserves, user positions, and interest accounting. You interact with a pool almost entirely through its `submit` entrypoint. Each pool references one oracle and one backstop. | +| **Reserve** | A single asset within a pool. Supply is tracked as **bTokens** (supply/collateral shares), debt as **dTokens** (liability shares); their value grows via `b_rate` / `d_rate` indexes. These are internal accounting units, not transferable SEP-41 tokens. | +| **Pool Factory** | Deploys pools deterministically and records which pool addresses it created. The backstop trusts only factory-deployed pools. Enumerate pools from here — see [discovering pools](lending.md#discovering-pools). | +| **Backstop** | Holds first-loss capital per pool, denominated in the **backstop token** — a Comet (Balancer-style) 80/20 BLND:USDC LP token `[VERIFY weights]`. Backstop depositors absorb bad debt in exchange for a share of interest + BLND emissions. See [backstop.md](backstop.md). | +| **Emitter** | Streams BLND emissions to the backstop, distributed to pools in the reward zone. | +| **Oracle** | A SEP-40-style price oracle the pool reads for asset prices. Address is per-pool (`PoolConfig.oracle`); read it, don't assume it. | + +## Contract references + +Only these three addresses per network are treated as fixed literals. **Everything else is derived on-chain** (RPC only, no indexer): the BLND token, the backstop LP token, each pool's oracle, and the pools themselves. + +| Role | Mainnet | Testnet | +|------|---------|---------| +| Pool Factory | `CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB` | `CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6` | +| Backstop | `CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3` | `CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA` | +| Emitter | `CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K` | `CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6` | + +`[VERIFY]` The testnet deployment is Blend v2; the mainnet addresses above carry no version label in the registry — confirm the deployed version against Blend's published deployment before assuming v1 vs v2 semantics (the `RequestType` numbering and `submit` shape can differ between versions). + +### Derive the rest on-chain + +```bash +# Set once. Blend runs on both networks; pick one. +POOL_FACTORY=CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB +BACKSTOP=CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3 +EMITTER=CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K +NETWORK=mainnet + +# BLND token address — from the Emitter [VERIFY getter name: get_blnd_token / blnd_id] +stellar contract invoke --id "$EMITTER" --network "$NETWORK" -- get_blnd_token + +# Backstop LP token (Comet BLND:USDC) — from the Backstop [VERIFY getter name] +stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +``` + +## Prerequisites + +- The [wallet skill](../../wallet/SKILL.md) set up: a funded key, a configured RPC/network, and (for supply/repay) a trustline to any classic asset you deposit. +- `stellar-cli` installed. Confirm a contract's real interface before invoking: `stellar contract info interface --id `. + +## Reading Blend state over RPC + +All reads are `contract invoke` on a getter. The CLI simulates read-only calls and returns the decoded value — no source key or signing required, no indexer involved. Inspect the exact ABI first: + +```bash +stellar contract info interface --id --network "$NETWORK" +``` + +- **Pool state** (reserve list, reserve data, config, positions) → [lending.md](lending.md#reading-pool-state). +- **Backstop state** (pool balance, user balance, Q4W, emissions) → [backstop.md](backstop.md#reading-backstop-state). + +## Building transactions + +State-changing calls (`submit`, backstop `deposit`, etc.) go through `contract invoke ... --send=yes` signed by a wallet key. Token movements into the protocol (the asset you supply/repay) are ordinary SEP-41 / SAC transfers the contract pulls via authorization; the CLI collects and signs the required auth entries automatically during simulation. + +- `contract invoke` — available today; the baseline for every Blend call. +- `stellar token` ([stellar-cli#2620](https://github.com/stellar/stellar-cli/issues/2620)) — the typed SEP-41/SAC client used for reading balances and moving the underlying assets; some examples note the `stellar token` form as the ergonomic path once shipped. +- Structured `{status, result, error}` output ([stellar-cli#2622](https://github.com/stellar/stellar-cli/issues/2622)) — the branchable receipt these examples read from once available. + +## Dependency notes + +This playbook is part of the Improve Agent UX for Wallets epic ([stellar-cli#2628](https://github.com/stellar/stellar-cli/issues/2628)) and composes with the wallet skill. Where a command relies on in-flight CLI surface (`stellar token`, structured output), it is noted inline; the underlying `contract invoke` path works today. + +## Related skills + +- Keys, signing, fees, submission, balances → [`../../wallet/SKILL.md`](../../wallet/SKILL.md) +- The DeFi index and other protocol playbooks → [`../SKILL.md`](../SKILL.md) +- Inspecting a contract's interface / SEP-41 tokens → [`../../smart-contracts/SKILL.md`](../../smart-contracts/SKILL.md) +- Reading chain data over RPC → [`../../data/SKILL.md`](../../data/SKILL.md) + +## Documentation + +- [Blend docs](https://docs.blend.capital) +- [blend-contracts (source of truth for the ABI)](https://github.com/blend-capital/blend-contracts) +- [DeFi skill: Blend playbook (issue #2625)](https://github.com/stellar/stellar-cli/issues/2625) diff --git a/skills/defi/blend/backstop.md b/skills/defi/blend/backstop.md new file mode 100644 index 0000000..2f7d164 --- /dev/null +++ b/skills/defi/blend/backstop.md @@ -0,0 +1,148 @@ +# Blend backstop: deposit, Q4W, withdraw, claim + +The backstop is Blend's insurance layer. Depositors stake the **backstop token** — a Comet 80/20 BLND:USDC LP token `[VERIFY weights]` — behind a specific pool. In return they earn a share of that pool's interest plus BLND emissions. In exchange, backstop capital is **first-loss**: it absorbs bad debt before lenders take a hit. That risk is why exits are time-locked (see [Q4W](#queue-for-withdrawal-q4w)). + +Everything here targets the Backstop contract: + +```bash +BACKSTOP=CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3 # mainnet +NETWORK=mainnet +ME=alice +POOL= # the pool you are backstopping + +stellar contract info interface --id "$BACKSTOP" --network "$NETWORK" # confirm the ABI +``` + +You deposit the **backstop token**, not BLND or USDC directly. Get its address from the backstop, then acquire/mint the LP token via its Comet pool before depositing: + +```bash +# [VERIFY getter name] +stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +``` + +## Deposit + +`[VERIFY signature]` — deposits backstop LP tokens behind a pool, minting backstop shares: + +```bash +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ + -- deposit \ + --from "$ME" \ + --pool_address "$POOL" \ + --amount "1000000000" +``` + +Shares represent your claim on that pool's backstop; their value rises as the backstop accrues interest and falls if it absorbs bad debt. + +## Queue for withdrawal (Q4W) + +Backstop deposits are **not** instantly withdrawable — this stops depositors from fleeing right before a liquidation. To exit you queue, wait out the timelock, then withdraw. **While queued, the funds stay at risk** (they still absorb bad debt) and keep accruing. + +`[VERIFY]` timelock duration — believed to be **21 days** (confirm the `Q4W_LOCK_TIME` constant in [`blend-contracts`](https://github.com/blend-capital/blend-contracts)). + +```bash +# Start the timelock on part of your position +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ + -- queue_withdrawal \ + --from "$ME" --pool_address "$POOL" --amount "1000000000" + +# Change your mind while still locked — return it to the active backstop +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ + -- dequeue_withdrawal \ + --from "$ME" --pool_address "$POOL" --amount "1000000000" + +# After the timelock elapses — withdraw the LP tokens +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ + -- withdraw \ + --from "$ME" --pool_address "$POOL" --amount "1000000000" +``` + +Check whether a queued entry has unlocked before calling `withdraw` — read the user balance and compare each Q4W entry's expiration against the current ledger time (see [reading backstop state](#reading-backstop-state)). + +## Claim backstop emissions + +Claim accrued BLND across one or more pools you backstop `[VERIFY signature]`: + +```bash +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ + -- claim \ + --from "$ME" \ + --pool_addresses '["'"$POOL"'"]' \ + --to "$ME" +``` + +## Reading backstop state + +All reads simulate; no signing, no indexer. `[VERIFY]` getter names against source: + +```bash +# Total backstop size for a pool: shares, LP tokens, and queued-for-withdrawal amount +stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- pool_balance --pool_address "$POOL" + +# Your balance for a pool: shares + your Q4W entries (each with amount + expiration) +stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" \ + -- user_balance --pool_address "$POOL" --user "$(stellar keys address "$ME")" + +# The backstop LP token address (Comet BLND:USDC) +stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +``` + +- **`PoolBalance`** — `shares`, `tokens` (LP tokens deposited), `q4w` (queued amount). +- **`UserBalance`** — `shares`, `q4w: Vec` where `exp` is the unlock ledger timestamp. + +## Find the most profitable backstop + +Backstop yield for a pool comes from two streams, both measured against the pool's total backstop deposit value: + +1. **Interest share** — the pool routes a fraction of borrower interest (`PoolConfig.bstop_rate`) to the backstop. Scales with borrow volume × borrow APR × `bstop_rate`. +2. **BLND emissions** — the Emitter streams BLND to reward-zone pools, split by backstop size and (within a pool) between the backstop and reserve suppliers/borrowers. `[VERIFY]` the exact split and reward-zone rules — v1 and v2 differ; do not hardcode percentages. + +Approximate backstop APR: + +``` +APR ≈ (annualized_interest_share_USD + annualized_emissions_USD) / backstop_deposit_value_USD +``` + +The following script ranks pools by an approximate backstop APR using **RPC only**. It is a scaffold: it reads the real on-chain inputs and shows where each number comes from; the final APR math carries `[VERIFY]` flags because the emission split and LP-token valuation must be confirmed against source before trusting the ranking for real capital. + +```bash +#!/usr/bin/env bash +# rank-backstops.sh — approximate backstop APR per Blend pool, RPC only. +# Requires: stellar-cli, jq. Usage: NETWORK=mainnet ./rank-backstops.sh +set -euo pipefail + +NETWORK=${NETWORK:-mainnet} +BACKSTOP=${BACKSTOP:-CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3} + +invoke() { stellar contract invoke --id "$1" --network "$NETWORK" -- "${@:2}"; } + +# 1. Get the candidate pools. The reward zone is the practical source of active, +# emission-eligible pools. [VERIFY getter name: reward_zone / get_rz] +pools=$(invoke "$BACKSTOP" reward_zone | jq -r '.[]') + +echo "pool,backstop_tokens,bstop_rate,note" +for POOL in $pools; do + # 2. Backstop size (APR denominator): LP tokens deposited for this pool. + bal=$(invoke "$BACKSTOP" pool_balance --pool_address "$POOL") + tokens=$(echo "$bal" | jq -r '.tokens // .["1"] // "0"') # [VERIFY field name] + + # 3. Interest-share input: the pool's backstop take rate. + cfg=$(invoke "$POOL" get_config) + bstop_rate=$(echo "$cfg" | jq -r '.bstop_rate // "0"') # [VERIFY field name] + + # 4. To finish the APR you still need, per pool: + # - sum over reserves of (d_supply * d_rate * borrow_rate) * bstop_rate -> interest to backstop + # - BLND emitted/sec allocated to this pool's backstop (from emission config) -> annualized + # - LP-token -> USD via the Comet pool reserves + oracle prices + # These require reading get_reserve per asset and the emission config. [VERIFY] + echo "$POOL,$tokens,$bstop_rate,partial-see-VERIFY" +done +``` + +To complete the APR, add per-pool reserve reads (`get_reserve --asset ` for `d_supply`, `d_rate`, and the current borrow rate — see [lending.md](lending.md#reading-pool-state)), the emission-config read for BLND/sec to the backstop, and an LP-token-to-USD conversion (Comet reserve balances + BLND/USDC oracle prices). Rank pools by the resulting APR. Every dynamic input above is read over RPC — no indexer. + +## Related + +- Supplying/borrowing in the pools you backstop → [lending.md](lending.md) +- Keys, signing, submission, and reading token balances → [`../../wallet/SKILL.md`](../../wallet/SKILL.md) +- The `getEvents` RPC pattern for enumerating pools → [`../../data/SKILL.md`](../../data/SKILL.md) diff --git a/skills/defi/blend/lending.md b/skills/defi/blend/lending.md new file mode 100644 index 0000000..622016e --- /dev/null +++ b/skills/defi/blend/lending.md @@ -0,0 +1,174 @@ +# Blend lending: supply, borrow, repay, withdraw + +Every lending action on a Blend pool goes through a single entrypoint: **`submit`**. You describe what you want as a list of `Request`s, and the pool executes them atomically against your position. Supplying, borrowing, repaying, and withdrawing are all just different `RequestType`s in that list. + +Read the pool's real interface before you build anything: + +```bash +stellar contract info interface --id --network "$NETWORK" +``` + +## The `submit` model + +`[VERIFY]` signature against [`blend-contracts`](https://github.com/blend-capital/blend-contracts); arg order/names are from the contract design: + +``` +submit(from: Address, spender: Address, to: Address, requests: Vec) -> Positions +``` + +- `from` — whose position is modified (the borrower/supplier). +- `spender` — who pays tokens **into** the pool (deposits, repays). +- `to` — who receives tokens **out** of the pool (borrows, withdrawals). +- Usually all three are the same account: you. + +Each `Request` is: + +``` +Request { request_type: u32, address: Address, amount: i128 } +``` + +- `address` — the reserve **asset contract** address (the SAC/SEP-41 token, a `C...`), not the pool. +- `amount` — in the asset's native decimals (e.g. 7 for most Stellar assets; read `decimals` to be sure). + +### RequestType + +`[VERIFY]` numbering against source — the Supply/SupplyCollateral split is a v2 feature and v1 may number differently: + +| Value | Name | Action | +|-------|------|--------| +| 0 | Supply | Deposit to earn interest; **not** counted as collateral | +| 1 | Withdraw | Withdraw a plain Supply position | +| 2 | SupplyCollateral | Deposit **as collateral** (enables borrowing power) | +| 3 | WithdrawCollateral | Withdraw collateral | +| 4 | Borrow | Borrow an asset (creates a dToken liability) | +| 5 | Repay | Repay debt | +| 6 | FillUserLiquidationAuction | Fill a liquidation auction | +| 7 | FillBadDebtAuction | Fill a bad-debt auction (backstop) | +| 8 | FillInterestAuction | Fill an interest auction | +| 9 | DeleteLiquidationAuction | Cancel an auction | + +So: **supply as collateral → 2**, **borrow → 4**, **repay → 5**, **withdraw collateral → 3**. + +The CLI accepts complex arguments as JSON. Each example below sets `POOL` and `ASSET` (a reserve asset address for that pool — discover them with the [reserve list](#reading-pool-state)) and `ME` (your wallet key alias). + +## Supply collateral + +```bash +POOL= +ASSET= # e.g. the USDC SAC +ME=alice # wallet key alias + +# amount is in the asset's native units; 100 units of a 7-decimal asset = 1000000000 +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- submit \ + --from "$ME" --spender "$ME" --to "$ME" \ + --requests '[{"request_type":2,"address":"'"$ASSET"'","amount":"1000000000"}]' +``` + +The pool pulls the asset from `spender` via authorization; the CLI collects and signs the required auth entries during simulation. `spender` needs a balance of `ASSET` (and, for a classic asset, a trustline — see the [wallet skill](../../wallet/SKILL.md)). + +## Borrow + +You can only borrow up to your collateral's borrowing power (collateral value × collateral factor). Borrow sends the asset to `to`: + +```bash +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- submit \ + --from "$ME" --spender "$ME" --to "$ME" \ + --requests '[{"request_type":4,"address":"'"$ASSET"'","amount":"500000000"}]' +``` + +## Repay + +Repay pulls the asset from `spender`. To fully repay, pass an `amount` larger than the debt — Blend caps it at the outstanding balance `[VERIFY over-repay behavior]`; prefer reading the exact liability from [positions](#reading-pool-state) first: + +```bash +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- submit \ + --from "$ME" --spender "$ME" --to "$ME" \ + --requests '[{"request_type":5,"address":"'"$ASSET"'","amount":"500000000"}]' +``` + +## Withdraw collateral + +```bash +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- submit \ + --from "$ME" --spender "$ME" --to "$ME" \ + --requests '[{"request_type":3,"address":"'"$ASSET"'","amount":"1000000000"}]' +``` + +Withdrawal is blocked if it would drop your position below the required collateralization while you hold debt. + +## Batching requests atomically + +One `submit` can carry multiple `Request`s that execute in order and settle atomically — either all succeed or the transaction reverts. Deposit collateral and borrow against it in a single call: + +```bash +COLLATERAL= # asset to supply as collateral +BORROW= # asset to borrow + +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- submit \ + --from "$ME" --spender "$ME" --to "$ME" \ + --requests '[ + {"request_type":2,"address":"'"$COLLATERAL"'","amount":"2000000000"}, + {"request_type":4,"address":"'"$BORROW"'","amount":"500000000"} + ]' +``` + +## Discovering pools + +Pools are created by the Pool Factory; enumerate them over RPC (no indexer). The factory records deployed pools — inspect its interface for the exact getter, then read the list: + +```bash +POOL_FACTORY=CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB # mainnet +stellar contract info interface --id "$POOL_FACTORY" --network "$NETWORK" + +# The factory exposes an is-pool check; some deployments also expose a list. +# [VERIFY getter names against source: e.g. is_pool(address) / deployed pools event history] +``` + +`[VERIFY]` The Pool Factory tracks deployment via `deploy` events and an `is_pool` check rather than always exposing a full list getter. Where no list getter exists, enumerate pool-creation events from RPC (`getEvents` on the factory contract) — still RPC-only, no indexer. See the [data skill](../../data/SKILL.md) for the `getEvents` pattern. The [backstop reward zone](backstop.md#reading-backstop-state) is also a practical source of active pool addresses. + +## Reading pool state + +All reads simulate; no signing. Inspect the interface first (`stellar contract info interface --id "$POOL"`), then call the getters. `[VERIFY]` getter names below: + +```bash +# Pool config: oracle, backstop take rate, status, max positions +stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_config + +# Reserve list: the asset addresses this pool supports +stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_reserve_list + +# One reserve's config + live data (rates, totals, utilization) +stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_reserve --asset "$ASSET" + +# A user's positions: collateral / liabilities / supply, keyed by reserve index +stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_positions --user "$(stellar keys address "$ME")" +``` + +Key fields you will read: + +- **`PoolConfig`** — `oracle` (price source), `bstop_rate` (fraction of interest routed to the backstop), `status`, `max_positions`. +- **`ReserveConfig`** — `decimals`, `c_factor` (collateral factor), `l_factor` (liability factor), the kinked interest-rate curve (`r_base`, `r_one`, `r_two`, `r_three`, `util`), `supply_cap` (v2), `enabled`. +- **`ReserveData`** — `b_rate` / `d_rate` (supply/borrow indexes), `b_supply` / `d_supply` (total b/d tokens), `ir_mod`, `backstop_credit`, `last_time`. +- **`Positions`** — `collateral`, `liabilities`, `supply` maps keyed by reserve index. + +Convert shares to underlying with the rate index: underlying supply ≈ `bToken_balance × b_rate`; underlying debt ≈ `dToken_balance × d_rate` `[VERIFY scaling/decimals]`. + +## Claim lender emissions + +Suppliers and borrowers accrue BLND emissions, claimed from the pool `[VERIFY signature]`: + +```bash +# reserve_token_ids identify which reserve emission streams to claim (supply/borrow sides) +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ + -- claim \ + --from "$ME" \ + --reserve_token_ids '[0,1]' \ + --to "$ME" +``` + +Backstop emissions are separate — see [backstop.md](backstop.md#claim-backstop-emissions). From 19f40855ee6ce0b6e90e8e30acba587e963996ba Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 8 Jul 2026 15:22:38 -0600 Subject: [PATCH 3/6] adds initial blend skill --- skills/defi/SKILL.md | 1 - 1 file changed, 1 deletion(-) diff --git a/skills/defi/SKILL.md b/skills/defi/SKILL.md index 7dafdd6..065b0f5 100644 --- a/skills/defi/SKILL.md +++ b/skills/defi/SKILL.md @@ -22,7 +22,6 @@ The division of labor is constant: |----------|------------|----------| | Blend | Permissionless lending pools + a backstop insurance module | [blend/SKILL.md](blend/SKILL.md) | -More protocols will be added here. When acting on a protocol not yet covered, use the [smart-contracts skill](../smart-contracts/SKILL.md) to inspect its interface and the [data skill](../data/SKILL.md) to read its state over RPC. ## Related skills From f01a43d5c9eee621e4c8ef96902f5d5794307f91 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Fri, 10 Jul 2026 12:17:02 -0600 Subject: [PATCH 4/6] Rewrite Blend skill for Blend v2 with on-chain-verified commands Target the skill exclusively at the Blend v2 deployment and correct everything that live testing against the deployed contracts surfaced: - Replace mainnet contract references with the v2 pool factory, backstop, and emitter addresses; document the v2 testnet deployment and warn about the coexisting v1 deployment - Match all signatures, getters, arg names, and struct shapes to the deployed v2 ABI (pool_data, user_balance, get_positions, backstop claim with min_lp_tokens_out and auto-compounding, 17-day Q4W) - Add emissions coverage: reserve_token_id layout, accrued-emissions getters, claim preview via simulation, and a projected-earnings section for supply interest and BLND emissions with correct scaling - Add tested recipes for pricing BLND via the Comet LP, pricing reserve assets via the pool oracle, and minting backstop LP tokens with a single-sided Comet join - Require --source on read commands (stellar-cli needs a source account to simulate) and note trustline requirements for borrowing - Extract rank-backstops.sh into scripts/ as an executable utility and add tables of contents to both reference files All commands exercised end-to-end on testnet: supply, borrow, repay with refund, withdraw, batched submit, emissions reads, and the full backstop deposit/queue/dequeue lifecycle. --- skills/defi/blend/SKILL.md | 60 +++++----- skills/defi/blend/backstop.md | 88 ++++++-------- skills/defi/blend/lending.md | 123 ++++++++++++++++---- skills/defi/blend/scripts/rank-backstops.sh | 35 ++++++ 4 files changed, 203 insertions(+), 103 deletions(-) create mode 100755 skills/defi/blend/scripts/rank-backstops.sh diff --git a/skills/defi/blend/SKILL.md b/skills/defi/blend/SKILL.md index 8fe7a55..65b23a1 100644 --- a/skills/defi/blend/SKILL.md +++ b/skills/defi/blend/SKILL.md @@ -1,24 +1,26 @@ --- name: blend -description: Blend lending protocol playbook for the Stellar CLI. Covers Blend's architecture (permissionless pools, reserves, the backstop insurance module, BLND emissions), the mainnet/testnet contract references, how to discover pools and read live pool/backstop state over RPC, and how to build the core lending and backstop transactions with `contract invoke` + `stellar token`. Use when supplying, borrowing, repaying, withdrawing, or providing backstop capital on Blend from the command line. +description: Blend v2 lending protocol playbook for the Stellar CLI. Covers Blend's architecture (permissionless pools, reserves, the backstop insurance module, BLND emissions), the v2 mainnet/testnet contract references, how to discover pools and read live pool/backstop state over RPC, how to check accrued BLND emissions and project position earnings, and how to build the core lending and backstop transactions with `contract invoke`. Use when supplying, borrowing, repaying, withdrawing, claiming emissions, estimating yield, or providing backstop capital on Blend from the command line. user-invocable: true argument-hint: "[blend task]" --- -# Blend: lending & backstop playbook +# Blend v2: lending & backstop playbook Blend is a permissionless, over-collateralized lending protocol on Soroban. Anyone can create an isolated lending **pool**; each pool holds one or more **reserves** (assets you can supply and borrow) and is insured by a **backstop** of first-loss capital that earns a share of interest plus BLND emissions. +This playbook covers **Blend v2** — the current deployment on both mainnet and testnet. All addresses, signatures, and semantics below are v2; the legacy v1 mainnet deployment (different addresses, different backstop `claim` shape, 21-day Q4W) is out of scope. + This playbook maps each Blend operation to the `contract invoke` (or `stellar token`) call that performs it, and shows how to read live pool/backstop state over RPC. Keys, signing, fees, and submission are handled by the [wallet skill](../../wallet/SKILL.md) — every transaction below is signed and submitted with that pipeline. -> **Scope and verification.** This playbook is written from Blend's contract architecture, not regenerated from live contract metadata. Items marked **`[VERIFY]`** — function signatures, the `RequestType` numbering, the Q4W timelock, and the emission split — should be confirmed against the [`blend-contracts`](https://github.com/blend-capital/blend-contracts) / [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2) source (or by simulating the call) before you sign a mainnet transaction. Confirm the deployed interface with `stellar contract info interface --id ` — see [reading state](#reading-blend-state-over-rpc). Mainnet and testnet run different contract versions; the ABI can differ. +> **Scope.** This playbook targets **Blend v2**. Confirm the deployed interface with `stellar contract info interface --id ` — see [reading state](#reading-blend-state-over-rpc). The addresses below are the **Blend v2** deployments on both networks; the older v1 mainnet deployment uses different addresses and its ABI can differ. ## Read the file that matches the task | Task | File | |------|------| -| Supply, borrow, repay, withdraw; read reserves, positions, pool config; claim lender emissions | [lending.md](lending.md) | -| Deposit to a backstop, queue/withdraw (Q4W), claim backstop emissions; find the most profitable backstop | [backstop.md](backstop.md) | +| Supply, borrow, repay, withdraw; read reserves, positions, pool config; check/claim BLND emissions; project position earnings (supply APY + emissions) | [lending.md](lending.md) | +| Deposit to a backstop, queue/withdraw (Q4W), preview/claim backstop emissions; find the most profitable backstop | [backstop.md](backstop.md) | ## Architecture @@ -27,46 +29,51 @@ This playbook maps each Blend operation to the `contract invoke` (or `stellar to | **Pool** | One isolated lending market. Holds all reserves, user positions, and interest accounting. You interact with a pool almost entirely through its `submit` entrypoint. Each pool references one oracle and one backstop. | | **Reserve** | A single asset within a pool. Supply is tracked as **bTokens** (supply/collateral shares), debt as **dTokens** (liability shares); their value grows via `b_rate` / `d_rate` indexes. These are internal accounting units, not transferable SEP-41 tokens. | | **Pool Factory** | Deploys pools deterministically and records which pool addresses it created. The backstop trusts only factory-deployed pools. Enumerate pools from here — see [discovering pools](lending.md#discovering-pools). | -| **Backstop** | Holds first-loss capital per pool, denominated in the **backstop token** — a Comet (Balancer-style) 80/20 BLND:USDC LP token `[VERIFY weights]`. Backstop depositors absorb bad debt in exchange for a share of interest + BLND emissions. See [backstop.md](backstop.md). | +| **Backstop** | Holds first-loss capital per pool, denominated in the **backstop token** — a Comet (Balancer-style) 80/20 BLND:USDC LP token (confirm weights with the LP token's `get_normalized_weight`). Backstop depositors absorb bad debt in exchange for a share of interest + BLND emissions. See [backstop.md](backstop.md). | | **Emitter** | Streams BLND emissions to the backstop, distributed to pools in the reward zone. | | **Oracle** | A SEP-40-style price oracle the pool reads for asset prices. Address is per-pool (`PoolConfig.oracle`); read it, don't assume it. | ## Contract references -Only these three addresses per network are treated as fixed literals. **Everything else is derived on-chain** (RPC only, no indexer): the BLND token, the backstop LP token, each pool's oracle, and the pools themselves. +Only these three addresses per network are treated as fixed literals. **Everything else is derived on-chain**: the BLND token, the backstop LP token, each pool's oracle, and the pools themselves. | Role | Mainnet | Testnet | |------|---------|---------| -| Pool Factory | `CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB` | `CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6` | -| Backstop | `CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3` | `CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA` | -| Emitter | `CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K` | `CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6` | +| Pool Factory | `CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU` | `CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6` | +| Backstop | `CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7` | `CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA` | +| Emitter | `CCOQM6S7ICIUWA225O5PSJWUBEMXGFSSW2PQFO6FP4DQEKMS5DASRGRR` | `CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6` | -`[VERIFY]` The testnet deployment is Blend v2; the mainnet addresses above carry no version label in the registry — confirm the deployed version against Blend's published deployment before assuming v1 vs v2 semantics (the `RequestType` numbering and `submit` shape can differ between versions). +Both columns are the **Blend v2** deployment, from the [blend-utils registry](https://github.com/blend-capital/blend-utils) (`mainnet.contracts.json` / `testnet.contracts.json`) and the [Blend docs deployment list](https://docs.blend.capital/mainnet-deployments). A separate v1 deployment still exists on mainnet at different addresses (v1 backstop `CAO3AGAM…`, v1 pool factory `CCZD6ESM…`); this playbook does not cover it — if a user hands you a pool address, confirm it was deployed by the v2 factory before assuming v2 semantics. ### Derive the rest on-chain ```bash -# Set once. Blend runs on both networks; pick one. -POOL_FACTORY=CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB -BACKSTOP=CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3 -EMITTER=CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K +# Set once. Blend runs on both networks; pick one. (v2 mainnet addresses) +POOL_FACTORY=CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU +BACKSTOP=CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7 +EMITTER=CCOQM6S7ICIUWA225O5PSJWUBEMXGFSSW2PQFO6FP4DQEKMS5DASRGRR NETWORK=mainnet +ME=alice # any funded key alias — required even for reads (simulation only, nothing is signed) + +# Backstop LP token (Comet BLND:USDC) — from the Backstop +LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token | tr -d '"') -# BLND token address — from the Emitter [VERIFY getter name: get_blnd_token / blnd_id] -stellar contract invoke --id "$EMITTER" --network "$NETWORK" -- get_blnd_token +# BLND and USDC token addresses — the Comet LP token lists its underlying tokens. +# (The Emitter exposes no BLND getter; derive BLND from the LP token.) +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" -- get_tokens -# Backstop LP token (Comet BLND:USDC) — from the Backstop [VERIFY getter name] -stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +# Pool weights (e.g. confirm the 80/20 BLND:USDC split — returns "8000000" = 80%) +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" -- get_normalized_weight --token ``` ## Prerequisites -- The [wallet skill](../../wallet/SKILL.md) set up: a funded key, a configured RPC/network, and (for supply/repay) a trustline to any classic asset you deposit. +- The [wallet skill](../../wallet/SKILL.md) set up: a funded key, a configured RPC/network, and a trustline to any classic asset you supply, repay, **or borrow/withdraw** (receiving from the pool also requires the trustline). - `stellar-cli` installed. Confirm a contract's real interface before invoking: `stellar contract info interface --id `. ## Reading Blend state over RPC -All reads are `contract invoke` on a getter. The CLI simulates read-only calls and returns the decoded value — no source key or signing required, no indexer involved. Inspect the exact ABI first: +All reads are `contract invoke` on a getter. The CLI simulates read-only calls and returns the decoded value — nothing is signed or submitted, no indexer involved. Note `--source-account` is still **required** to build the simulation (any funded key works); only `contract info interface` runs without one. Inspect the exact ABI first: ```bash stellar contract info interface --id --network "$NETWORK" @@ -79,14 +86,6 @@ stellar contract info interface --id --network "$NET State-changing calls (`submit`, backstop `deposit`, etc.) go through `contract invoke ... --send=yes` signed by a wallet key. Token movements into the protocol (the asset you supply/repay) are ordinary SEP-41 / SAC transfers the contract pulls via authorization; the CLI collects and signs the required auth entries automatically during simulation. -- `contract invoke` — available today; the baseline for every Blend call. -- `stellar token` ([stellar-cli#2620](https://github.com/stellar/stellar-cli/issues/2620)) — the typed SEP-41/SAC client used for reading balances and moving the underlying assets; some examples note the `stellar token` form as the ergonomic path once shipped. -- Structured `{status, result, error}` output ([stellar-cli#2622](https://github.com/stellar/stellar-cli/issues/2622)) — the branchable receipt these examples read from once available. - -## Dependency notes - -This playbook is part of the Improve Agent UX for Wallets epic ([stellar-cli#2628](https://github.com/stellar/stellar-cli/issues/2628)) and composes with the wallet skill. Where a command relies on in-flight CLI surface (`stellar token`, structured output), it is noted inline; the underlying `contract invoke` path works today. - ## Related skills - Keys, signing, fees, submission, balances → [`../../wallet/SKILL.md`](../../wallet/SKILL.md) @@ -97,5 +96,4 @@ This playbook is part of the Improve Agent UX for Wallets epic ([stellar-cli#262 ## Documentation - [Blend docs](https://docs.blend.capital) -- [blend-contracts (source of truth for the ABI)](https://github.com/blend-capital/blend-contracts) -- [DeFi skill: Blend playbook (issue #2625)](https://github.com/stellar/stellar-cli/issues/2625) +- [blend-contracts-v2 (source of truth for the ABI)](https://github.com/blend-capital/blend-contracts-v2) diff --git a/skills/defi/blend/backstop.md b/skills/defi/blend/backstop.md index 2f7d164..006b1eb 100644 --- a/skills/defi/blend/backstop.md +++ b/skills/defi/blend/backstop.md @@ -1,11 +1,19 @@ # Blend backstop: deposit, Q4W, withdraw, claim -The backstop is Blend's insurance layer. Depositors stake the **backstop token** — a Comet 80/20 BLND:USDC LP token `[VERIFY weights]` — behind a specific pool. In return they earn a share of that pool's interest plus BLND emissions. In exchange, backstop capital is **first-loss**: it absorbs bad debt before lenders take a hit. That risk is why exits are time-locked (see [Q4W](#queue-for-withdrawal-q4w)). +## Contents + +- [Deposit](#deposit) +- [Queue for withdrawal (Q4W)](#queue-for-withdrawal-q4w) +- [Claim backstop emissions](#claim-backstop-emissions) +- [Reading backstop state](#reading-backstop-state) +- [Find the most profitable backstop](#find-the-most-profitable-backstop) (incl. `scripts/rank-backstops.sh`) + +The backstop is Blend's insurance layer. Depositors stake the **backstop token** — a Comet 80/20 BLND:USDC LP token (confirm weights with the LP token's `get_normalized_weight`) — behind a specific pool. In return they earn a share of that pool's interest plus BLND emissions. In exchange, backstop capital is **first-loss**: it absorbs bad debt before lenders take a hit. That risk is why exits are time-locked (see [Q4W](#queue-for-withdrawal-q4w)). Everything here targets the Backstop contract: ```bash -BACKSTOP=CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3 # mainnet +BACKSTOP=CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7 # mainnet (v2) NETWORK=mainnet ME=alice POOL= # the pool you are backstopping @@ -13,16 +21,20 @@ POOL= # the pool you are backstopping stellar contract info interface --id "$BACKSTOP" --network "$NETWORK" # confirm the ABI ``` -You deposit the **backstop token**, not BLND or USDC directly. Get its address from the backstop, then acquire/mint the LP token via its Comet pool before depositing: +You deposit the **backstop token**, not BLND or USDC directly. Get its address from the backstop, then mint LP tokens via the Comet pool — a single-sided join with USDC (or BLND) works and needs no second asset: ```bash -# [VERIFY getter name] -stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token | tr -d '"') + +# Single-sided join: deposit USDC into the Comet pool, receive LP tokens (returns the LP amount minted) +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=yes \ + -- dep_tokn_amt_in_get_lp_tokns_out \ + --token_in --token_amount_in 100000000 --min_pool_amount_out 0 --user "$ME" ``` ## Deposit -`[VERIFY signature]` — deposits backstop LP tokens behind a pool, minting backstop shares: +`deposit(from, pool_address, amount)` deposits backstop LP tokens behind a pool, minting backstop shares: ```bash stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ @@ -38,7 +50,7 @@ Shares represent your claim on that pool's backstop; their value rises as the ba Backstop deposits are **not** instantly withdrawable — this stops depositors from fleeing right before a liquidation. To exit you queue, wait out the timelock, then withdraw. **While queued, the funds stay at risk** (they still absorb bad debt) and keep accruing. -`[VERIFY]` timelock duration — believed to be **21 days** (confirm the `Q4W_LOCK_TIME` constant in [`blend-contracts`](https://github.com/blend-capital/blend-contracts)). +The v2 timelock is **17 days** (v1 was 21): `queue_withdrawal` returns a `Q4W` whose `exp` is exactly 17 days out. Withdrawing before `exp` fails simulation with contract error `#1001`. ```bash # Start the timelock on part of your position @@ -61,33 +73,35 @@ Check whether a queued entry has unlocked before calling `withdraw` — read the ## Claim backstop emissions -Claim accrued BLND across one or more pools you backstop `[VERIFY signature]`: +In v2, `claim(from, pool_addresses, min_lp_tokens_out)` does **not** pay BLND out to you — it converts the claimed BLND into backstop LP tokens and deposits them back behind the pools you claim from (auto-compound). `min_lp_tokens_out` is a slippage floor on that conversion; the call returns the LP tokens minted: ```bash stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=yes \ -- claim \ --from "$ME" \ --pool_addresses '["'"$POOL"'"]' \ - --to "$ME" + --min_lp_tokens_out "0" # set a real floor for meaningful amounts ``` +To preview what's claimable without submitting, simulate the same call with `--send=no` — the returned value is the LP tokens the claim would mint (use it to set a sensible `min_lp_tokens_out`). + ## Reading backstop state -All reads simulate; no signing, no indexer. `[VERIFY]` getter names against source: +All reads simulate; no signing, no indexer. ```bash -# Total backstop size for a pool: shares, LP tokens, and queued-for-withdrawal amount -stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- pool_balance --pool_address "$POOL" +# Total backstop data for a pool: shares, LP tokens, queued %, and LP spot price +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- pool_data --pool "$POOL" # Your balance for a pool: shares + your Q4W entries (each with amount + expiration) -stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" \ - -- user_balance --pool_address "$POOL" --user "$(stellar keys address "$ME")" +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" \ + -- user_balance --pool "$POOL" --user "$(stellar keys address "$ME")" # The backstop LP token address (Comet BLND:USDC) -stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token ``` -- **`PoolBalance`** — `shares`, `tokens` (LP tokens deposited), `q4w` (queued amount). +- **`PoolBackstopData`** — `shares`, `tokens` (LP tokens deposited), `q4w_pct` (fraction queued for withdrawal), `blnd` / `usdc` (underlying amounts), `token_spot_price` (LP token spot price — useful for USD conversion). - **`UserBalance`** — `shares`, `q4w: Vec` where `exp` is the unlock ledger timestamp. ## Find the most profitable backstop @@ -95,7 +109,7 @@ stellar contract invoke --id "$BACKSTOP" --network "$NETWORK" -- backstop_token Backstop yield for a pool comes from two streams, both measured against the pool's total backstop deposit value: 1. **Interest share** — the pool routes a fraction of borrower interest (`PoolConfig.bstop_rate`) to the backstop. Scales with borrow volume × borrow APR × `bstop_rate`. -2. **BLND emissions** — the Emitter streams BLND to reward-zone pools, split by backstop size and (within a pool) between the backstop and reserve suppliers/borrowers. `[VERIFY]` the exact split and reward-zone rules — v1 and v2 differ; do not hardcode percentages. +2. **BLND emissions** — the Emitter streams BLND to the backstop, which distributes it across reward-zone pools in proportion to non-queued backstop size. Within a pool's allocation, **70% goes to backstop depositors and 30% to pool users**. Approximate backstop APR: @@ -103,43 +117,17 @@ Approximate backstop APR: APR ≈ (annualized_interest_share_USD + annualized_emissions_USD) / backstop_deposit_value_USD ``` -The following script ranks pools by an approximate backstop APR using **RPC only**. It is a scaffold: it reads the real on-chain inputs and shows where each number comes from; the final APR math carries `[VERIFY]` flags because the emission split and LP-token valuation must be confirmed against source before trusting the ranking for real capital. +Your own projected earnings are pro-rata: `(your user_balance.shares / pool_data.shares) × the pool-level totals above`. For already-accrued (unclaimed) emissions, simulate `claim` with `--send=no` — see [claiming](#claim-backstop-emissions). + +The bundled script [`scripts/rank-backstops.sh`](scripts/rank-backstops.sh) ranks pools by an approximate backstop APR using **RPC only**. It is a scaffold: it reads the real on-chain inputs and shows where each number comes from (its comments walk through each step); finishing the APR still requires the per-reserve reads sketched in the script's step 4. Requires stellar-cli, jq, and a funded key alias. Run it: ```bash -#!/usr/bin/env bash -# rank-backstops.sh — approximate backstop APR per Blend pool, RPC only. -# Requires: stellar-cli, jq. Usage: NETWORK=mainnet ./rank-backstops.sh -set -euo pipefail - -NETWORK=${NETWORK:-mainnet} -BACKSTOP=${BACKSTOP:-CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3} - -invoke() { stellar contract invoke --id "$1" --network "$NETWORK" -- "${@:2}"; } - -# 1. Get the candidate pools. The reward zone is the practical source of active, -# emission-eligible pools. [VERIFY getter name: reward_zone / get_rz] -pools=$(invoke "$BACKSTOP" reward_zone | jq -r '.[]') - -echo "pool,backstop_tokens,bstop_rate,note" -for POOL in $pools; do - # 2. Backstop size (APR denominator): LP tokens deposited for this pool. - bal=$(invoke "$BACKSTOP" pool_balance --pool_address "$POOL") - tokens=$(echo "$bal" | jq -r '.tokens // .["1"] // "0"') # [VERIFY field name] - - # 3. Interest-share input: the pool's backstop take rate. - cfg=$(invoke "$POOL" get_config) - bstop_rate=$(echo "$cfg" | jq -r '.bstop_rate // "0"') # [VERIFY field name] - - # 4. To finish the APR you still need, per pool: - # - sum over reserves of (d_supply * d_rate * borrow_rate) * bstop_rate -> interest to backstop - # - BLND emitted/sec allocated to this pool's backstop (from emission config) -> annualized - # - LP-token -> USD via the Comet pool reserves + oracle prices - # These require reading get_reserve per asset and the emission config. [VERIFY] - echo "$POOL,$tokens,$bstop_rate,partial-see-VERIFY" -done +NETWORK=mainnet SOURCE=alice scripts/rank-backstops.sh ``` -To complete the APR, add per-pool reserve reads (`get_reserve --asset ` for `d_supply`, `d_rate`, and the current borrow rate — see [lending.md](lending.md#reading-pool-state)), the emission-config read for BLND/sec to the backstop, and an LP-token-to-USD conversion (Comet reserve balances + BLND/USDC oracle prices). Rank pools by the resulting APR. Every dynamic input above is read over RPC — no indexer. +Output is CSV: `pool,backstop_tokens,bstop_rate,note`, one row per reward-zone pool. + +To complete the APR, add per-pool reserve reads (`get_reserve --asset ` for `d_supply`, `d_rate`, and the current borrow rate — see [lending.md](lending.md#reading-pool-state)), the pool-side emission reads (`get_reserve_emissions` per reserve side, scaled by 70/30 to get the backstop's BLND/sec — see [projected earnings](lending.md#projected-earnings)), and an LP-token-to-USD conversion (`token_spot_price` from `pool_data`). Rank pools by the resulting APR. ## Related diff --git a/skills/defi/blend/lending.md b/skills/defi/blend/lending.md index 622016e..0567396 100644 --- a/skills/defi/blend/lending.md +++ b/skills/defi/blend/lending.md @@ -1,5 +1,18 @@ # Blend lending: supply, borrow, repay, withdraw +## Contents + +- [The `submit` model](#the-submit-model) (incl. the `RequestType` table) +- [Supply collateral](#supply-collateral) +- [Borrow](#borrow) +- [Repay](#repay) +- [Withdraw collateral](#withdraw-collateral) +- [Batching requests atomically](#batching-requests-atomically) +- [Discovering pools](#discovering-pools) +- [Reading pool state](#reading-pool-state) +- [Emissions: check, project, claim](#emissions-check-project-claim) +- [Projected earnings](#projected-earnings) + Every lending action on a Blend pool goes through a single entrypoint: **`submit`**. You describe what you want as a list of `Request`s, and the pool executes them atomically against your position. Supplying, borrowing, repaying, and withdrawing are all just different `RequestType`s in that list. Read the pool's real interface before you build anything: @@ -10,7 +23,7 @@ stellar contract info interface --id --network "$NETWORK" ## The `submit` model -`[VERIFY]` signature against [`blend-contracts`](https://github.com/blend-capital/blend-contracts); arg order/names are from the contract design: +Signature (v2 pools also expose `submit_with_allowance` and `flash_loan` variants): ``` submit(from: Address, spender: Address, to: Address, requests: Vec) -> Positions @@ -32,7 +45,7 @@ Request { request_type: u32, address: Address, amount: i128 } ### RequestType -`[VERIFY]` numbering against source — the Supply/SupplyCollateral split is a v2 feature and v1 may number differently: +This numbering is shared by Blend v1 and v2 (including the Supply/SupplyCollateral split) — confirm against [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2) if in doubt: | Value | Name | Action | |-------|------|--------| @@ -69,7 +82,7 @@ The pool pulls the asset from `spender` via authorization; the CLI collects and ## Borrow -You can only borrow up to your collateral's borrowing power (collateral value × collateral factor). Borrow sends the asset to `to`: +You can only borrow up to your collateral's borrowing power (collateral value × collateral factor); exceeding it fails simulation with contract error `#1205`. Borrow sends the asset to `to` — if the asset is a classic-asset SAC (e.g. USDC), `to` needs a **trustline** first (`stellar tx new change-trust --line `; get the `CODE:ISSUER` string from the token's `name()`): ```bash stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ @@ -80,7 +93,7 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= ## Repay -Repay pulls the asset from `spender`. To fully repay, pass an `amount` larger than the debt — Blend caps it at the outstanding balance `[VERIFY over-repay behavior]`; prefer reading the exact liability from [positions](#reading-pool-state) first: +Repay pulls the asset from `spender`. To fully repay, pass an `amount` larger than the debt — the pool pulls the full amount and refunds the excess to `spender` in the same transaction, so `spender` needs a balance covering the full amount you pass: ```bash stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ @@ -98,7 +111,7 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= --requests '[{"request_type":3,"address":"'"$ASSET"'","amount":"1000000000"}]' ``` -Withdrawal is blocked if it would drop your position below the required collateralization while you hold debt. +Withdrawal is blocked if it would drop your position below the required collateralization while you hold debt. Passing an amount larger than your balance withdraws the full balance — so to fully exit a reserve, pass an oversized amount. ## Batching requests atomically @@ -119,51 +132,78 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= ## Discovering pools -Pools are created by the Pool Factory; enumerate them over RPC (no indexer). The factory records deployed pools — inspect its interface for the exact getter, then read the list: +Pools are created by the Pool Factory; enumerate them over RPC. The factory records deployed pools — inspect its interface for the exact getter, then read the list: ```bash -POOL_FACTORY=CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB # mainnet +POOL_FACTORY=CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU # mainnet (v2) stellar contract info interface --id "$POOL_FACTORY" --network "$NETWORK" -# The factory exposes an is-pool check; some deployments also expose a list. -# [VERIFY getter names against source: e.g. is_pool(address) / deployed pools event history] +# The v2 factory exposes only deploy(...) and is_pool(pool_address) -> bool — no list getter. +stellar contract invoke --id "$POOL_FACTORY" --source "$ME" --network "$NETWORK" -- is_pool --pool_address ``` -`[VERIFY]` The Pool Factory tracks deployment via `deploy` events and an `is_pool` check rather than always exposing a full list getter. Where no list getter exists, enumerate pool-creation events from RPC (`getEvents` on the factory contract) — still RPC-only, no indexer. See the [data skill](../../data/SKILL.md) for the `getEvents` pattern. The [backstop reward zone](backstop.md#reading-backstop-state) is also a practical source of active pool addresses. +Since there is no list getter, enumerate pool-creation events from RPC (`getEvents` on the factory contract) — still RPC-only, no indexer. See the [data skill](../../data/SKILL.md) for the `getEvents` pattern. The [backstop reward zone](backstop.md#reading-backstop-state) is also a practical source of active pool addresses. ## Reading pool state -All reads simulate; no signing. Inspect the interface first (`stellar contract info interface --id "$POOL"`), then call the getters. `[VERIFY]` getter names below: +All reads simulate; nothing is signed or submitted, but the CLI still requires `--source-account` (any funded key) to build the simulation. ```bash # Pool config: oracle, backstop take rate, status, max positions -stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_config +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_config # Reserve list: the asset addresses this pool supports -stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_reserve_list +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_reserve_list -# One reserve's config + live data (rates, totals, utilization) -stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_reserve --asset "$ASSET" +# One reserve: Reserve { asset, config: ReserveConfig, data: ReserveData, scalar } +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_reserve --asset "$ASSET" # A user's positions: collateral / liabilities / supply, keyed by reserve index -stellar contract invoke --id "$POOL" --network "$NETWORK" -- get_positions --user "$(stellar keys address "$ME")" +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_positions --address "$(stellar keys address "$ME")" ``` Key fields you will read: -- **`PoolConfig`** — `oracle` (price source), `bstop_rate` (fraction of interest routed to the backstop), `status`, `max_positions`. -- **`ReserveConfig`** — `decimals`, `c_factor` (collateral factor), `l_factor` (liability factor), the kinked interest-rate curve (`r_base`, `r_one`, `r_two`, `r_three`, `util`), `supply_cap` (v2), `enabled`. +- **`PoolConfig`** — `oracle` (price source), `bstop_rate` (fraction of interest routed to the backstop), `status`, `max_positions`, `min_collateral`. +- **`ReserveConfig`** — `decimals`, `c_factor` (collateral factor), `l_factor` (liability factor), the kinked interest-rate curve (`r_base`, `r_one`, `r_two`, `r_three`, `util`, `max_util`, `reactivity`), `supply_cap`, `enabled`, `index`. - **`ReserveData`** — `b_rate` / `d_rate` (supply/borrow indexes), `b_supply` / `d_supply` (total b/d tokens), `ir_mod`, `backstop_credit`, `last_time`. - **`Positions`** — `collateral`, `liabilities`, `supply` maps keyed by reserve index. -Convert shares to underlying with the rate index: underlying supply ≈ `bToken_balance × b_rate`; underlying debt ≈ `dToken_balance × d_rate` `[VERIFY scaling/decimals]`. +Convert shares to underlying with the rate index: underlying supply = `bToken_balance × b_rate / 1e12`; underlying debt = `dToken_balance × d_rate / 1e12`. `b_rate` / `d_rate` are 12-decimal fixed-point (`SCALAR_12`); the result is in the asset's native decimals. + +## Emissions: check, project, claim + +Suppliers and borrowers accrue BLND emissions per reserve **side**. Each reserve has two emission streams, identified by a `reserve_token_id`: + +- **borrow side** (dTokens / liabilities): `reserve_index × 2` +- **supply side** (bTokens / supply + collateral): `reserve_index × 2 + 1` + +`reserve_index` is `ReserveConfig.index` from `get_reserve`. Not every stream is funded — `get_reserve_emissions` returns `None` (void) for streams with no emissions configured. + +### Check accrued emissions + +```bash +# The stream itself: eps (BLND/sec — 14 total decimals: BLND's 7 + a 7-decimal scalar), index, expiration +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" \ + -- get_reserve_emissions --reserve_token_index 1 + +# Your accrued-but-unclaimed BLND on that stream: UserEmissionData { accrued, index } +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" \ + -- get_user_emissions --user "$(stellar keys address "$ME")" --reserve_token_index 1 +``` + +`accrued` only updates when your position is touched, so BLND earned since your last interaction isn't in it. For the exact claimable total, **simulate the claim**: run it with `--send=no` and read the returned amount without submitting anything: + +```bash +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no \ + -- claim --from "$ME" --reserve_token_ids '[0,1]' --to "$ME" +``` -## Claim lender emissions +### Claim -Suppliers and borrowers accrue BLND emissions, claimed from the pool `[VERIFY signature]`: +Same call submitted for real — `claim(from, reserve_token_ids, to)` transfers the BLND to `to`: ```bash -# reserve_token_ids identify which reserve emission streams to claim (supply/borrow sides) stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ -- claim \ --from "$ME" \ @@ -172,3 +212,42 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= ``` Backstop emissions are separate — see [backstop.md](backstop.md#claim-backstop-emissions). + +## Projected earnings + +A position earns (or costs) from two streams; every input below is read over RPC via `get_reserve`, `get_config`, and the emission getters. + +**1. Interest.** Borrowers pay the reserve's borrow rate; the backstop takes a `bstop_rate` cut and suppliers split the rest, so: + +``` +utilization = (d_supply × d_rate) / (b_supply × b_rate) # underlying borrowed / supplied +supply_rate ≈ borrow_rate × utilization × (1 − bstop_rate) # bstop_rate is 7-decimal fixed point +``` + +`borrow_rate` comes from the reserve's kinked curve — `r_base` plus the `r_one`/`r_two`/`r_three` slopes around target `util`, scaled by the live `ir_mod` modifier (see `pool/src/pool/interest.rs` in [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2)). Projected yearly interest ≈ `your_supplied_underlying × supply_rate` (or `your_debt × borrow_rate` on the cost side). + +**2. BLND emissions.** Your share of a stream is proportional to your b/dTokens over the side's total: + +``` +your_blnd_per_year = (eps / 1e14) × 31,536,000 × (your_b_or_d_tokens / total_b_or_d_supply) +``` + +The `1e14` is BLND's 7 decimals plus an extra 7-decimal scalar baked into `eps`; the result is whole BLND. + +Check the stream's `expiration` — emissions stop when it lapses (streams refresh via `gulp_emissions` while the pool is in the [reward zone](backstop.md#reading-backstop-state)). Note only 30% of a pool's emission allocation flows to pool users, split across reserve sides by pool config; the other 70% goes to backstop depositors. + +To express BLND earnings in USD, read the BLND price from the Comet LP (pool oracles list only the pool's reserve assets, so they usually can't price BLND). `LP_TOKEN`, `BLND`, and `USDC` come from [deriving contracts on-chain](SKILL.md#derive-the-rest-on-chain): + +```bash +# BLND price in USDC — 7-decimal fixed point, e.g. "3008070" = 0.30 USDC per BLND +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" \ + -- get_spot_price --token_in "$USDC" --token_out "$BLND" +``` + +To price a reserve asset in USD (the interest side), use the pool's oracle (`PoolConfig.oracle` from `get_config`, SEP-40). The `Asset` argument is an enum — pass it as JSON: + +```bash +# Returns PriceData { price, timestamp }; scale price by the oracle's decimals() (typically 7) +stellar contract invoke --id "$ORACLE" --source "$ME" --network "$NETWORK" \ + -- lastprice --asset '{"Stellar":"'"$ASSET"'"}' +``` diff --git a/skills/defi/blend/scripts/rank-backstops.sh b/skills/defi/blend/scripts/rank-backstops.sh new file mode 100755 index 0000000..7acbd67 --- /dev/null +++ b/skills/defi/blend/scripts/rank-backstops.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# rank-backstops.sh — approximate backstop APR per Blend pool, RPC only. +# Requires: stellar-cli, jq, a funded key alias (reads simulate but need a source account). +# Usage: NETWORK=mainnet SOURCE=alice ./rank-backstops.sh +set -euo pipefail + +NETWORK=${NETWORK:-mainnet} +BACKSTOP=${BACKSTOP:-CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7} +SOURCE=${SOURCE:?set SOURCE to a funded key alias} + +invoke() { stellar contract invoke --id "$1" --source "$SOURCE" --network "$NETWORK" -- "${@:2}" 2>/dev/null; } + +# 1. Get the candidate pools. The reward zone is the practical source of active, +# emission-eligible pools. +pools=$(invoke "$BACKSTOP" reward_zone | jq -r '.[]') + +echo "pool,backstop_tokens,bstop_rate,note" +for POOL in $pools; do + # 2. Backstop size (APR denominator): LP tokens deposited for this pool. + # pool_data also returns token_spot_price for LP -> USD conversion. + bal=$(invoke "$BACKSTOP" pool_data --pool "$POOL") + tokens=$(echo "$bal" | jq -r '.tokens // "0"') + + # 3. Interest-share input: the pool's backstop take rate. + cfg=$(invoke "$POOL" get_config) + bstop_rate=$(echo "$cfg" | jq -r '.bstop_rate // "0"') + + # 4. To finish the APR you still need, per pool: + # - sum over reserves of (d_supply * d_rate * borrow_rate) * bstop_rate -> interest to backstop + # - backstop BLND/sec: no direct getter, but pool-side streams are readable — + # sum get_reserve_emissions eps over the pool's reserve sides, then + # backstop_eps = pool_eps_sum * 70/30 + # - LP-token -> USD: token_spot_price from pool_data + echo "$POOL,$tokens,$bstop_rate,partial" +done From f25469cf7bf738101445afbfcfd7709b0b3a36e4 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Fri, 10 Jul 2026 14:00:11 -0600 Subject: [PATCH 5/6] Remove Blend playbook design spec from repo --- .../2026-07-08-blend-defi-playbook-design.md | 115 ------------------ 1 file changed, 115 deletions(-) delete mode 100644 docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md diff --git a/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md b/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md deleted file mode 100644 index 6e761a3..0000000 --- a/docs/superpowers/specs/2026-07-08-blend-defi-playbook-design.md +++ /dev/null @@ -1,115 +0,0 @@ -# Blend DeFi Playbook Skill — Design - -**Date:** 2026-07-08 -**Issue:** [stellar/stellar-cli#2625](https://github.com/stellar/stellar-cli/issues/2625) — "DeFi skill: Blend playbook" -**Branch:** `feat/defi-blend-skill` (branched from `feat/wallet-skills-cli`) -**Epic:** Improve Agent UX for Wallets ([stellar-cli#2628](https://github.com/stellar/stellar-cli/issues/2628)) - -## Goal - -Add a playbook skill that lets an AI agent interact with the **Blend** lending -protocol on Stellar/Soroban using existing CLI commands (`contract invoke`, -`stellar token`) composed with the wallet skill. The playbook bridges raw ledger -data and protocol architecture: an agent should be able to locate Blend pools, -read live pool/backstop state over RPC, and construct the core lending and -backstop transactions. - -### Acceptance criteria (from the issue) - -1. The playbook must let agents locate Blend pools and execute core - lending/backstop transactions using `stellar token` / `contract invoke` + - wallet skills. -2. All dynamic-data scripts must operate using **RPC calls only** — no indexer. - -## Decisions - -- **Structure:** Blend is its own package under a DeFi namespace. A thin - `skills/defi/SKILL.md` index is added so the wallet skill's existing - `../defi/SKILL.md` forward-link resolves and so future protocols have a home. -- **Verification:** Documented from architecture knowledge. Function signatures, - the `RequestType` enum numbering, the Q4W timelock duration, and the emission - split carry explicit `[VERIFY]` flags pointing at the `blend-contracts` / - `blend-contracts-v2` source. Scripts are structurally correct RPC recipes to be - confirmed against live contracts before signing a mainnet transaction. -- **Networks:** Mainnet and testnet get equal-weight treatment; addresses are - tabled per network. - -## File layout - -``` -skills/defi/ - SKILL.md # DeFi index/router; wallet<->protocol boundary; links to blend/ - blend/ - SKILL.md # Blend entry: architecture, contract refs, routing, deps, [VERIFY] notes - lending.md # supply / withdraw / borrow / repay; reading reserves & positions - backstop.md # deposit / Q4W / withdraw / claim; backstop APR + ranking script -``` - -## Contract references - -Only three addresses per network are authoritative from the Stellar protocol -registry; everything else is derived on-chain (RPC-only, satisfying criterion 2). - -| Role | Mainnet | Testnet (v2) | -|------|---------|--------------| -| Pool Factory | `CBP7NO6F7FRDHSOFQBT2L2UWYIZ2PU76JKVRYAQTG3KZSQLYAOKIF2WB` | `CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6` | -| Backstop | `CAO3AGAMZVRMHITL36EJ2VZQWKYRPWMQAPDQD5YEOF3GIF7T44U4JAL3` | `CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA` | -| Emitter | `CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K` | `CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6` | - -Derived on-chain (not hardcoded): -- **BLND token** — from the Emitter getter. -- **Backstop token** (Comet 80/20 BLND:USDC LP) — from `Backstop.backstop_token()`. -- **Oracle** — per-pool, from `PoolConfig.oracle`. -- **Individual pools** — enumerated from the Pool Factory. - -## Component detail - -### `skills/defi/SKILL.md` (index) -~15 lines. What DeFi skills cover, the wallet-primitives vs protocol-logic -boundary (mirrors the wallet skill's "Working with Soroban protocols" section), -and a link into `blend/`. - -### `skills/defi/blend/SKILL.md` (entry) -- **Architecture:** pools, reserves (bToken/dToken accounting), backstop module, - BLND emissions, emitter, oracle, pool factory. -- **Contract references:** the table above; derive-on-chain getters for the rest. -- **Routing:** lend/borrow → `lending.md`; earn on backstop → `backstop.md`. -- **Dependency notes:** `contract invoke` works today; `stellar token` (#2620) - and structured output (#2622) are the ergonomic path once shipped; keys, - signing, and submission defer to the wallet skill (`../../wallet/`). -- **Scope/`[VERIFY]` banner** stated prominently near the top. - -### `skills/defi/blend/lending.md` -- Pool `submit(from, spender, to, requests)` model. `[VERIFY]` arg order. -- **RequestType table:** Supply 0, Withdraw 1, SupplyCollateral 2, - WithdrawCollateral 3, Borrow 4, Repay 5, plus auction fill types. `[VERIFY]` - against source; note V1/V2 numbering may differ. -- Worked `contract invoke` examples (both networks): supply-collateral, borrow, - repay, withdraw; batching multiple `Request`s atomically. -- Reading reserve list / reserve data / pool config / user positions via RPC - (`simulateTransaction` on read getters). Claiming lender emissions. - -### `skills/defi/blend/backstop.md` -- `deposit`, `queue_withdrawal` (Q4W), `dequeue_withdrawal`, `withdraw`, `claim`. -- Q4W timelock mechanics and duration (`[VERIFY]` — 21 days believed; confirm - `Q4W_LOCK_TIME`). Funds remain at risk while queued. -- **Dynamic-data scripts (embedded bash, RPC-only):** - 1. **List available pools** — enumerate factory-created pools from the Pool - Factory. - 2. **Rank backstop opportunities** — per pool read backstop size + - `bstop_rate` interest share + BLND emissions, compute an approximate - backstop APR, rank. Assumptions/`[VERIFY]` inline. - -## Site registration - -- `site/src/data/skills.ts`: add `"DeFi"` to `FilterType` + `FILTERS`; add a - `SKILL_CARD_SOURCES` entry pointing at `skills/defi/blend/SKILL.md`. -- `site/src/app/styles.scss`: add `DeFi` to the `@each` filter whitelist so the - card renders when the filter is selected (same wiring the wallet card needed). - -## Out of scope - -- Live on-chain verification of signatures/enums (deferred by the - document-from-knowledge decision; `[VERIFY]` flags mark what to confirm). -- Protocols other than Blend (the DeFi index is structured to add them later). -- Liquidation/auction bot logic beyond documenting the relevant RequestTypes. From 00d46d18b6b13ba1e76c07afcc2c47d3808609e3 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 13 Jul 2026 09:53:33 -0600 Subject: [PATCH 6/6] Fold mainnet stress-test errata into the Blend skill Addresses the agent stress-test feedback on #55: repay full-close recipe (dToken round-up, #10/#1205 failure modes, buffer-first), min_collateral semantics (#1224, c_factor-adjusted, oracle decimals), --send=no on all reads + archived-state RestoreFootprint caveat, reward_zone() as the pool-discovery method (factory events are past RPC retention), the borrow-rate curve with exact scales (ir_mod 7dp [0.1,10], status enum, q4w_pct 7dp, get_tokens order), BLND trustline prerequisite for claims (#13), getting assets from XLM (borrow or path payment; two testnet USDCs), tx-new-vs-invoke submit semantics, resource-fee and timeout remediation notes, testnet-first env blocks, and a verified public mainnet RPC endpoint list in the standards skill (the CLI's built-in mainnet alias has no RPC URL). rank-backstops.sh now derives the backstop per network and fails loudly instead of printing nothing off-mainnet; verified against testnet and mainnet. --- skills/defi/SKILL.md | 4 +- skills/defi/blend/SKILL.md | 62 +++++++++++--- skills/defi/blend/backstop.md | 38 ++++----- skills/defi/blend/lending.md | 90 +++++++++++++++------ skills/defi/blend/scripts/rank-backstops.sh | 26 ++++-- skills/standards/SKILL.md | 18 +++++ 6 files changed, 178 insertions(+), 60 deletions(-) diff --git a/skills/defi/SKILL.md b/skills/defi/SKILL.md index 065b0f5..b90f683 100644 --- a/skills/defi/SKILL.md +++ b/skills/defi/SKILL.md @@ -1,6 +1,6 @@ --- name: defi -description: Interact with Stellar/Soroban DeFi protocols from the CLI: discover pools and markets, read live protocol state over RPC, and compose the transactions that act on them (lend, borrow, provide backstop/liquidity). Per-protocol playbooks map each protocol's operations to concrete `contract invoke` / `stellar token` calls. Use when acting on a Soroban DeFi protocol rather than moving value between accounts. +description: Interact with Stellar/Soroban DeFi protocols from the CLI: discover pools and markets, read live protocol state over RPC, and compose the transactions that act on them (lend, borrow, provide backstop/liquidity). Per-protocol playbooks map each protocol's operations to concrete `contract invoke` calls. Use when acting on a Soroban DeFi protocol rather than moving value between accounts. user-invocable: true argument-hint: "[defi task]" --- @@ -9,7 +9,7 @@ argument-hint: "[defi task]" The [wallet skill](../wallet/SKILL.md) covers wallet **primitives** — moving value, trustlines, transaction mechanics. It deliberately does *not* teach how any specific Soroban protocol works. -These DeFi playbooks pick up where the wallet skill stops. Each one maps a protocol's operations to concrete `contract invoke` / `stellar token` calls, carries the current contract references, and shows how to read the protocol's live state over RPC — so you act on the protocol instead of reverse-engineering it from raw ledger data. +These DeFi playbooks pick up where the wallet skill stops. Each one maps a protocol's operations to concrete `contract invoke` calls, carries the current contract references, and shows how to read the protocol's live state over RPC — so you act on the protocol instead of reverse-engineering it from raw ledger data. The division of labor is constant: diff --git a/skills/defi/blend/SKILL.md b/skills/defi/blend/SKILL.md index 65b23a1..5178870 100644 --- a/skills/defi/blend/SKILL.md +++ b/skills/defi/blend/SKILL.md @@ -11,7 +11,7 @@ Blend is a permissionless, over-collateralized lending protocol on Soroban. Anyo This playbook covers **Blend v2** — the current deployment on both mainnet and testnet. All addresses, signatures, and semantics below are v2; the legacy v1 mainnet deployment (different addresses, different backstop `claim` shape, 21-day Q4W) is out of scope. -This playbook maps each Blend operation to the `contract invoke` (or `stellar token`) call that performs it, and shows how to read live pool/backstop state over RPC. Keys, signing, fees, and submission are handled by the [wallet skill](../../wallet/SKILL.md) — every transaction below is signed and submitted with that pipeline. +This playbook maps each Blend operation to the `contract invoke` call that performs it, and shows how to read live pool/backstop state over RPC. Keys, signing, fees, and submission are handled by the [wallet skill](../../wallet/SKILL.md) — every transaction below is signed and submitted with that pipeline. > **Scope.** This playbook targets **Blend v2**. Confirm the deployed interface with `stellar contract info interface --id ` — see [reading state](#reading-blend-state-over-rpc). The addresses below are the **Blend v2** deployments on both networks; the older v1 mainnet deployment uses different addresses and its ABI can differ. @@ -28,7 +28,7 @@ This playbook maps each Blend operation to the `contract invoke` (or `stellar to |--------|------------| | **Pool** | One isolated lending market. Holds all reserves, user positions, and interest accounting. You interact with a pool almost entirely through its `submit` entrypoint. Each pool references one oracle and one backstop. | | **Reserve** | A single asset within a pool. Supply is tracked as **bTokens** (supply/collateral shares), debt as **dTokens** (liability shares); their value grows via `b_rate` / `d_rate` indexes. These are internal accounting units, not transferable SEP-41 tokens. | -| **Pool Factory** | Deploys pools deterministically and records which pool addresses it created. The backstop trusts only factory-deployed pools. Enumerate pools from here — see [discovering pools](lending.md#discovering-pools). | +| **Pool Factory** | Deploys pools deterministically and records which pool addresses it created (`is_pool`). The backstop trusts only factory-deployed pools. To enumerate pools, use the backstop's reward zone — see [discovering pools](lending.md#discovering-pools). | | **Backstop** | Holds first-loss capital per pool, denominated in the **backstop token** — a Comet (Balancer-style) 80/20 BLND:USDC LP token (confirm weights with the LP token's `get_normalized_weight`). Backstop depositors absorb bad debt in exchange for a share of interest + BLND emissions. See [backstop.md](backstop.md). | | **Emitter** | Streams BLND emissions to the backstop, distributed to pools in the reward zone. | | **Oracle** | A SEP-40-style price oracle the pool reads for asset prices. Address is per-pool (`PoolConfig.oracle`); read it, don't assume it. | @@ -45,35 +45,71 @@ Only these three addresses per network are treated as fixed literals. **Everythi Both columns are the **Blend v2** deployment, from the [blend-utils registry](https://github.com/blend-capital/blend-utils) (`mainnet.contracts.json` / `testnet.contracts.json`) and the [Blend docs deployment list](https://docs.blend.capital/mainnet-deployments). A separate v1 deployment still exists on mainnet at different addresses (v1 backstop `CAO3AGAM…`, v1 pool factory `CCZD6ESM…`); this playbook does not cover it — if a user hands you a pool address, confirm it was deployed by the v2 factory before assuming v2 semantics. -### Derive the rest on-chain +### Network setup + +Set the three literals once. **Testnet works out of the box** (the CLI ships a testnet RPC): + +```bash +# Testnet (v2) +POOL_FACTORY=CDV6RX4CGPCOKGTBFS52V3LMWQGZN3LCQTXF5RVPOOCG4XVMHXQ4NTF6 +BACKSTOP=CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA +EMITTER=CC3WJVJINN4E3LPMNTWKK7LQZLYDQMZHZA7EZGXATPHHBPKNZRIO3KZ6 +NETWORK=testnet +ME=alice # any funded key alias — required even for reads (they only build a simulation) +``` + +**Mainnet does not work out of the box**: the CLI's built-in `mainnet` network is a placeholder with **no RPC URL** — every command fails until you add one. Pick a public endpoint from the [standards skill's mainnet RPC list](../../standards/SKILL.md#public-mainnet-rpc-endpoints) and register it first: ```bash -# Set once. Blend runs on both networks; pick one. (v2 mainnet addresses) +# Mainnet (v2) — add an RPC endpoint first; the built-in `mainnet` alias has none +stellar network add mainnet \ + --rpc-url \ + --network-passphrase "Public Global Stellar Network ; September 2015" + POOL_FACTORY=CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU BACKSTOP=CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7 EMITTER=CCOQM6S7ICIUWA225O5PSJWUBEMXGFSSW2PQFO6FP4DQEKMS5DASRGRR NETWORK=mainnet -ME=alice # any funded key alias — required even for reads (simulation only, nothing is signed) +ME=alice +``` +Every example below passes `--network "$NETWORK"` explicitly — keep that habit. A read without `--network` silently queries whatever network the command defaults to, and a funded account "looks empty" because you're asking the wrong chain. + +### Derive the rest on-chain + +```bash # Backstop LP token (Comet BLND:USDC) — from the Backstop -LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token | tr -d '"') +LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no -- backstop_token | tr -d '"') -# BLND and USDC token addresses — the Comet LP token lists its underlying tokens. +# BLND and USDC token addresses — the Comet LP token lists its underlying tokens, +# returned in weight order: [BLND, USDC]. # (The Emitter exposes no BLND getter; derive BLND from the LP token.) -stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" -- get_tokens +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=no -- get_tokens # Pool weights (e.g. confirm the 80/20 BLND:USDC split — returns "8000000" = 80%) -stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" -- get_normalized_weight --token +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=no -- get_normalized_weight --token ``` ## Prerequisites - The [wallet skill](../../wallet/SKILL.md) set up: a funded key, a configured RPC/network, and a trustline to any classic asset you supply, repay, **or borrow/withdraw** (receiving from the pool also requires the trustline). +- A **BLND trustline before any emissions `claim`** — even the simulated preview reverts with `#13` (SAC `TrustlineMissingError`) without it. Derive BLND from the LP token (`backstop_token` → `get_tokens`, first entry), read its `CODE:ISSUER` with `name()`, then `stellar tx new change-trust --line `. - `stellar-cli` installed. Confirm a contract's real interface before invoking: `stellar contract info interface --id `. +### Getting the assets these flows assume + +Blend flows assume a balance of a reserve asset (USDC, etc.) or of BLND/USDC for the backstop — but Friendbot funds only XLM, and issuer `mint` is gated. Starting from XLM you have two routes: + +- **Borrow it**: supply XLM as collateral, then borrow the asset you need — see [lending.md](lending.md). (This is also the practical way to fund a backstop position from XLM.) +- **Swap for it on the DEX**: `stellar tx new path-payment-strict-receive` (or `-strict-send`) — see the [wallet skill](../../wallet/SKILL.md). + +Two gotchas: XLM's SAC address comes from `stellar contract id asset --asset native` (`contract invoke --id native` does not work). And "USDC" is not one asset — testnet's two Blend pools use **two different USDC issuers**, one of which has no DEX liquidity. Read the token's `name()` (returns `CODE:ISSUER`) before assuming which one you hold or swap for. + ## Reading Blend state over RPC -All reads are `contract invoke` on a getter. The CLI simulates read-only calls and returns the decoded value — nothing is signed or submitted, no indexer involved. Note `--source-account` is still **required** to build the simulation (any funded key works); only `contract info interface` runs without one. Inspect the exact ABI first: +All reads are `contract invoke` on a getter — the CLI simulates the call and returns the decoded value, no indexer involved. **Pass `--send=no` on every read.** By default the CLI decides for itself whether to submit, and against TTL-archived ledger state a plain getter will skip simulation and **auto-submit a real RestoreFootprint transaction** — a signed, fee-paying write triggered by a "read" (abandoned pools and dead oracles are exactly where you hit this). A surprise `Account not found` error on a read is the symptom of archived state: stop and investigate; don't retry, because the retry is what submits the restore. + +Note `--source-account` is still **required** to build the simulation (any funded key works); only `contract info interface` runs without one. Inspect the exact ABI first: ```bash stellar contract info interface --id --network "$NETWORK" @@ -86,6 +122,12 @@ stellar contract info interface --id --network "$NET State-changing calls (`submit`, backstop `deposit`, etc.) go through `contract invoke ... --send=yes` signed by a wallet key. Token movements into the protocol (the asset you supply/repay) are ordinary SEP-41 / SAC transfers the contract pulls via authorization; the CLI collects and signs the required auth entries automatically during simulation. +Mechanics that surprise people: + +- **`--send` is an invoke-only convention.** `stellar tx new …` operations (trustlines, path payments, account merges) take no `--send` flag and **submit by default**. +- **Soroban writes carry resource fees** — expect ~0.09 XLM per pool `submit`/`deposit` on top of the inclusion fee, deducted even though the examples don't mention a fee flag. +- **On `transaction submission timeout`**: public RPCs drop low-fee Soroban transactions under surge pricing. First verify the transaction didn't land (`stellar tx fetch result`, or check the account's sequence — see the wallet skill's [diagnose + safe-retry rules](../../wallet/accounts-and-tx.md)), then retry with a higher bid: `--inclusion-fee 1000000`. (`--fee` is deprecated; use `--inclusion-fee`.) + ## Related skills - Keys, signing, fees, submission, balances → [`../../wallet/SKILL.md`](../../wallet/SKILL.md) diff --git a/skills/defi/blend/backstop.md b/skills/defi/blend/backstop.md index 006b1eb..9d45645 100644 --- a/skills/defi/blend/backstop.md +++ b/skills/defi/blend/backstop.md @@ -10,13 +10,10 @@ The backstop is Blend's insurance layer. Depositors stake the **backstop token** — a Comet 80/20 BLND:USDC LP token (confirm weights with the LP token's `get_normalized_weight`) — behind a specific pool. In return they earn a share of that pool's interest plus BLND emissions. In exchange, backstop capital is **first-loss**: it absorbs bad debt before lenders take a hit. That risk is why exits are time-locked (see [Q4W](#queue-for-withdrawal-q4w)). -Everything here targets the Backstop contract: +Everything here targets the Backstop contract. `BACKSTOP`, `NETWORK`, and `ME` come from [network setup](SKILL.md#network-setup) (both networks' addresses are there): ```bash -BACKSTOP=CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7 # mainnet (v2) -NETWORK=mainnet -ME=alice -POOL= # the pool you are backstopping +POOL= # the pool you are backstopping — see lending.md "Discovering pools" stellar contract info interface --id "$BACKSTOP" --network "$NETWORK" # confirm the ABI ``` @@ -24,7 +21,10 @@ stellar contract info interface --id "$BACKSTOP" --network "$NETWORK" # confir You deposit the **backstop token**, not BLND or USDC directly. Get its address from the backstop, then mint LP tokens via the Comet pool — a single-sided join with USDC (or BLND) works and needs no second asset: ```bash -LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token | tr -d '"') +LP_TOKEN=$(stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no -- backstop_token | tr -d '"') + +# Its underlying tokens, in weight order: [BLND, USDC] +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=no -- get_tokens # Single-sided join: deposit USDC into the Comet pool, receive LP tokens (returns the LP amount minted) stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=yes \ @@ -32,6 +32,8 @@ stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --s --token_in --token_amount_in 100000000 --min_pool_amount_out 0 --user "$ME" ``` +If all you hold is XLM, you can't join the Comet pool directly — first obtain USDC (or BLND) by **borrowing it against XLM collateral** ([lending.md](lending.md)) or swapping via a DEX path payment; make sure it's the *right* USDC (`name()` — see [getting assets](SKILL.md#getting-the-assets-these-flows-assume)). + ## Deposit `deposit(from, pool_address, amount)` deposits backstop LP tokens behind a pool, minting backstop shares: @@ -50,7 +52,7 @@ Shares represent your claim on that pool's backstop; their value rises as the ba Backstop deposits are **not** instantly withdrawable — this stops depositors from fleeing right before a liquidation. To exit you queue, wait out the timelock, then withdraw. **While queued, the funds stay at risk** (they still absorb bad debt) and keep accruing. -The v2 timelock is **17 days** (v1 was 21): `queue_withdrawal` returns a `Q4W` whose `exp` is exactly 17 days out. Withdrawing before `exp` fails simulation with contract error `#1001`. +The v2 timelock is **17 days** (v1 was 21): `queue_withdrawal` returns a `Q4W` whose `exp` is exactly 17 days out. Withdrawing before `exp` fails simulation with contract error `#1001` (`NotExpired`). ```bash # Start the timelock on part of your position @@ -87,22 +89,22 @@ To preview what's claimable without submitting, simulate the same call with `--s ## Reading backstop state -All reads simulate; no signing, no indexer. +All reads simulate — pass `--send=no` so they stay reads even against archived state (see [reading Blend state](SKILL.md#reading-blend-state-over-rpc)); no indexer. ```bash # Total backstop data for a pool: shares, LP tokens, queued %, and LP spot price -stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- pool_data --pool "$POOL" +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no -- pool_data --pool "$POOL" # Your balance for a pool: shares + your Q4W entries (each with amount + expiration) -stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" \ +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no \ -- user_balance --pool "$POOL" --user "$(stellar keys address "$ME")" # The backstop LP token address (Comet BLND:USDC) -stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" -- backstop_token +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no -- backstop_token ``` -- **`PoolBackstopData`** — `shares`, `tokens` (LP tokens deposited), `q4w_pct` (fraction queued for withdrawal), `blnd` / `usdc` (underlying amounts), `token_spot_price` (LP token spot price — useful for USD conversion). -- **`UserBalance`** — `shares`, `q4w: Vec` where `exp` is the unlock ledger timestamp. +- **`PoolBackstopData`** — `shares`, `tokens` (LP tokens deposited), `q4w_pct` (fraction of shares queued for withdrawal, **7-decimal** fixed point: `3333334` ≈ 33%), `blnd` / `usdc` (underlying amounts), `token_spot_price` (LP token spot price — useful for USD conversion). +- **`UserBalance`** — `shares`, `q4w: Vec` where `exp` is the unlock time as a **unix timestamp** (seconds) — compare against the latest ledger's close time. ## Find the most profitable backstop @@ -119,18 +121,18 @@ APR ≈ (annualized_interest_share_USD + annualized_emissions_USD) / backstop_de Your own projected earnings are pro-rata: `(your user_balance.shares / pool_data.shares) × the pool-level totals above`. For already-accrued (unclaimed) emissions, simulate `claim` with `--send=no` — see [claiming](#claim-backstop-emissions). -The bundled script [`scripts/rank-backstops.sh`](scripts/rank-backstops.sh) ranks pools by an approximate backstop APR using **RPC only**. It is a scaffold: it reads the real on-chain inputs and shows where each number comes from (its comments walk through each step); finishing the APR still requires the per-reserve reads sketched in the script's step 4. Requires stellar-cli, jq, and a funded key alias. Run it: +The bundled script [`scripts/rank-backstops.sh`](scripts/rank-backstops.sh) ranks pools by an approximate backstop APR using **RPC only**. It is a scaffold: it reads the real on-chain inputs and shows where each number comes from (its comments walk through each step); finishing the APR still requires the per-reserve reads sketched in the script's step 4. Requires stellar-cli, jq, and a funded key alias. It knows the v2 backstop for testnet and mainnet (override with `BACKSTOP=` for anything else; mainnet needs an [RPC configured](SKILL.md#network-setup) first). Run it: ```bash -NETWORK=mainnet SOURCE=alice scripts/rank-backstops.sh +NETWORK=testnet SOURCE=alice scripts/rank-backstops.sh ``` Output is CSV: `pool,backstop_tokens,bstop_rate,note`, one row per reward-zone pool. -To complete the APR, add per-pool reserve reads (`get_reserve --asset ` for `d_supply`, `d_rate`, and the current borrow rate — see [lending.md](lending.md#reading-pool-state)), the pool-side emission reads (`get_reserve_emissions` per reserve side, scaled by 70/30 to get the backstop's BLND/sec — see [projected earnings](lending.md#projected-earnings)), and an LP-token-to-USD conversion (`token_spot_price` from `pool_data`). Rank pools by the resulting APR. +To complete the APR, add per-pool reserve reads (`get_reserve --asset ` for `d_supply` and `d_rate`, then compute the borrow rate with [the borrow-rate formula](lending.md#the-borrow-rate)), the pool-side emission reads (`get_reserve_emissions` per reserve side, scaled by 70/30 to get the backstop's BLND/sec — see [projected earnings](lending.md#projected-earnings)), and an LP-token-to-USD conversion (`token_spot_price` from `pool_data`). Rank pools by the resulting APR. ## Related -- Supplying/borrowing in the pools you backstop → [lending.md](lending.md) +- Supplying/borrowing in the pools you backstop; discovering pools → [lending.md](lending.md) - Keys, signing, submission, and reading token balances → [`../../wallet/SKILL.md`](../../wallet/SKILL.md) -- The `getEvents` RPC pattern for enumerating pools → [`../../data/SKILL.md`](../../data/SKILL.md) +- Reading chain data over RPC → [`../../data/SKILL.md`](../../data/SKILL.md) diff --git a/skills/defi/blend/lending.md b/skills/defi/blend/lending.md index 0567396..6d265fc 100644 --- a/skills/defi/blend/lending.md +++ b/skills/defi/blend/lending.md @@ -82,7 +82,9 @@ The pool pulls the asset from `spender` via authorization; the CLI collects and ## Borrow -You can only borrow up to your collateral's borrowing power (collateral value × collateral factor); exceeding it fails simulation with contract error `#1205`. Borrow sends the asset to `to` — if the asset is a classic-asset SAC (e.g. USDC), `to` needs a **trustline** first (`stellar tx new change-trust --line `; get the `CODE:ISSUER` string from the token's `name()`): +You can only borrow up to your collateral's borrowing power (collateral value × collateral factor); exceeding it fails simulation with contract error `#1205` (`InvalidHf`). Borrow sends the asset to `to` — if the asset is a classic-asset SAC (e.g. USDC), `to` needs a **trustline** first (`stellar tx new change-trust --line `; get the `CODE:ISSUER` string from the token's `name()`). + +There is also a **minimum position size**: `PoolConfig.min_collateral` (read it with [`get_config`](#reading-pool-state)) is the smallest **c_factor-adjusted** collateral value — *not* your raw collateral value — that may back a borrow position, denominated in the oracle's base asset at the oracle's `decimals()`. Falling below it fails with `#1224` (`MinCollateralNotMet`). The active mainnet reward-zone pools currently set a $5 floor; with XLM's `c_factor` at 0.75 that means roughly $6.70 worth of raw XLM collateral (a few tens of XLM) before you can borrow anything at all: ```bash stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ @@ -93,7 +95,7 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= ## Repay -Repay pulls the asset from `spender`. To fully repay, pass an `amount` larger than the debt — the pool pulls the full amount and refunds the excess to `spender` in the same transaction, so `spender` needs a balance covering the full amount you pass: +Repay pulls the asset from `spender`: ```bash stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=yes \ @@ -102,6 +104,20 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= --requests '[{"request_type":5,"address":"'"$ASSET"'","amount":"500000000"}]' ``` +### Fully closing a loan + +Your debt is always **slightly more than what you borrowed** — dTokens round up at borrow, so debt exceeds the proceeds from the very same ledger (measured: 8 stroops over within minutes). That makes the two "obvious" full-repay moves both wrong when all you hold is the loan proceeds: + +- Pass more than your balance → the pool pulls the **full amount before refunding**, and the transfer fails with `#10` (`BalanceError`). +- Pass exactly your balance → dust debt survives, and later blocks withdraw-all with `#1205` (any withdraw that leaves debt must stay healthy). + +The working recipe: + +1. **Get a small buffer (~5–10%) of the exact borrowed asset** — same `CODE:ISSUER`, check with `name()`. From XLM, use a DEX path payment: `stellar tx new path-payment-strict-receive` (see the [wallet skill](../../wallet/SKILL.md)). +2. **Repay with any `amount` between your debt and your balance** — over the debt so nothing survives, within your balance so the pull succeeds. The pool pulls only what's owed and refunds the excess to `spender` exactly, in the same transaction. + +After the repay, `get_positions` should show no liability for the reserve — then withdraw-all works. + ## Withdraw collateral ```bash @@ -111,7 +127,7 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= --requests '[{"request_type":3,"address":"'"$ASSET"'","amount":"1000000000"}]' ``` -Withdrawal is blocked if it would drop your position below the required collateralization while you hold debt. Passing an amount larger than your balance withdraws the full balance — so to fully exit a reserve, pass an oversized amount. +Withdrawal is blocked (`#1205`) if it would drop your position below the required collateralization while you hold debt — including **dust debt** left by an inexact repay (see [fully closing a loan](#fully-closing-a-loan)). Passing an amount larger than your balance withdraws the full balance — so to fully exit a reserve, pass an oversized amount. ## Batching requests atomically @@ -132,45 +148,67 @@ stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send= ## Discovering pools -Pools are created by the Pool Factory; enumerate them over RPC. The factory records deployed pools — inspect its interface for the exact getter, then read the list: +**Enumerate pools with `reward_zone()` on the Backstop.** It returns the list of active, emission-eligible pool addresses — which is what you want for supplying, borrowing, or yield analysis (`BACKSTOP` and `NETWORK` come from [network setup](SKILL.md#network-setup)): ```bash -POOL_FACTORY=CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU # mainnet (v2) -stellar contract info interface --id "$POOL_FACTORY" --network "$NETWORK" +stellar contract invoke --id "$BACKSTOP" --source "$ME" --network "$NETWORK" --send=no -- reward_zone +``` + +On testnet this currently includes the blend-utils reference pool, ready to paste: -# The v2 factory exposes only deploy(...) and is_pool(pool_address) -> bool — no list getter. -stellar contract invoke --id "$POOL_FACTORY" --source "$ME" --network "$NETWORK" -- is_pool --pool_address +```bash +POOL=CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF # testnet (v2) "TestnetV2" pool ``` -Since there is no list getter, enumerate pool-creation events from RPC (`getEvents` on the factory contract) — still RPC-only, no indexer. See the [data skill](../../data/SKILL.md) for the `getEvents` pattern. The [backstop reward zone](backstop.md#reading-backstop-state) is also a practical source of active pool addresses. +To check whether an arbitrary address is a genuine v2 pool (the factory exposes only `deploy(...)` and `is_pool` — no list getter): + +```bash +stellar contract invoke --id "$POOL_FACTORY" --source "$ME" --network "$NETWORK" --send=no -- is_pool --pool_address +``` + +Don't try to enumerate pools from the factory's deploy events: RPC keeps only ~7 days of events, and the deploys are long past — `getEvents` returns nothing. Pools outside the reward zone can only be found by address (from the user, [Blend's UI](https://mainnet.blend.capital), or an indexer). ## Reading pool state -All reads simulate; nothing is signed or submitted, but the CLI still requires `--source-account` (any funded key) to build the simulation. +All reads simulate — pass `--send=no` so they stay reads even against archived state (see [reading Blend state](SKILL.md#reading-blend-state-over-rpc)). The CLI still requires `--source-account` (any funded key) to build the simulation. ```bash -# Pool config: oracle, backstop take rate, status, max positions -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_config +# Pool config: oracle, backstop take rate, status, max positions, min collateral +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no -- get_config # Reserve list: the asset addresses this pool supports -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_reserve_list +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no -- get_reserve_list # One reserve: Reserve { asset, config: ReserveConfig, data: ReserveData, scalar } -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_reserve --asset "$ASSET" +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no -- get_reserve --asset "$ASSET" # A user's positions: collateral / liabilities / supply, keyed by reserve index -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" -- get_positions --address "$(stellar keys address "$ME")" +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no -- get_positions --address "$(stellar keys address "$ME")" ``` Key fields you will read: -- **`PoolConfig`** — `oracle` (price source), `bstop_rate` (fraction of interest routed to the backstop), `status`, `max_positions`, `min_collateral`. -- **`ReserveConfig`** — `decimals`, `c_factor` (collateral factor), `l_factor` (liability factor), the kinked interest-rate curve (`r_base`, `r_one`, `r_two`, `r_three`, `util`, `max_util`, `reactivity`), `supply_cap`, `enabled`, `index`. -- **`ReserveData`** — `b_rate` / `d_rate` (supply/borrow indexes), `b_supply` / `d_supply` (total b/d tokens), `ir_mod`, `backstop_credit`, `last_time`. +- **`PoolConfig`** — `oracle` (price source), `bstop_rate` (fraction of interest routed to the backstop, 7-decimal fixed point), `status`, `max_positions`, `min_collateral` (minimum c_factor-adjusted collateral value to hold a borrow position, in the oracle's base asset at the oracle's `decimals()` — see [Borrow](#borrow)). +- **`PoolConfig.status`** — `0` Admin-Active, `1` Active, `2` Admin-On-Ice, `3` On-Ice, `4` Admin-Frozen, `5` Frozen, `6` Setup. Status > 1 disables borrowing; status > 3 also disables supplying (violations fail with `#1206`). +- **`ReserveConfig`** — `decimals`, `c_factor` (collateral factor), `l_factor` (liability factor), the kinked interest-rate curve (`r_base`, `r_one`, `r_two`, `r_three`, `util`, `max_util`, `reactivity` — all 7-decimal), `supply_cap`, `enabled`, `index`. +- **`ReserveData`** — `b_rate` / `d_rate` (supply/borrow indexes), `b_supply` / `d_supply` (total b/d tokens), `ir_mod` (rate modifier, **7-decimal** fixed point, clamped to [0.1, 10] — i.e. `1000000`–`100000000`), `backstop_credit`, `last_time`. - **`Positions`** — `collateral`, `liabilities`, `supply` maps keyed by reserve index. Convert shares to underlying with the rate index: underlying supply = `bToken_balance × b_rate / 1e12`; underlying debt = `dToken_balance × d_rate / 1e12`. `b_rate` / `d_rate` are 12-decimal fixed-point (`SCALAR_12`); the result is in the asset's native decimals. +### The borrow rate + +The current borrow APR is computable from `ReserveConfig` + `ReserveData` alone (all inputs and the result are 7-decimal fixed point; `util` here is current utilization, `target` is `ReserveConfig.util`): + +``` +util ≤ target: rate = (r_base + r_one × util/target) × ir_mod +target < util ≤ 0.95: rate = (r_base + r_one + r_two × (util − target)/(0.95 − target)) × ir_mod +util > 0.95: rate = (r_base + r_one + r_two) × ir_mod + + r_three × (util − 0.95)/0.05 # r_three leg is NOT scaled by ir_mod +``` + +with `utilization = (d_supply × d_rate) / (b_supply × b_rate)`. This is `calc_accrual` in `pool/src/pool/interest.rs` of [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2), transcribed; check the source if in doubt. + ## Emissions: check, project, claim Suppliers and borrowers accrue BLND emissions per reserve **side**. Each reserve has two emission streams, identified by a `reserve_token_id`: @@ -184,15 +222,17 @@ Suppliers and borrowers accrue BLND emissions per reserve **side**. Each reserve ```bash # The stream itself: eps (BLND/sec — 14 total decimals: BLND's 7 + a 7-decimal scalar), index, expiration -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" \ +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no \ -- get_reserve_emissions --reserve_token_index 1 # Your accrued-but-unclaimed BLND on that stream: UserEmissionData { accrued, index } -stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" \ +stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no \ -- get_user_emissions --user "$(stellar keys address "$ME")" --reserve_token_index 1 ``` -`accrued` only updates when your position is touched, so BLND earned since your last interaction isn't in it. For the exact claimable total, **simulate the claim**: run it with `--send=no` and read the returned amount without submitting anything: +`accrued` only updates when your position is touched, so BLND earned since your last interaction isn't in it. For the exact claimable total, **simulate the claim**: run it with `--send=no` and read the returned amount without submitting anything. + +> **Prerequisite:** `to` needs a **BLND trustline** before any claim — even this simulated preview reverts with `#13` (`TrustlineMissingError`) without one. See [prerequisites](SKILL.md#prerequisites) for deriving BLND and adding the trustline. ```bash stellar contract invoke --id "$POOL" --source "$ME" --network "$NETWORK" --send=no \ @@ -224,7 +264,7 @@ utilization = (d_supply × d_rate) / (b_supply × b_rate) # underlying bor supply_rate ≈ borrow_rate × utilization × (1 − bstop_rate) # bstop_rate is 7-decimal fixed point ``` -`borrow_rate` comes from the reserve's kinked curve — `r_base` plus the `r_one`/`r_two`/`r_three` slopes around target `util`, scaled by the live `ir_mod` modifier (see `pool/src/pool/interest.rs` in [`blend-contracts-v2`](https://github.com/blend-capital/blend-contracts-v2)). Projected yearly interest ≈ `your_supplied_underlying × supply_rate` (or `your_debt × borrow_rate` on the cost side). +`borrow_rate` is computed from `ReserveConfig` + `ReserveData` with the formula in [the borrow rate](#the-borrow-rate). Projected yearly interest ≈ `your_supplied_underlying × supply_rate` (or `your_debt × borrow_rate` on the cost side). **2. BLND emissions.** Your share of a stream is proportional to your b/dTokens over the side's total: @@ -240,14 +280,14 @@ To express BLND earnings in USD, read the BLND price from the Comet LP (pool ora ```bash # BLND price in USDC — 7-decimal fixed point, e.g. "3008070" = 0.30 USDC per BLND -stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" \ +stellar contract invoke --id "$LP_TOKEN" --source "$ME" --network "$NETWORK" --send=no \ -- get_spot_price --token_in "$USDC" --token_out "$BLND" ``` To price a reserve asset in USD (the interest side), use the pool's oracle (`PoolConfig.oracle` from `get_config`, SEP-40). The `Asset` argument is an enum — pass it as JSON: ```bash -# Returns PriceData { price, timestamp }; scale price by the oracle's decimals() (typically 7) -stellar contract invoke --id "$ORACLE" --source "$ME" --network "$NETWORK" \ +# Returns PriceData { price, timestamp }; scale price by the oracle's decimals() (typically 7 or 14) +stellar contract invoke --id "$ORACLE" --source "$ME" --network "$NETWORK" --send=no \ -- lastprice --asset '{"Stellar":"'"$ASSET"'"}' ``` diff --git a/skills/defi/blend/scripts/rank-backstops.sh b/skills/defi/blend/scripts/rank-backstops.sh index 7acbd67..71afc8f 100755 --- a/skills/defi/blend/scripts/rank-backstops.sh +++ b/skills/defi/blend/scripts/rank-backstops.sh @@ -1,18 +1,32 @@ #!/usr/bin/env bash # rank-backstops.sh — approximate backstop APR per Blend pool, RPC only. # Requires: stellar-cli, jq, a funded key alias (reads simulate but need a source account). -# Usage: NETWORK=mainnet SOURCE=alice ./rank-backstops.sh +# Usage: NETWORK=testnet SOURCE=alice ./rank-backstops.sh +# NETWORK=mainnet SOURCE=alice ./rank-backstops.sh (needs `stellar network add mainnet` first) set -euo pipefail -NETWORK=${NETWORK:-mainnet} -BACKSTOP=${BACKSTOP:-CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7} +NETWORK=${NETWORK:-testnet} SOURCE=${SOURCE:?set SOURCE to a funded key alias} -invoke() { stellar contract invoke --id "$1" --source "$SOURCE" --network "$NETWORK" -- "${@:2}" 2>/dev/null; } +# Blend v2 backstop per network; pass BACKSTOP explicitly to override. +if [[ -z "${BACKSTOP:-}" ]]; then + case "$NETWORK" in + mainnet) BACKSTOP=CAQQR5SWBXKIGZKPBZDH3KM5GQ5GUTPKB7JAFCINLZBC5WXPJKRG3IM7 ;; + testnet) BACKSTOP=CBDVWXT433PRVTUNM56C3JREF3HIZHRBA64NB2C3B2UNCKIS65ZYCLZA ;; + *) echo "error: no default backstop for NETWORK=$NETWORK — set BACKSTOP=C..." >&2; exit 1 ;; + esac +fi + +# Errors surface on stderr; --send=no keeps every call a pure read. +invoke() { stellar contract invoke --id "$1" --source "$SOURCE" --network "$NETWORK" --send=no -- "${@:2}"; } # 1. Get the candidate pools. The reward zone is the practical source of active, -# emission-eligible pools. +# emission-eligible pools (see lending.md "Discovering pools"). pools=$(invoke "$BACKSTOP" reward_zone | jq -r '.[]') +if [[ -z "$pools" ]]; then + echo "error: reward_zone returned no pools on $NETWORK (backstop $BACKSTOP)" >&2 + exit 1 +fi echo "pool,backstop_tokens,bstop_rate,note" for POOL in $pools; do @@ -27,6 +41,8 @@ for POOL in $pools; do # 4. To finish the APR you still need, per pool: # - sum over reserves of (d_supply * d_rate * borrow_rate) * bstop_rate -> interest to backstop + # (borrow_rate: compute from get_reserve fields with the formula in + # lending.md "The borrow rate") # - backstop BLND/sec: no direct getter, but pool-side streams are readable — # sum get_reserve_emissions eps over the pool's reserve sides, then # backstop_eps = pool_eps_sum * 70/30 diff --git a/skills/standards/SKILL.md b/skills/standards/SKILL.md index 133de57..b58092a 100644 --- a/skills/standards/SKILL.md +++ b/skills/standards/SKILL.md @@ -753,6 +753,24 @@ Always verify CAP status and network support before treating any ZK primitive as - [NOWNodes](https://nownodes.io) - All networks incl. Futurenet - [GetBlock](https://getblock.io) - Testnet & Mainnet +#### Public mainnet RPC endpoints + +SDF provides no public **mainnet** RPC, and the Stellar CLI's built-in `mainnet` network has **no RPC URL** — nothing works against mainnet until you register an endpoint. No-signup public endpoints (each verified with a `getHealth` probe; most keep ~1–7 days of history — check `ledgerRetentionWindow` in the `getHealth` response): + +| Endpoint | Operator | +|----------|----------| +| `https://mainnet.sorobanrpc.com` | Community-run public endpoint | +| `https://rpc.ankr.com/stellar_soroban` | Ankr (public tier) | +| `https://soroban-rpc.creit.tech` | Creit Tech (xBull) | + +```bash +stellar network add mainnet \ + --rpc-url https://mainnet.sorobanrpc.com \ + --network-passphrase "Public Global Stellar Network ; September 2015" +``` + +Public endpoints are rate-limited, best-effort, and may drop low-fee Soroban transactions under load (retry with a higher `--inclusion-fee`) — use a provider above for production. + ## Protocol & Governance ### Stellar Protocol