From fe9cd8b9f63147874d050cbe315b4fa68bd67be7 Mon Sep 17 00:00:00 2001 From: tyler-tsai <15355143+tyler-tsai@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:01:11 +0800 Subject: [PATCH 1/4] docs(for-developer): contract & protocol pages (CDP, DEX, slisBNB, slisBNBx, Credit, Lending, Oracle) All contract-derived pages, for contract-team review: - CDP mechanics: no interest at borrow, dynamic AMO duty clamped to maxDuty (~200% APY)/minDuty, Abacus auction curves, borrowed() +100 wei - V3 DEX README + mechanics: Uniswap V3 fork; fee tiers 0.05/0.30/1.00%; Lista-specific pool init-code hash for off-chain address derivation - slisBNB smart-contract: fix 2 stale OFT addresses (+ StakeHub/GovBNB) - slisBNBx delegation: changeable per-account via SlisBNBxMinter.delegateAllTo - Credit loan-lifecycle: penalty 15%; correct supplyAndBorrow signature - Lending: contract-reference, events & onMoolah* callbacks, Adaptive Curve IRM, Smart Lending/StableSwap integration (+ README contents) - Multi-Oracle: consuming-prices (peek USD vs Moolah getPrice 10**(36+loanDec-collDec) scale, raw-unit conversion) Co-Authored-By: Claude Opus 4.8 --- for-developer/clisbnb/delegation.md | 76 ++++- .../collateral-debt-position/mechanics.md | 244 +++++++------- for-developer/credit-loans/loan-lifecycle.md | 97 +++++- for-developer/dex/README.md | 30 +- for-developer/dex/mechanics.md | 213 ++++++++++++ .../liquid-staking-slisbnb/smart-contract.md | 26 +- for-developer/lista-lending/README.md | 4 + .../lista-lending/contract-reference.md | 304 ++++++++++++++++++ .../lista-lending/events-and-callbacks.md | 178 ++++++++++ for-developer/lista-lending/irm.md | 153 +++++++++ .../lista-lending/stableswap-integration.md | 246 ++++++++++++++ .../multi-oracle/consuming-prices.md | 220 +++++++++++++ 12 files changed, 1638 insertions(+), 153 deletions(-) create mode 100644 for-developer/dex/mechanics.md create mode 100644 for-developer/lista-lending/contract-reference.md create mode 100644 for-developer/lista-lending/events-and-callbacks.md create mode 100644 for-developer/lista-lending/irm.md create mode 100644 for-developer/lista-lending/stableswap-integration.md create mode 100644 for-developer/multi-oracle/consuming-prices.md diff --git a/for-developer/clisbnb/delegation.md b/for-developer/clisbnb/delegation.md index 60916a3..1f8501c 100644 --- a/for-developer/clisbnb/delegation.md +++ b/for-developer/clisbnb/delegation.md @@ -1,18 +1,72 @@ # Delegation -When depositing collateral and triggering mint, the depositor can optionally set a delegate address to receive `slisBNBx` instead of the depositor address. +`slisBNBx` is a non-transferable certificate, so a holder cannot move it with a normal ERC-20 `transfer`. Instead, the protocol lets an account choose **which wallet holds the `slisBNBx` minted on its behalf**. This is called delegation, and it is the mechanism used to route a position's `slisBNBx` (and therefore its Binance Launchpool eligibility) to another wallet, such as a Binance Web3 MPC wallet. -This is useful when users want Launchpool rewards to accrue to another wallet, such as a Binance Web3 wallet. +Delegation is managed by `SlisBNBxMinter`, the mint-and-burn engine for `slisBNBx`. See [Token Lifecycle](token-lifecycle.md) for how `slisBNBx` is minted and burned, and [Smart Contract](smart-contract.md) for addresses. -## Rules +## Delegation model -* Only one delegate address can be set per depositor at mint time. -* Once assigned for a position, delegate address cannot be changed or transferred. -* `slisBNBx` remains non-transferable even in delegated accounts. -* On withdrawal, burn is executed from the delegated holder address. +| Property | Behavior | +| --- | --- | +| Scope | **Per account, not per position.** A single delegatee holds the account's *entire* `slisBNBx` balance across all collateral modules (the `slisBNB` provider and the `slisBNB/BNB` LP provider). | +| Granularity | **No partial delegation.** You cannot split an account's `slisBNBx` between multiple wallets — it is all-or-nothing. | +| Default | If an account has never set a delegatee, the holder defaults to **the account itself**. The first mint records `delegation[account] = account`. | +| Mutability | **Can be changed at any time** via `delegateAllTo`. It is *not* fixed at mint time. | +| Effect on collateral | Delegation only changes **who holds the `slisBNBx` certificate**. It does not move, re-own, or otherwise affect the underlying collateral in Moolah. | -## Operational Impact +The minter stores delegation as a single mapping, `delegation[account] => delegatee`, and tracks the total `slisBNBx` minted for each account across every module in `userTotalBalance[account]`. Both are read-only and queryable on-chain. -* Deposit path can credit certificate ownership to a separate reward wallet. -* Withdrawal path must burn from that same delegated holder. -* Delegation affects who holds the certificate, not collateral ownership rules in Moolah. +## Changing the delegatee + +Reassigning the delegatee is **atomic**: the minter burns the account's entire `slisBNBx` balance from the old holder and mints the same amount to the new delegatee in a single call. No rebalance against collateral happens during the switch — only the holder changes. + +### `delegateAllTo` (called by the account) + +```solidity +function delegateAllTo(address newDelegatee) external; +``` + +Called by the account itself (`msg.sender`). It delegates the account's whole `slisBNBx` balance to `newDelegatee`. + +* No-op if `newDelegatee` already equals the current delegatee (this equality check runs first). +* Otherwise, the zero address is rejected as a new delegatee. +* Internally: reads `userTotalBalance[account]`, burns that amount from the current holder (the old delegatee, or the account itself if none was set), records `delegation[account] = newDelegatee`, then mints the burned amount to `newDelegatee`. + +### `syncDelegatee` (called by a module) + +```solidity +function syncDelegatee(address account, address newDelegatee) external; +``` + +The module-callable variant. It is restricted to registered collateral modules (`msg.sender` must be a configured module), and performs the same atomic burn-from-old / mint-to-new as `delegateAllTo`. It exists so a provider module can carry a user's existing delegation over to the minter during the migration to the `SlisBNBxMinter` architecture; integrators delegate through `delegateAllTo`, not this function. + +### Burn-tolerant reassignment + +The burn step uses a safe-burn that tolerates a holder whose actual `slisBNBx` balance is lower than the recorded total (balances can drift from collateral value, exchange-rate, or fee-rate changes). If less than the expected amount can be burned, `userTotalBalance[account]` is adjusted down to the amount actually burned, and only that amount is minted to the new delegatee. This keeps the certificate accounting consistent rather than reverting. + +## Legacy `SlisBNBProvider.delegateAllTo` is disabled + +The `SlisBNBProvider` collateral module also exposes a `delegateAllTo(address)` function from an earlier design. Once the provider has been wired to the minter (`slisBNBxMinter` is set), this legacy path **reverts with `"not supported"`**. All delegation must go through `SlisBNBxMinter.delegateAllTo`. + +```solidity +// SlisBNBProvider.delegateAllTo — legacy path, gated off once the minter is set +require(slisBNBxMinter == address(0), "not supported"); +``` + +## Events + +Index these to track delegation and the resulting certificate movements: + +| Event | Emitted by | Signature | Meaning | +| --- | --- | --- | --- | +| `ChangeDelegateTo` | `SlisBNBxMinter` | `ChangeDelegateTo(address account, address oldDelegatee, address newDelegatee, uint256 amount)` | Delegatee reassigned; `amount` is the `slisBNBx` actually burned from the old holder and minted to the new one. | +| `UserModuleRebalanced` | `SlisBNBxMinter` | `UserModuleRebalanced(address account, address module, uint256 userPart, uint256 feePart)` | Per-module `slisBNBx` recomputed for the account during a rebalance. | +| `Rebalance` | `SlisBNBxMinter` | `Rebalance(address account, uint256 latestModuleBalance, address module, uint256 latestTotalBalance)` | A module triggered a rebalance; `latestTotalBalance` is the account's new total across modules. | + +> The `SlisBNBProvider` contract emits its own `ChangeDelegateTo(address account, address oldDelegatee, address newDelegatee)` (note: **no `amount` field**) only from the legacy, now-disabled path. For the current minter architecture, listen for the `SlisBNBxMinter` event above. + +## Integration notes + +* **Read, don't expect transfers.** `slisBNBx` is non-transferable. To find an account's certificate holder, read `delegation[account]`; to find the minted total, read `userTotalBalance[account]`. +* **Delegation does not auto-rebalance.** `delegateAllTo` moves the existing balance as-is. Subsequent collateral supply/withdraw will rebalance the certificate to the *current* delegatee. See [Token Lifecycle](token-lifecycle.md). +* **The new holder receives Launchpool eligibility.** Because eligibility is computed off-chain from `slisBNBx` balances, the wallet that holds the certificate after delegation is the one credited with rewards. diff --git a/for-developer/collateral-debt-position/mechanics.md b/for-developer/collateral-debt-position/mechanics.md index 27f774b..fa1f56b 100644 --- a/for-developer/collateral-debt-position/mechanics.md +++ b/for-developer/collateral-debt-position/mechanics.md @@ -1,194 +1,188 @@ # Mechanics -In the Lista mechanism, users can earn rewards by strategically utilizing their assets, which may include BNB, ETH, slisBNB, wBETH, and BTCB. The process begins with users depositing these assets into the Interaction (CDP) module, where they are used as collateral to borrow LisUSD.
+The Collateral Debt Position (CDP) module lets a user deposit a supported asset as collateral and mint **lisUSD**, an over-collateralized stablecoin, against it. The engine is a MakerDAO/Helio-style fork deployed on BNB Chain: a small set of specialized contracts (VAT, JUG, SPOT, DOG, CLIP, ABACI, VOW) sit behind a single user-facing entrypoint, the **Interaction** contract. -In addition to depositing assets and borrowing LisUSD, users can earn rewards by staking LisUSD and BNB within the Lista ecosystem. By participating in these staking activities, they accrue interest and additional rewards, significantly enhancing their overall earnings. +> **Naming.** `lisUSD` is the canonical stablecoin name. Throughout the engine source you will also see the legacy alias **Hay** (`hay.sol`, name `"Hay Destablecoin"`) and the `HayJoin` adapter. `Hay` and `lisUSD` refer to the same token; the live mainnet token is `LisUSD` (name `"Lista USD"`). This page uses `lisUSD`. -
+This page describes the on-chain mechanics and ties each user action to the real `Interaction` function and the `IDao` event it emits, so integrators and auditors can index and reproduce each step. -### Fees +## Component map -1. Borrowing interest — an interest paid to Lista for borrowing lisUSD. The rate is a fixed number set by the Lista governance platform. -2. Liquidation penalty — percentage subtracted in the form of lisUSD when selling user's collateral in a Dutch action during the liquidation process. +| Contract | Role | +| --- | --- | +| `Interaction` | User entrypoint; orchestrates deposit / borrow / payback / withdraw and the auction surface. | +| `Vat` | Core accounting engine: per-collateral `ilks` and per-user `urns` (`ink` = collateral, `art` = normalized debt), plus the debt rate accumulator. | +| `Jug` | Stability-fee accrual. `drip` folds the per-collateral `duty` into the Vat `rate` accumulator. | +| `Spotter` (SPOT) | Pulls the collateral price from the oracle and applies the liquidation ratio (`mat`) to produce the safe `spot` price. | +| `GemJoin` | Per-collateral adapter that escrows the ERC-20 collateral and credits it into the Vat. | +| `HayJoin` | lisUSD adapter: `exit` mints lisUSD to the user on borrow; `join` burns it on repay. | +| `Dog` | Liquidation trigger (`bark`): marks an unsafe position and kicks a Dutch auction. | +| `Clipper` (CLIP) | Per-collateral Dutch auction (`kick` / `take` / `redo`). | +| `Abacus` (ABACI) | Auction price-decay curve (`LinearDecrease` / `StairstepExponentialDecrease` / `ExponentialDecrease`). | +| `Vow` | Surplus/debt accounting; receiver of auction proceeds. | +| `DynamicDutyCalculator` (AMO) | Computes the per-collateral borrow rate dynamically from the lisUSD price (see [Borrow rate](#borrow-rate)). | -### Collateral ratio +## Fees -Collateral ratio is a percentage of the user's collateral value that determines the maximum borrowing limit for the user; it is calculated as follows: (total amount of lisUSD minted / total value of the collateral \* 100). Different assets will have different collateral ratios, depending on asset volatility. Collateral ratio is used as a liquidation bar to decide when a liquidation event should happen. +1. **Borrowing interest (stability fee).** Interest accrues continuously into the Vat debt-rate accumulator and is realized in lisUSD when debt is repaid. **No interest is charged at borrow time.** The rate is **dynamic**, computed per collateral by the AMO `DynamicDutyCalculator` from the lisUSD price — it is not a fixed governance number. See [Borrow rate](#borrow-rate). +2. **Liquidation penalty.** When a position is liquidated, the lisUSD target the auction must raise is the outstanding debt plus a liquidation penalty (`Dog.chop`). The penalty is a governance/manager-adjustable on-chain parameter; its live value is not published here. -
+## Collateral ratio -### CDP Module +Each collateral has a liquidation ratio (`mat`, set per `ilk` on the Spotter). The Spotter combines the oracle price with `mat` to compute the `spot` price used by the Vat to decide whether a position is safe. A position is **safe** while -The following sections will introduce the functions of the CDP Module one by one, explaining how users can borrow LisUSD by providing collateral and the interactions between different involved contracts. +``` +ink * spot >= art * rate +``` -
+i.e. collateral value (at the safe price) is at least the current debt. When this no longer holds the position becomes liquidatable. The contract exposes read-only helpers an integrator can call directly: -**a. Deposit Collateral** +| Function | Returns | +| --- | --- | +| `collateralPrice(token)` | Current oracle price of the collateral. | +| `collateralRate(token)` | `10**45 / mat` — the collateral ratio (18 decimals). | +| `locked(token, usr)` | User collateral (`ink`). | +| `free(token, usr)` | Unlocked (un-pledged) collateral. | +| `borrowed(token, usr)` | Current lisUSD debt (`art * rate / RAY`); when the debt is non-zero the helper adds a flat 100-wei buffer so `repay` can fully clear the position. | +| `availableToBorrow(token, usr)` | Additional lisUSD borrowable against current collateral. | +| `willBorrow(token, usr, amount)` | Borrowable lisUSD if `amount` more collateral were added (or removed, if negative). | +| `currentLiquidationPrice(token, usr)` | Collateral price at which the position becomes liquidatable. | +| `borrowApr(token)` | Current annualized borrow rate for the collateral. | -
- -1. User Deposits Collateral: The user initiates the deposit process by transferring their collateral to the Interaction contract. -2. Interaction: It moves the collateral to the GemJoin (like a Treasury). -3. GemJoin: receives the collateral from Interaction. -4. Vat: The Vat contract, which is the core of the CDP engine. It records the user’s collateral information and ensures that the collateral enters the system. - -
+## Borrow rate (AMO) -This process ensures that the user’s collateral is securely deposited and recorded within the CDP Module, allowing them to proceed with borrowing LisUSD against their collateral. +The borrow rate is **not** a fixed governance constant. It is the per-collateral stability fee (`duty`) computed on-chain by the `DynamicDutyCalculator` (Algorithmic Market Operations / AMO) to defend the lisUSD peg, modeled on Curve's crvUSD monetary policy. The mechanism and formula are already public in the protocol's [AMO documentation](../../introduction/collateral-debt-position-lisusd/lisusd/algorithmic-market-operations-amo/README.md). -
+The rate is derived from the lisUSD oracle price: -**b. Borrow LisUSD** - -
+``` +deviation = PEG - price(lisUSD) // PEG = $1 (1e8) +r = r0 * exp(deviation / beta) +duty = r + 1e27 // per-second rate in the Vat +``` -1. User Initiates Borrowing: The user requests to borrow a specific amount of LisUSD against their deposited collateral by calling borrow(). -2. Interaction: This request is processed by the Interaction, which then communicates with Vat. The user also pays interest during the process, the interest rate is a fixed number set by the Lista governance platform. -3. Vat: records an increase in the user's debt corresponding to the borrowed LisUSD against the specific collateral. -4. HayJoin: Interaction calls the \`exit()\` to mint the specified amount of LisUSD and sends it to the user. -5. ListaDistributor: Interaction calls ListaDistributor contract’s snapshot method to record user’s debt value against the collateral for calculating and distributing future rewards to the user. +where `r0` is the per-collateral baseline rate (when lisUSD trades at peg) and `beta` controls how sharply the rate responds to depeg. When lisUSD trades below $1 the rate rises (incentivizing repayment, contracting supply); above $1 it falls. **The rate is bounded on-chain by `maxDuty` and `minDuty`**: the contract clamps `duty` to that range, applying `maxDuty` when the price is at/below `minPrice` and `minDuty` when it is at/above `maxPrice`. The default `maxDuty` is ≈200% APY (`1000000034836767751273470154`) and `minDuty` is 0% APY (`1e27`); both are governance/manager-adjustable, so treat them as current on-chain values rather than a fixed cap. -
+The per-collateral `r0`, `beta`, and the price/duty bounds are governance/manager-adjustable on-chain values — read them from `DynamicDutyCalculator.ilks(collateral)` and the lisUSD oracle rather than treating them as fixed promises. On every borrow, repay, or deposit, `Interaction.drip(token)` asks the calculator for the up-to-date `duty` and updates the Jug before the Vat operation, so the live rate is always re-applied. -This sequence ensures that the user's debt is accurately recorded, the borrowed LisUSD is successfully minted and transferred to the user, and interest payments are made according to the fixed rate determined by Lista governance. +## CDP lifecycle -
+Each step below names the `Interaction` function called and the `IDao` event emitted, for indexing. -**c. Payback LisUSD** +### a. Deposit collateral -
+
-1. User Initiates Payback: The user initiates the payback process by specifying the amount of LisUSD to be repaid against the specific collateral. -2. Interaction: This payback request is processed by the Interaction -3. Vat: It updates the user’s debt, reducing it by the amount of LisUSD repaid. If the user fully repays their debt, the CDP (Collateralized Debt Position) is closed -4. HayJoin: Interaction calls the \`join()\` method, which burns LisUSD from the user’s account -5. ListaDistributor: Interaction calls ListaDistributor contract’s snapshot method to record user’s debt value against the collateral for calculating and distributing future rewards to the user. +**`deposit(address participant, address token, uint256 dink)`** -
+1. `Interaction` drips the stability fee (`drip`), then pulls `dink` of collateral from the caller. +2. The collateral is escrowed via the collateral's `GemJoin` adapter (`gem.join`). +3. `Interaction` calls `vat.frob` to record the collateral (`ink`) against the user's position in the Vat. +4. A debt snapshot is taken for reward accounting. -This process ensures that the user’s debt is accurately reduced or cleared, and the corresponding amount of LisUSD is burned, effectively removing it from circulation. +Collaterals that have a registered provider must be deposited **through** that provider unless `providerCompatibilityMode` is enabled for the token. Emits `Deposit(user, collateral, amount, totalAmount)`. -
+### b. Borrow lisUSD -**d. Withdraw Collateral**
+
-
+**`borrow(address token, uint256 hayAmount)`** -1. User Initiates Withdrawal: The user initiates the withdrawal process by specifying the amount of collateral they wish to withdraw. -2. Interaction: The withdrawal request is processed by the Interaction contract. Please note that if the user has borrowed LisUSD and has not yet paid it back, the amount of collateral they can withdraw is less than the original deposit amount, as some collateral must remain to secure the outstanding debt. -3. GemJoin: Interaction calls the \`exit()\` method, which transfers the specified amount of collateral from GemJoin back to the user. -4. Vat: It records the user's collateral information, updating the system to reflect that the collateral has left the system +1. `Interaction` first calls `drip(token)` (refresh the dynamic rate and accrue interest into the Vat) and `poke(token)` (refresh the collateral price). +2. It converts the requested lisUSD amount into a normalized debt delta `dart = hayAmount * RAY / rate` (rounded up) and calls `vat.frob` to record the new debt against the position. **No interest is charged at this point** — `frob` only increases `art`; the stability fee accrues over time through the Vat `rate` accumulator and is paid in lisUSD when the debt is repaid. +3. `Interaction` moves the freshly minted internal balance (`vat.move`) and calls `hayJoin.exit` to mint `hayAmount` of lisUSD to the borrower. +4. A debt snapshot is taken for reward accounting. -
+The borrow reverts if it would leave the position unsafe (enforced inside `vat.frob`). Emits `Borrow(user, collateral, collateralAmount, amount, liquidationPrice)`. -This process ensures that the user's collateral is accurately withdrawn and returned, while the system records the change in collateral status. +### c. Payback (repay) lisUSD -
+
-**e. Stake LisUSD** +**`payback(address token, uint256 hayAmount)`** — repay your own debt. +**`paybackFor(address token, uint256 hayAmount, address borrower)`** — repay another address's debt. -
+1. `Interaction` calls `drip` and `poke`, so the repayment settles the debt **including all accrued interest** at the current `rate`. +2. lisUSD is pulled from the caller and burned via `hayJoin.join`. If the amount covers the full debt the position is closed (`art` set to 0); otherwise the debt is reduced proportionally (`dart = realAmount * RAY / rate`). +3. `vat.frob` reduces the position's `art` by `dart`. +4. A debt snapshot is taken for reward accounting. -
+Emits `Payback(borrower, collateral, amount, debt, liquidationPrice)`. -1. User Initiates Staking: The user calls the \`join()\` method to stake a specified amount of LisUSD. This amount of LisUSD is then transferred to the Jar contract. -2. Jar: The Jar contract records the following information: - 1. The user’s staked LisUSD balance. - 2. Increase the total amount of LisUSD staked by all users. - 3. The time when the user staked the LisUSD. -3. ListaDistributor: ListaDistributor takes a snapshot of the user’s balance from the Jar. it will be used for calculating and distributing future rewards to the user. +### d. Withdraw collateral -
+
-**f. Unstake LisUSD** +**`withdraw(address participant, address token, uint256 dink)`** -
+1. `Interaction` calls `drip` and `poke`. +2. If the position has outstanding debt, only collateral above the amount required to keep the position safe can be withdrawn; the Vat enforces this and reverts otherwise. +3. Collateral is moved out via `vat.flux` and returned to the user via the `GemJoin` adapter (`gem.exit`). +4. A debt snapshot is taken for reward accounting. -1. User Initiates Unstaking: The user calls the \`exit()\` method to unstake a specified amount of LisUSD. -2. Jar: This amount of LisUSD, plus any rewarded amount, is transferred back to the user. It also records the following information: - 1. The user’s staked LisUSD balance is reduced by the unstaked amount X. - 2. The total staked amount of LisUSD is reduced by the unstaked amount X. - 3. A record of the withdrawal is saved. -3. ListaDistributor: The ListaDistributor takes a snapshot of the user’s balance and records the user's staked LisUSD balance for future reward calculations. +As with deposits, tokens with a registered provider are withdrawn through that provider (unless `providerCompatibilityMode` is on). Emits `Withdraw(participant, amount)`. -
+## Liquidation & Dutch auctions -This process user will only interact with the Jar contract, it is responsible for managing and distributing interest to the participants of who stakes LisUSD. +When a position falls below its liquidation ratio (`ink * spot < art * rate`), its collateral is sold for lisUSD through a **Dutch auction**: the price starts above the oracle price and decreases over time until a buyer takes it. The `Interaction` contract exposes the auction surface; the work is done by `Dog`, `Clipper`, and an `Abacus` price curve. -
+> The example numbers below are **illustrative only** to show the shape of the math. The live liquidation parameters — penalty (`Dog.chop`), starting-price multiplier (`buf`), reset window (`tail`), reset price-drop threshold (`cusp`), and keeper incentives (`tip`, `chip`) — are governance/manager-adjustable on-chain values and are **not** published here. -**g. Liquidation** +### g.1 Starting an auction
-The flowchart shows how an auction is being kick started. - -**g.1 How an auction get started** - -Determine Price and Ratio: +**`startAuction(address token, address user, address keeper)`** → `Dog.bark` → `Clipper.kick`. -* Price of 1 unit of collateral: $2 -* Collateral ratio: 66% -* Collateral price based on collateral ratio: 2∗0.66=$1.322 +`Dog.bark` checks the position is unsafe, grabs the collateral and debt out of the position into the auction, adds the liquidation penalty (`chop`) to compute the lisUSD target (`tab`) the auction must raise, and kicks a `Clipper` auction. The Clipper sets the starting price: -User Deposit and Borrow Limit: +``` +top = collateralFeedPrice * buf +``` -* Assume User deposits 10 units of collateral: 10∗2=$20 -* Borrow limit: 20∗0.66=$13.2 -* Assume User borrows $13.2 of lisUSD: 13.2 lisUSD +where `buf >= 1` lifts the start price above the current oracle price. A keeper that triggers the auction can receive an incentive (a flat `tip` plus a `chip` proportion of `tab`). -Monitor Collateral Price Decrease: +*Illustrative shape:* with collateral at \$1.80 and a starting-price multiplier `buf`, the auction would open at `1.80 * buf`; the lisUSD to raise would be the outstanding debt scaled up by `(1 + penalty)`. -* Assume the price of 1 unit of collateral decreases to: $1.8 -* Collateral unit price with safety margin: 1.8∗0.66=$1.188 -* Current worth of collateral with safety margin: 1.188∗10=$11.88 -* Determine liquidation status: - * 13.2−11.88=$1.32 (positive difference indicates liquidation) +Emits `AuctionStarted(token, user, amount, price)` and the Dog's `Bark` event. -Liquidation Auction Preparation: +### g.2 Buying from an auction -* Amount of collateral that goes to Dutch auction: 10 units -* Liquidation penalty (fixed by Lista governance): 13% of the debt -* Debt to cover in the auction: 13.2∗1.13=$14.916 -* Buffer (percentage similar to liquidation penalty, fixed by Lista governance): 2% -* Starting auction price (top): 1.8∗1.02=$1.836 - -Trigger Auction: - -* Somebody triggers the auction and gets a tip + chip as a reward (details described later). +
-
+**`buyFromAuction(address token, uint256 auctionId, uint256 collateralAmount, uint256 maxPrice, address receiverAddress, bytes data)`** → `Clipper.take`. -**g.2 Buy from Auction** +The price decreases from `top` according to the auction's configured **Abacus** curve. Three curves are implemented in `abaci.sol`; a given Clipper is wired to one of them via its `calc` setting: -
+| Curve | Price as a function of elapsed time `dur` | +| --- | --- | +| `LinearDecrease` | `price = top * (tau - dur) / tau` (reaches 0 at `tau`). | +| `StairstepExponentialDecrease` | `price = top * cut^(dur / step)` — drops by a fixed factor `cut` every `step` seconds. | +| `ExponentialDecrease` | `price = top * cut^dur` — continuous per-second exponential decay. | -The flowchart shows how the user buys collateral from an auction. +(There is also `AlwaysOneDollarCalc`, a fixed \$1 curve used only for specific USD-denominated LP collateral.) -
+A buyer calls `take` with an upper bound on collateral and a `maxPrice` slippage guard; the auction never collects more lisUSD than its `tab`, and partial buys must leave a non-dusty remainder (`Clipper.chost`). Emits `Liquidation(urn, token, collateralAmount, leftover)` and the Clipper's `Take` event. -Example: +### g.3 Resetting an auction -Auction Start and Price Decrease: +**`resetAuction(address token, uint256 auctionId, address keeper)`** → `Clipper.redo`. -* Auction starts, and the price gradually decreases. -* Liquidator can participate to buy a customized amount of liquidated collateral. -* Linear decrease of price (subject to disruption by specific conditions): - * Formula: $$f(x) = x * e^{2 pi i \xi x}$$ - * Example: 1.836∗((3600-600)/3600)=$1.53 +An auction can be reset (its price re-initialized from the current feed) once it has run too long or its price has fallen too far. `Clipper.status` flags a reset when either condition holds: -Conditions to Pause Auction: +``` +needsRedo = (now - startTime) > tail // ran longer than the reset window + || price / top < cusp // price dropped past the reset threshold +``` -* The auction can pause because of one of two conditions: - * Tail (specific amount of time elapsed, fixed by Lista governance) - * Cusp (% of price drop; 40% start auction price, fixed by Lista governance) -* Once either requirement is met, the auction will be restarted. +The keeper that resets it may receive the same `tip` + `chip` incentive (subject to the auction still being economically meaningful). The concrete `tail` and `cusp` values are on-chain risk parameters and are not published here. -
+## Earn / staking note -**g.3 Restart Auction** +Historically, lisUSD holders could stake into the **Jar** (`jar.sol`) to earn rewards. The Jar contract still exists in the repository but is **largely deprecated**: the live lisUSD staking / saving-rate product is now the **LisUSDPoolSet** / **EarnPool** stack (the Stable Pool / lisUSD Saving Rate layer). New integrations should target that layer rather than the Jar. See the [Stable Pool (PSM)](../../introduction/collateral-debt-position-lisusd/lisusd/stable-pool-price-stability-module-psm.md) and [lisUSD Saving Rate (LSR)](../../introduction/collateral-debt-position-lisusd/lisusd/lisusd-saving-rate-lsr.md) docs. -Wait until someone restarts the auction. The restarter gets a tip + chip as a reward. +## See also -* Tip (flat fee, fixed by Lista governance): 5 lisUSD -* Chip (dynamic fee, fixed by Lista governance): 0 lisUSD +- [Flash Loan](flash-loan.md) — ERC-3156 flash minting of lisUSD. +- [Smart Contract](smart-contract.md) — deployed CDP contract addresses. +- [Algorithmic Market Operations (AMO)](../../introduction/collateral-debt-position-lisusd/lisusd/algorithmic-market-operations-amo/README.md) — the public borrow-rate formula and parameters. diff --git a/for-developer/credit-loans/loan-lifecycle.md b/for-developer/credit-loans/loan-lifecycle.md index c4c412f..adfddd1 100644 --- a/for-developer/credit-loans/loan-lifecycle.md +++ b/for-developer/credit-loans/loan-lifecycle.md @@ -1,18 +1,85 @@ # Loan Lifecycle -| Phase | Timing | Description | +This page describes the on-chain lifecycle of a Lista Credit loan, from credit-limit provisioning through borrowing, repayment, the grace/penalty window, and reinstatement. Every step below is implemented in the deployed `CreditToken` and `CreditBroker` contracts; the off-chain credit assessment is only ever surfaced on-chain as a Merkle root, never as scoring inputs or formulas. + +> The values in this page (grace period, penalty rate, LISTA discount, no-interest period, Merkle waiting period) are the **current on-chain configuration**. They are manager/governance-adjustable on-chain and should be read live from the contracts, not treated as fixed product guarantees. + +## Roles in the flow + +| Actor | Responsibility | +| --- | --- | +| Borrower | Supplies `CreditToken` collateral, borrows the loan token, repays principal + interest (+ penalty if overdue). | +| Off-chain assessment | Computes eligibility/limits off-chain and publishes **only a Merkle root** to `CreditToken`. Scoring inputs and logic are not on-chain. | +| `BOT` role | Rotates the `CreditToken` Merkle root (two-step, time-locked) and triggers liquidation of penalized positions. Operational keeper. | +| `CreditBroker` | Orchestrates collateral supply/withdraw and fixed-term borrow/repay against the underlying Moolah market. | +| `CreditBrokerInterestRelayer` | Receives interest and penalty from the broker and supplies it into the Moolah Credit Vault as revenue. | + +## Lifecycle + +| Phase | Trigger | What happens on-chain | +| --- | --- | --- | +| 1. Credit-limit publication | Off-chain assessment → `CreditToken.setPendingMerkleRoot` then `acceptMerkleRoot` (`BOT`) | A new Merkle root is staged, then accepted after a waiting period (current default 6h, min 6h). Accepting increments `versionId`. Only the root is published — not the underlying scores. | +| 2. Score sync (mint/burn) | `syncCreditScore(user, score, proof)` — also run implicitly by broker actions | The user's `(score, proof)` is verified against the current root and `versionId`. `CreditToken` mints up to the new score or burns down to it (1 token = 1 USD of credit capacity). | +| 3. Supply collateral | `supplyCollateral(amount, score, proof)` | Broker syncs the score, pulls `CreditToken` from the user, and supplies it as collateral into the broker's Moolah market. | +| 4. Borrow (open fixed position) | `supplyAndBorrow(collateralAmount, borrowAmount, termId, score, proof)` or `borrow(amount, termId, score, proof)` | Guarded by `noDebt` and `userNotPenalized`. A `FixedLoanPosition` is created with the term's `apr`, `start`, `end`, and `termType`; the broker borrows from Moolah and transfers the loan token to the user. Emits `FixedLoanPositionCreated`. | +| 5. Interest accrual | Time | Interest grows per the position's `FixedTermType` (see below). The broker's per-user `peek` price decreases as broker debt outpaces the 0%-rate Moolah debt, so Moolah perceives the rising risk. | +| 6. Repayment | `repay(amount, posId, onBehalf)` / `repayAndWithdraw(...)` / `repayInterestWithLista(...)` | Interest is repaid first, then principal. Interest (and any penalty) is supplied to the Credit Vault via the relayer. Emits `RepaidFixedLoanPosition`. | +| 7. Grace period | `end` → `end + graceConfig.period` | The loan is overdue but **no penalty applies yet**. The borrower can still repay normally. Current grace period: 3 days. | +| 8. Penalty window | After `end + graceConfig.period` (`dueTime`) | The position is "penalized." A penalty of `penaltyRate × (remaining principal + accrued interest)` is required, and the position **must be repaid in full** in a single repayment. Current penalty rate: 15% (`graceConfig.penaltyRate = 15 * 1e25`). | +| 9. Liquidation / bad debt | `CreditBroker.liquidate(borrower, posId)` (`BOT` only) | A penalized, not-already-bad-debt position is written off via `Moolah.liquidateBrokerPosition`; the position is flagged `isBadDebt`. See [Bad Debt Handling](bad-debt-handling.md). | +| 10. Reinstatement | Full repayment of the penalized/bad-debt position | The position is paid off and removed; `PaidOffPenalizedPosition` is emitted when a penalty was settled. Once outstanding debt (`CreditToken.debtOf`) is cleared, the user may borrow again. | + +## Fixed-term interest modes (`FixedTermType`) + +Each fixed-term product (`FixedTermAndRate`) carries one of two interest modes. The mode is fixed at borrow time and stored on the position; it determines how interest is computed. + +| Mode | Enum | How interest is charged | +| --- | --- | --- | +| Accrue per-second | `ACCRUE_INTEREST` (0) | Interest accrues linearly over time: `principal × aprPerSecond × elapsed`, where `aprPerSecond = (apr − 1) / 365 days`. Accrual is capped at the position `end`. There is no upfront charge. | +| Upfront | `UPFRONT_INTEREST` (1) | Full term interest is owed once the no-interest window passes: `principal × (apr − 1) × term / 365 days`. Within `noInterestUntil` (set to `start + graceConfig.noInterestPeriod`, current default 1 second) the interest is 0. | + +`apr` is scaled by `RATE_SCALE = 1e27`, expressed as `1 + rate` (e.g. a 10% APR is `1.10 * 1e27`). + +## Repayment rules + +* **Interest first, then principal.** A repayment pays down outstanding interest before principal. Interest collected is supplied to the Credit Vault through the relayer. +* **Penalized positions must be repaid in full.** Once past `dueTime`, a repayment must cover remaining principal + remaining interest + penalty in one transaction, or it reverts. +* **Repay interest with LISTA (discounted).** `repayInterestWithLista(loanTokenAmount, listaAmount, posId, onBehalf)` lets a borrower settle outstanding interest using LISTA at a discount — current discount 20% (`listaDiscountRate = 20 * 1e25`), i.e. only 80% of the interest value need be paid in LISTA. The LISTA is sent to the relayer; the equivalent loan-token interest is credited from the relayer to the broker. This path is only available while the relayer has `allowTransferLoan` enabled. +* **Minimum loan check.** After any borrow/repay, each non-cleared position must remain at or above the Moolah market `minLoan`. + +## Borrow guards + +A new fixed position can only be opened when both broker modifiers pass: + +| Guard | Condition | Source | | --- | --- | --- | -| 1. Credit assessment | Daily (off-chain) | Lista scores eligible Binance MPC wallets, assigns credit limits, and publishes a new Merkle root to `CreditBroker`. | -| 2. CreditToken mint/burn | After root update | User submits Merkle proof to claim or adjust `CreditToken` balance. Mint for increased score, burn for reduced score. | -| 3. Supply and borrow | T = 0 | Borrower calls `CreditBroker.supplyAndBorrow(amount, proof)`. Proof is validated, broker calls `Moolah.borrow()` on behalf of user, upfront interest is deducted, and net amount is disbursed. | -| 4. Repayment | On or before maturity | Borrower calls `repay()`. Principal returns to vault. | -| 5. Grace period | Maturity to maturity + grace | Loan is overdue but penalty is not yet applied. Borrower can still repay principal plus origination fee. | -| 6. Penalty applied | After grace period | A `3%` penalty is applied to outstanding debt and routed to Credit Vault. | -| 7. Blacklist | After penalty trigger | Merkle-gated operations (supply/withdraw/borrow) are suspended until full repayment including penalty. | -| 8. Reinstatement | After full repayment | Blacklist is removed. Credit score may be restored gradually, based on policy. | - -## Notes - -* Interest is typically collected upfront in the broker flow. -* `CreditBrokerInterestRelayer` routes interest to Credit Vault. -* Merkle proof checks are central to both credit-limit updates and borrow authorization. +| `noDebt` | `CreditToken.debtOf(borrower) == 0` — the user must have no outstanding credit-token debt (excess balance over current score). | `CreditBroker._borrow` | +| `userNotPenalized` | The user has no fixed position past its `dueTime`. | `CreditBroker._borrow` | + +Before borrowing, the broker calls `_tryWithdrawAndBurnDebt`, which withdraws and burns any `CreditToken` debt so the `noDebt` guard can be satisfied. + +## Credit-limit gating (Merkle root) + +* `CreditToken` is non-transferable except by whitelisted `TRANSFERER`s (the brokers and Moolah). `1 CreditToken = 1 USD` of borrow capacity. +* A user's balance is reconciled to their published score on every `syncCreditScore`: the broker passes `(score, proof)` and the token verifies the leaf `keccak256(abi.encode(chainid, creditToken, user, score, versionId))` against the current root before minting up to, or burning down to, the score. +* The root is rotated in two steps by the `BOT` role: `setPendingMerkleRoot` → wait `waitingPeriod` (current default 6h, minimum 6h) → `acceptMerkleRoot` (increments `versionId`). A pending root can be cancelled by the `MANAGER` via `revokePendingMerkleRoot`. + +> The off-chain credit assessment that determines eligibility and limits is **not** documented here and is not on-chain. Only the resulting Merkle root is published. Do not infer scoring inputs from the on-chain flow. + +## Key events + +| Event | Emitted when | +| --- | --- | +| `AcceptMerkleRoot` | A new credit-limit Merkle root is accepted (`CreditToken`). | +| `ScoreSynced` | A user's score/balance is reconciled to the current root (`CreditToken`). | +| `FixedLoanPositionCreated` | A new fixed-term position is opened. | +| `RepaidFixedLoanPosition` | A repayment is applied (interest/principal/penalty breakdown in the event). | +| `RepayInterestWithLista` | Interest is settled using discounted LISTA. | +| `PaidOffPenalizedPosition` | A penalized position is fully paid off. | +| `PositionLiquidate` | A penalized position is liquidated and flagged as bad debt. | + +## Related + +* [Bad Debt Handling](bad-debt-handling.md) — how a flagged position is written off and how the loss hits Credit Vault shareholders. +* [Smart Contract](smart-contract.md) — contract roster and roles. +* Canonical addresses: [BSC Credit (Lista Lending)](../lista-lending/smart-contract-bsc-credit.md). diff --git a/for-developer/dex/README.md b/for-developer/dex/README.md index 2a90645..20b44fb 100644 --- a/for-developer/dex/README.md +++ b/for-developer/dex/README.md @@ -1,7 +1,35 @@ # V3 Dex -Lista V3 Dex is a concentrated-liquidity DEX on BNB Smart Chain (Uniswap V3-style), with positions held as NFTs via the `NonfungiblePositionManager` and swaps routed through the `SwapRouter`. +## Overview + +Lista V3 Dex is a concentrated-liquidity automated market maker (AMM) on BNB Smart Chain. Liquidity providers concentrate capital within chosen price ranges (tick ranges) rather than across the full price curve, and each liquidity position is held as an ERC-721 NFT. The same pools are the on-chain liquidity engine behind Lista's Smart Lending / Smart Swap experience. The protocol is a Uniswap V3 fork, so the canonical Uniswap V3 model and tooling apply directly. + +## Core components + +| Contract | Role | +| -------- | ---- | +| `ListaV3Factory` | Deploys and registers one `ListaV3Pool` per `(token0, token1, fee)` triple, and controls which fee tiers are enabled. | +| `ListaV3Pool` | Per-pair, per-fee AMM pool. Holds concentrated liquidity, tracks the current price/tick, and executes swaps, mints, burns, collects, and flash loans. CREATE2-deployed by the factory. | +| `NonfungiblePositionManager` (NPM) | Wraps liquidity positions as ERC-721 NFTs (`Lista V3 Positions NFT`, symbol `LISTA-V3`). Entry point for mint / increaseLiquidity / decreaseLiquidity / collect / burn. Deployed behind a `TransparentUpgradeableProxy`. | +| `SwapRouter` | Stateless entry point for executing swaps (single-hop and multi-hop) against the pools, with WBNB wrapping/unwrapping, slippage, and deadline handling. | + +## Fee tiers + +The factory seeds three fee tiers at deployment. Fees are denominated in hundredths of a basis point (units of `1e-6`): + +| Fee tier | `fee` value | Tick spacing | +| -------- | ----------- | ------------ | +| 0.05% | `500` | `10` | +| 0.30% | `3000` | `60` | +| 1.00% | `10000` | `200` | + +The factory owner can enable additional fee tiers on-chain via `enableFeeAmount`; the three tiers above are those configured at deployment. + +## Uniswap V3 compatibility + +Because Lista V3 Dex is a Uniswap V3 fork, the canonical Uniswap V3 concentrated-liquidity model applies: `sqrtPriceX96`/tick price representation, tick-range liquidity, tick crossing on swaps, and ERC-721 positions. Standard Uniswap V3 SDKs and integration patterns work against these contracts. Use the deployed Lista addresses (see [Smart Contract](smart-contract.md)) in place of the upstream Uniswap deployment, and refer to the official Uniswap V3 documentation for the underlying math. One deployment-specific caveat: off-chain pool-address derivation must use Lista's own pool init-code hash (or read `factory.getPool`) — see [Mechanics](mechanics.md#building-on-lista-v3). ## Contents +* [Mechanics](mechanics.md) * [Smart Contract](smart-contract.md) diff --git a/for-developer/dex/mechanics.md b/for-developer/dex/mechanics.md new file mode 100644 index 0000000..e5b80d3 --- /dev/null +++ b/for-developer/dex/mechanics.md @@ -0,0 +1,213 @@ +# Architecture & Mechanics + +Lista V3 Dex is a concentrated-liquidity automated market maker (AMM) deployed on BNB Smart Chain. + +> **Lista V3 is a fork of Uniswap V3.** The canonical Uniswap V3 model, math, contract interfaces, and SDKs (e.g. `@uniswap/v3-sdk`, `@uniswap/sdk-core`) apply directly. Contracts are renamed (`ListaV3Factory`, `ListaV3Pool`) but their interfaces, events, and behaviour match Uniswap V3. This page documents the model as deployed by Lista and points out the deployment-specific details (fee tiers, NFT name, upgradeable position manager, and — importantly for off-chain address derivation — a Lista-specific pool init-code hash; see [Building on Lista V3](#building-on-lista-v3)). For deep math and reference, use the Uniswap V3 whitepaper and developer docs. + +For deployed contract addresses see [Smart Contract](smart-contract.md). + +## Component contracts + +| Contract | Role | +| -------- | ---- | +| `ProxyAdmin` | Admin of the upgradeable proxies in the deployment (e.g. the position manager). | +| `ListaV3Factory` | Deploys and registers one pool per `(token0, token1, fee)` tuple; owns the enabled fee-tier → tick-spacing table. | +| `ListaV3Pool` | The core AMM contract for a single pair + fee tier. Holds liquidity, `slot0` price/tick state, fee accumulators, ticks, and positions. Created by the factory. | +| `NonfungiblePositionManager` | Periphery contract that wraps liquidity positions as ERC-721 NFTs and handles mint / increase / decrease / collect / burn. Deployed behind a transparent proxy. | +| `NonfungibleTokenPositionDescriptor` | Renders on-chain token metadata (`tokenURI`) for position NFTs. | +| `SwapRouter` | Periphery contract for executing single-hop and multi-hop swaps with slippage and deadline protection. | + +The `*Pool` rows in [Smart Contract](smart-contract.md) are individual pools deployed by the factory, not a singleton — every distinct `(token0, token1, fee)` combination is its own `ListaV3Pool` instance. + +## AMM model + +### Factory → Pool + +A pool is uniquely identified by the ordered token pair and the fee tier. Tokens are sorted by address so that `token0 < token1`; the factory stores the pool both ways in `getPool[token0][token1][fee]`. + +```solidity +// ListaV3Factory +function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool); +function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool); +function feeAmountTickSpacing(uint24 fee) external view returns (int24); + +event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool); +``` + +`createPool` derives `tickSpacing` from the fee tier, deploys the pool deterministically, and emits `PoolCreated`. A newly created pool must be initialized once via `initialize(sqrtPriceX96)` before liquidity can be added. The pool address is deterministic (CREATE2 from the pool key), but Lista's pools have their own bytecode, so off-chain derivation must use **Lista's** pool init-code hash — not Uniswap's — or simply read `factory.getPool(token0, token1, fee)`. See [Building on Lista V3](#building-on-lista-v3) for the hash. + +### Positions as ERC-721 (NonfungiblePositionManager) + +Liquidity positions are minted as ERC-721 NFTs. The collection is named **`Lista V3 Positions NFT`** with symbol **`LISTA-V3`**. Unlike Uniswap's immutable position manager, the Lista deployment is an upgradeable contract (transparent proxy + `initialize()`); the ERC-721 token semantics are otherwise standard and it supports EIP-712 permit. + +```solidity +// NonfungiblePositionManager (struct fields verbatim from source) +struct MintParams { + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; +} + +function mint(MintParams calldata params) + external payable + returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + +function increaseLiquidity(IncreaseLiquidityParams calldata params) + external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1); + +function decreaseLiquidity(DecreaseLiquidityParams calldata params) + external payable returns (uint256 amount0, uint256 amount1); + +function collect(CollectParams calldata params) + external payable returns (uint256 amount0, uint256 amount1); + +function burn(uint256 tokenId) external payable; +``` + +Reading a position returns the full Uniswap V3 position tuple: + +```solidity +function positions(uint256 tokenId) external view returns ( + uint96 nonce, + address operator, + address token0, + address token1, + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint128 liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 +); +``` + +`tokensOwed0` / `tokensOwed1` are the uncollected fees accrued to the position; they are updated on liquidity changes and realized via `collect`. + +### Swaps (SwapRouter) + +Swaps are routed through the `SwapRouter`, which supports exact-input and exact-output, single-hop and multi-hop. Single-hop selects a pool by `(tokenIn, tokenOut, fee)`; multi-hop uses a packed `path` (20-byte token, 3-byte fee, 20-byte token, …). + +```solidity +// SwapRouter +struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; +} + +function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); +function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); +function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); +function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); +``` + +`amountOutMinimum` / `amountInMaximum` enforce slippage limits, `deadline` bounds execution time, and `sqrtPriceLimitX96` optionally caps the price movement within the swap. + +## Concentrated liquidity & tick ranges + +Liquidity is provided over a bounded price range `[tickLower, tickUpper]` rather than across the full price curve. Each position concentrates capital within its range, so liquidity is only "active" while the pool's current tick lies inside the range. + +- **Ticks** discretize price. The price at tick `i` is `1.0001^i`. Valid ticks run from `MIN_TICK = -887272` to `MAX_TICK = 887272` (canonical Uniswap V3 bounds). +- **tickSpacing** restricts which ticks may bound a position. Positions must use ticks that are multiples of the pool's `tickSpacing`. Larger spacing means coarser price granularity but cheaper tick-crossing during swaps. +- A position only earns fees while the active price is within `[tickLower, tickUpper]`. Out-of-range positions earn nothing until price re-enters. + +## Price state: `sqrtPriceX96` and the current tick + +The pool stores its current price and tick in `slot0`: + +```solidity +struct Slot0 { + uint160 sqrtPriceX96; // current price as sqrt(token1/token0) in Q64.96 fixed point + int24 tick; // current tick = floor(log_1.0001(price)) + uint16 observationIndex; + uint16 observationCardinality; + uint16 observationCardinalityNext; + uint8 feeProtocol; + bool unlocked; +} +Slot0 public slot0; // public getter returns the struct fields as a tuple +``` + +- `sqrtPriceX96` is the square root of the price (`token1` per `token0`) encoded as a Q64.96 fixed-point number. It is the canonical price representation used throughout the math libraries. +- `tick` is the current tick, `floor(log_1.0001(price))`. +- A pool is price-initialized once via `initialize(sqrtPriceX96)`, which sets `slot0` and emits `Initialize(sqrtPriceX96, tick)`. +- The pool also maintains an oracle observation ring buffer (`observations`, capacity 65535); cumulative tick / seconds-per-liquidity accumulators support TWAP-style queries. `increaseObservationCardinalityNext` grows the buffer. + +To convert between `sqrtPriceX96`, `tick`, and a human-readable price, use the standard Uniswap V3 math (`TickMath`, `SqrtPriceMath`) or `@uniswap/v3-sdk`. + +## Fee accrual & fee growth + +Swaps charge the pool's fee tier on the input amount. Accrued fees are tracked using the global fee-growth accumulators and per-tick / per-position bookkeeping, exactly as in Uniswap V3: + +```solidity +function feeGrowthGlobal0X128() external view returns (uint256); +function feeGrowthGlobal1X128() external view returns (uint256); +``` + +- `feeGrowthGlobal{0,1}X128` are monotonically increasing accumulators of fees per unit of liquidity, in Q128.128 fixed point. +- Each position records `feeGrowthInside{0,1}LastX128` at its last touch; uncollected fees are the difference between current in-range fee growth and that snapshot, scaled by the position's liquidity. +- Fees are credited to `tokensOwed0` / `tokensOwed1` and remain claimable until withdrawn via `collect` (pool level) or the position manager's `collect`. +- A separate protocol fee (`feeProtocol`) can be switched on per pool by the factory owner; when zero (the default at initialization), all swap fees accrue to LPs. + +## Swaps and tick crossing + +During a swap the pool walks the price along the curve, consuming liquidity tick by tick. When the price crosses an initialized tick, that tick's net liquidity is applied (added or removed) and its fee-growth-outside values flip, so positions begin or stop earning. A swap emits: + +```solidity +event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, // pool price after the swap + uint128 liquidity, // active liquidity after the swap + int24 tick // pool tick after the swap +); +``` + +Liquidity events follow the same Uniswap V3 shapes: `Mint`, `Burn`, `Collect`, `Flash`, plus `Initialize`. Flash loans of either token are supported via `flash(...)` and repaid (with fee) in the `IUniswapV3FlashCallback`. + +## Fee tiers and tick spacing + +Fee tiers are set in the `ListaV3Factory`. Each tier maps a fee (in hundredths of a basis point, i.e. `1e-6`) to a `tickSpacing`. The following tiers are enabled in the factory constructor: + +| Fee tier | `fee` (uint24) | `tickSpacing` | Typical use | +| -------- | -------------- | ------------- | ----------- | +| 0.05% | `500` | `10` | Stable / correlated pairs | +| 0.30% | `3000` | `60` | Most pairs | +| 1.00% | `10000` | `200` | Exotic / volatile pairs | + +Additional tiers can be added by the factory owner via `enableFeeAmount(fee, tickSpacing)`; a tier, once enabled, can never be removed, and `feeAmountTickSpacing(fee)` returns `0` for tiers that are not enabled. Read `feeAmountTickSpacing(fee)` (or watch the `FeeAmountEnabled` event) for the authoritative live list rather than hard-coding tiers. + +```solidity +function enableFeeAmount(uint24 fee, int24 tickSpacing) external; // factory owner only +event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); +``` + +## Building on Lista V3 + +Because Lista V3 is a Uniswap V3 fork: + +- Use `@uniswap/v3-sdk` and `@uniswap/sdk-core` for price/tick math, position math, and route encoding, pointing them at the Lista contract addresses and BNB Smart Chain (chainId 56). +- The pool address is deterministic from `(factory, token0, token1, fee)` via CREATE2, **but you must use Lista's own pool init-code hash** — `0xa93d35cf943696a95cabbe3aa4b3d87ea5387169face953a337716fc15136ca2` — in the `PoolAddress` computation. Uniswap V3's default init-code hash yields the wrong address for Lista pools. Alternatively, read `factory.getPool(token0, token1, fee)` on-chain instead of deriving it. +- Pool, factory, and periphery interfaces match Uniswap V3 `IUniswapV3Pool` / `IUniswapV3Factory` / `INonfungiblePositionManager` / `ISwapRouter`, so existing V3 integrations port with minimal changes (renamed contracts, the upgradeable position manager, and the Lista-specific NFT name/symbol aside). + +## Related pages + +- [Smart Contract](smart-contract.md) — deployed contract addresses on BNB Smart Chain. +- [V3 Dex overview](README.md) — section landing page. diff --git a/for-developer/liquid-staking-slisbnb/smart-contract.md b/for-developer/liquid-staking-slisbnb/smart-contract.md index 80dac79..074ab59 100644 --- a/for-developer/liquid-staking-slisbnb/smart-contract.md +++ b/for-developer/liquid-staking-slisbnb/smart-contract.md @@ -1,6 +1,30 @@ # Smart Contract +slisBNB is Lista's yield-bearing liquid staking token for BNB. BNB is staked through `ListaStakeManager`, which delegates to BSC validators via the native StakeHub system contract and mints slisBNB to the staker. slisBNB can be bridged to Ethereum through LayerZero OFT contracts (lock on BSC, mint on Ethereum). See [Mechanics](mechanics.md) for the staking flow and [Cross-Chain Bridge](cross-chain-bridge.md) for the bridge architecture. +> The addresses below are manually curated for this page (not auto-synced). They are cross-checked against the Lista DAO contract source and the [CDP Smart Contract](../collateral-debt-position/smart-contract.md) reference. Verify against the relevant block explorer before integrating. -
NameContract Address
slisBNBhttps://bscscan.com/address/0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B
StakeManagerhttps://bscscan.com/address/0x1adB950d8bB3dA4bE104211D5AB038628e477fE6
slisBNB on ethereum:https://etherscan.io/address/0x44388Ef3bc730BDE8670a3b4831281dd7E89C584
slisBNB OFTAdaptor https://bscscan.com/address/0x82f5bcD1473BDa5794239D01073797093a413f02
+## Core Contracts (BNB Chain) +| Contract | Description | Address | +| --- | --- | --- | +| slisBNB | Liquid staking token (ERC-20, name `Staked Lista BNB`, symbol `slisBNB`) | [0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B](https://bscscan.com/address/0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B) | +| ListaStakeManager | Stakes BNB through the native StakeHub and mints/burns slisBNB | [0x1adB950d8bB3dA4bE104211D5AB038628e477fE6](https://bscscan.com/address/0x1adB950d8bB3dA4bE104211D5AB038628e477fE6) | + +## Cross-Chain (LayerZero OFT) + +slisBNB uses the LayerZero OFT standard. The adapter locks slisBNB on BNB Chain; the OFT mints/burns the equivalent on Ethereum. LayerZero endpoint IDs (EIDs): BNB Chain `30102`, Ethereum `30101`. + +| Contract | Chain | Role | Address | +| --- | --- | --- | --- | +| ListaOFTAdapter | BNB Chain (EID 30102) | Lock/unlock adapter over the canonical slisBNB token | [0x837CB07f6B8a98731856092457524FF37b25E7B3](https://bscscan.com/address/0x837CB07f6B8a98731856092457524FF37b25E7B3) | +| ListaOFT | Ethereum (EID 30101) | Mint/burn OFT representing slisBNB on Ethereum | [0xf9B24C9364457Ea85792179D285855753549eBAa](https://etherscan.io/address/0xf9B24C9364457Ea85792179D285855753549eBAa) | + +## Native BSC System Contracts + +`ListaStakeManager` delegates, redelegates, and undelegates through BSC's built-in staking system contracts. These are fixed protocol addresses on BNB Chain. + +| Contract | Description | Address | +| --- | --- | --- | +| StakeHub | Native BSC staking hub used for delegation, redelegation, and reward claims | [0x0000000000000000000000000000000000002002](https://bscscan.com/address/0x0000000000000000000000000000000000002002) | +| GovBNB | Native BSC governance token minted on delegation | [0x0000000000000000000000000000000000002005](https://bscscan.com/address/0x0000000000000000000000000000000000002005) | diff --git a/for-developer/lista-lending/README.md b/for-developer/lista-lending/README.md index efa8ba6..1d67ecd 100644 --- a/for-developer/lista-lending/README.md +++ b/for-developer/lista-lending/README.md @@ -15,6 +15,10 @@ In addition, it supports two integration patterns for external systems: * [Protocol Extensions](protocol-extensions.md) * [Integration Patterns](integration-patterns.md) +* [Contract & Interface Reference](contract-reference.md) +* [Events & Callbacks](events-and-callbacks.md) +* [Interest Rate Model (IRM)](irm.md) +* [Smart Lending & StableSwap](stableswap-integration.md) * [Smart Contract](smart-contract.md) * [BSC Core](smart-contract-bsc-core.md) * [BSC Lending Brokers](smart-contract-bsc-brokers.md) diff --git a/for-developer/lista-lending/contract-reference.md b/for-developer/lista-lending/contract-reference.md new file mode 100644 index 0000000..5000aa2 --- /dev/null +++ b/for-developer/lista-lending/contract-reference.md @@ -0,0 +1,304 @@ +# Contract & Interface Reference + +This page is the interface-level reference for the **Moolah** core contract — the singleton that holds every Lista Lending market. It is intended for auditors, security researchers, and integrators calling the contract directly (e.g. from Solidity, or from a non-TypeScript stack that cannot use the [SDK](../sdk.md)). + +Moolah is powered by Morpho and built on the Morpho Blue smart contracts, then extended with Lista-specific controls. Every market — regardless of collateral, oracle, or IRM — lives inside this one contract and is addressed by a market `Id`. For deployed addresses, see the [Smart Contract](smart-contract.md) reference; this page does not restate address tables. For higher-level flows see [Integration Patterns](integration-patterns.md); for the callback interfaces and emitted events see [Events & Callbacks](events-and-callbacks.md). + +All facts below are derived from `src/moolah/Moolah.sol` and `src/moolah/interfaces/IMoolah.sol`. + +--- + +## Types & structs + +### `Id` + +```solidity +type Id is bytes32; +``` + +A market's identifier is the `keccak256` hash of the ABI-encoded `MarketParams` (five 32-byte words, in struct order). Two markets with identical parameters therefore collapse to the same `Id`. + +```solidity +Id id = Id.wrap(keccak256(abi.encode(marketParams))); +``` + +The same value is exposed on-chain via the `idToMarketParams(Id)` view (the reverse mapping) and is used as the key for `position`, `market`, providers, brokers, and both whitelists. + +### `MarketParams` + +The immutable definition of a market. Once created, these values cannot change; a new combination is simply a new market. + +| Field | Type | Meaning | +| --- | --- | --- | +| `loanToken` | `address` | The asset supplied and borrowed. | +| `collateralToken` | `address` | The asset posted as collateral. | +| `oracle` | `address` | Price source; must expose `peek(address)` returning an 8-decimal price. | +| `irm` | `address` | Interest Rate Model. Must be enabled via `isIrmEnabled`. | +| `lltv` | `uint256` | Liquidation Loan-To-Value, scaled by `WAD` (`1e18`). Must be enabled via `isLltvEnabled`. | + +### `Position` + +Per-user, per-market state: `mapping(Id => mapping(address => Position))`. + +| Field | Type | Meaning | +| --- | --- | --- | +| `supplyShares` | `uint256` | Supply-side shares owned by the user. | +| `borrowShares` | `uint128` | Borrow-side (debt) shares owed by the user. | +| `collateral` | `uint128` | Collateral balance posted by the user. | + +> For `feeRecipient`, `supplyShares` does not include fee shares accrued since the last interest accrual. + +### `Market` + +Aggregate per-market accounting: `mapping(Id => Market)`. + +| Field | Type | Meaning | +| --- | --- | --- | +| `totalSupplyAssets` | `uint128` | Total supplied assets (excl. interest since last accrual). | +| `totalSupplyShares` | `uint128` | Total supply shares outstanding. | +| `totalBorrowAssets` | `uint128` | Total borrowed assets (excl. interest since last accrual). | +| `totalBorrowShares` | `uint128` | Total borrow shares outstanding. | +| `lastUpdate` | `uint128` | Timestamp of the last interest accrual. A non-zero value means the market exists. | +| `fee` | `uint128` | Market fee on interest, scaled by `WAD`. Capped at `MAX_FEE`. | + +Shares use OpenZeppelin's virtual-shares method (`VIRTUAL_SHARES = 1e6`, `VIRTUAL_ASSETS = 1`) to mitigate share-price manipulation; conversions round in the protocol's favor. + +### `Authorization` & `Signature` + +Used by `setAuthorizationWithSig` for gasless (EIP-712) authorization delegation. + +| `Authorization` field | Type | Meaning | +| --- | --- | --- | +| `authorizer` | `address` | The account granting authorization (and the recovered signer). | +| `authorized` | `address` | The account being authorized to manage `authorizer`'s positions. | +| `isAuthorized` | `bool` | The authorization value to set. | +| `nonce` | `uint256` | Must equal `authorizer`'s current `nonce`; prevents replay. | +| `deadline` | `uint256` | Signature expiry timestamp. | + +`Signature` is a standard `{ uint8 v; bytes32 r; bytes32 s; }`. The EIP-712 domain is `EIP712Domain(uint256 chainId,address verifyingContract)`; the type hash is `Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)`. The current chain's separator is readable via `domainSeparator()`. + +--- + +## Core external functions + +Signatures below are exact. Where a function takes both `assets` and `shares`, **exactly one must be zero** (enforced by `exactlyOneZero`); the other side is derived by the shares math. + +### Market creation + +```solidity +function createMarket(MarketParams memory marketParams) external; +``` + +Creates a market. Reverts unless `irm` and `lltv` are enabled, `loanToken`/`collateralToken`/`oracle` are non-zero, and the market does not already exist. Records `lastUpdate = block.timestamp`, sets the market `fee` to `defaultMarketFee`, stores the reverse mapping, probes the oracle for both tokens, and initializes the IRM. Emits `CreateMarket`. When the `OPERATOR` role has members, only an `OPERATOR` may create markets; otherwise creation is permissionless. + +### Supply / withdraw (loan-side liquidity) + +```solidity +function supply(MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data) + external returns (uint256 assetsSupplied, uint256 sharesSupplied); +``` + +Supplies loan assets on behalf of `onBehalf`, minting supply shares. Accrues interest first. If `data` is non-empty, calls back `onMoolahSupply` on the caller before pulling tokens via `transferFrom`. Subject to the per-market whitelist and vault blacklist, and to the `minLoan` floor on the resulting position. + +```solidity +function withdraw(MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) + external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn); +``` + +Burns supply shares of `onBehalf` and sends assets to `receiver`. Caller must be `onBehalf` or authorized. Reverts if it would push `totalBorrowAssets` above `totalSupplyAssets` (`insufficient liquidity`). + +### Borrow / repay (debt-side) + +```solidity +function borrow(MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) + external returns (uint256 assetsBorrowed, uint256 sharesBorrowed); +``` + +Borrows loan assets against `onBehalf`'s collateral and sends them to `receiver`. Accrues interest, then requires the resulting position to be healthy (`_isHealthy`) and the market to keep `totalBorrowAssets <= totalSupplyAssets`. Access depends on the market's routing: if a **broker** is set, only the broker may borrow (and must be the receiver); else if the caller is the market's **provider**, the receiver must be that provider; otherwise the caller must be `onBehalf` or authorized. Subject to the whitelist and the `minLoan` floor. + +```solidity +function repay(MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data) + external returns (uint256 assetsRepaid, uint256 sharesRepaid); +``` + +Repays `onBehalf`'s debt, burning borrow shares. Accrues interest first; if `data` is non-empty, calls back `onMoolahRepay` before pulling tokens. If a broker is set for the market, only the broker may repay. A partial repay that would leave debt below `minLoan` reverts. + +### Collateral management + +```solidity +function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data) external; +``` + +Posts collateral for `onBehalf`. Does **not** accrue interest (not required, saves gas). If a provider is set for the collateral token, only the provider may call. If `data` is non-empty, calls back `onMoolahSupplyCollateral` before pulling tokens. Subject to the whitelist. + +```solidity +function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; +``` + +Withdraws collateral to `receiver`. Accrues interest, then requires the position to remain healthy. If a provider is set, only the provider may call (and must be the receiver); otherwise the caller must be `onBehalf` or authorized. + +### Liquidation + +```solidity +function liquidate(MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes memory data) + external returns (uint256 seized, uint256 repaid); +``` + +Liquidates an unhealthy `borrower`. Exactly one of `seizedAssets` / `repaidShares` must be zero; the other is derived using the liquidation incentive factor `min(MAX_LIQUIDATION_INCENTIVE_FACTOR, 1 / (1 - LIQUIDATION_CURSOR * (1 - lltv)))`. Accrues interest, checks the position is unhealthy at the oracle price, seizes collateral to the caller, and pulls the repaid loan assets. If collateral reaches zero, remaining debt is realized as bad debt against the market's supply side. Gated by the per-market **liquidation whitelist**. If `data` is non-empty, `onMoolahLiquidate` is called before the repay transfer. + +```solidity +function liquidateBrokerPosition(MarketParams memory marketParams, address borrower, uint256 badDebtShares) + external returns (uint256 seized, uint256 repaid); +``` + +Broker-only path to write down `badDebtShares` of a broker-market borrower against the market's supply side — the loss is socialized across all suppliers by reducing `totalSupplyAssets`, and the fee recipient's position is not touched. Callable only by the market's broker; this path performs **no** health check, so the broker is responsible for determining that the position is unhealthy. + +### Flash loans + +```solidity +function flashLoan(address token, uint256 assets, bytes calldata data) external; +``` + +Transfers `assets` of `token` to the caller, invokes `onMoolahFlashLoan`, then pulls the same `assets` back in the same transaction. Has access to the contract's entire balance of `token` (all markets' liquidity and collateral combined). The flash **fee is zero**. Reverts if the token is on the flash-loan blacklist or `assets` is zero. Not ERC-3156 compliant. Note this function is `whenNotPaused` but is **not** guarded by `nonReentrant` (unlike supply/withdraw/borrow/repay/collateral/liquidate). + +### Authorization + +```solidity +function setAuthorization(address authorized, bool newIsAuthorized) external; +function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; +``` + +`setAuthorization` sets whether `authorized` may manage `msg.sender`'s positions across all markets. `setAuthorizationWithSig` does the same via an EIP-712 signature, consuming the authorizer's `nonce` and requiring `block.timestamp <= deadline`. Anyone may always manage their own positions regardless of these flags. + +### Interest accrual + +```solidity +function accrueInterest(MarketParams memory marketParams) external; +``` + +Permissionlessly accrues interest for a market: queries `borrowRate` from the IRM, compounds it over the elapsed time (three-term Taylor approximation of continuous compounding), grows `totalBorrowAssets`/`totalSupplyAssets`, and mints fee shares to `feeRecipient` when the market `fee` is non-zero. Emits `AccrueInterest`. The interest-bearing entry points — `supply`, `withdraw`, `borrow`, `repay`, `withdrawCollateral`, and `liquidate` — accrue interest internally before touching balances. `supplyCollateral` deliberately skips accrual (collateral neither earns nor owes interest, so it saves gas), and `flashLoan` does not accrue. + +--- + +## View functions + +| View | Signature | Returns | +| --- | --- | --- | +| Position | `position(Id id, address user)` | The `Position` struct for a user in a market. | +| Market | `market(Id id)` | The `Market` accounting struct. | +| Params by id | `idToMarketParams(Id id)` | The `MarketParams` for a market (reverse of the `Id` hash). | +| Authorization | `isAuthorized(address authorizer, address authorized)` | Whether `authorized` may manage `authorizer`'s positions. | +| Nonce | `nonce(address authorizer)` | The authorizer's current EIP-712 nonce. | +| Domain separator | `domainSeparator()` | The EIP-712 domain separator for the current chain. | +| Health | `isHealthy(MarketParams, Id, address borrower)` | Whether a borrower's position is healthy. | +| Price | `getPrice(MarketParams)` | Collateral price in loan-asset terms, scaled by `10 ** (36 + quoteDecimals - baseDecimals)`. | +| IRM enabled | `isIrmEnabled(address irm)` | Whether an IRM may be used for new markets. | +| LLTV enabled | `isLltvEnabled(uint256 lltv)` | Whether an LLTV may be used for new markets. | +| Fee recipient | `feeRecipient()` | The global fee recipient. | +| Default fee | `defaultMarketFee()` | Fee applied to newly created markets. | + +**Computing a market `Id`** off-chain is `keccak256(abi.encode(loanToken, collateralToken, oracle, irm, lltv))`; on-chain the equivalent is `MarketParamsLib.id(marketParams)`. The reverse (`Id` → params) is `idToMarketParams`. + +### Relevant constants + +Verified from `ConstantsLib.sol` / `MathLib.sol`. These are compile-time constants of the interface, not adjustable parameters. + +| Constant | Value | Role | +| --- | --- | --- | +| `WAD` | `1e18` | Fixed-point scale for rates, fees, and `lltv`. | +| `ORACLE_PRICE_SCALE` | `1e36` | Internal price scale used in health/liquidation math. | +| `MAX_FEE` | `0.25e18` (25%) | Upper bound on any market fee. | +| `LIQUIDATION_CURSOR` | `0.3e18` | Input to the liquidation incentive formula. | +| `MAX_LIQUIDATION_INCENTIVE_FACTOR` | `1.15e18` | Cap on the liquidation incentive factor. | + +--- + +## Lista-specific extensions + +These controls are additions on top of the Morpho Blue base and are what distinguishes Moolah from a vanilla deployment. See also [Protocol Extensions](protocol-extensions.md) for the conceptual overview. + +### Minimum loan floor (`minLoanValue` / `minLoan`) + +```solidity +function minLoan(MarketParams memory marketParams) external view returns (uint256); +function minLoanValue() external view returns (uint256); +``` + +`minLoanValue` is a protocol-wide floor denominated in the oracle's 8-decimal USD unit. `minLoan(marketParams)` converts it into the market's loan-token amount using the oracle price and token decimals. Supply, borrow, and repay all enforce that a **non-zero** resulting position stays at or above this floor, which prevents dust positions that are uneconomical to liquidate. The value is a current, manager-adjustable on-chain value (`setMinLoanValue`, `MANAGER` role), not a fixed promise. + +### Liquidation whitelist + +```solidity +function batchToggleLiquidationWhitelist(Id[] memory ids, address[][] memory accounts, bool isAddition) external; // MANAGER +function getLiquidationWhitelist(Id id) external view returns (address[] memory); +function isLiquidationWhitelist(Id id, address account) external view returns (bool); +``` + +Per-market allowlist of eligible liquidators. When a market's list is **empty, liquidation is open to anyone**; once any address is added, only listed addresses may call `liquidate` for that market. + +### Supply/borrow whitelist + +```solidity +function setWhiteList(Id id, address account, bool isAddition) external; // MANAGER +function getWhiteList(Id id) external view returns (address[] memory); +function isWhiteList(Id id, address account) external view returns (bool); +``` + +Optional per-market gate on `supply`, `supplyCollateral`, and `borrow` (checked against `onBehalf`). An **empty list means the market is open**; a non-empty list restricts these actions to listed accounts. + +### Vault & flash-loan blacklists + +```solidity +function setVaultBlacklist(address account, bool isBlacklisted) external; // MANAGER +function vaultBlacklist(address account) external view returns (bool); +function setFlashLoanTokenBlacklist(address token, bool isBlacklisted) external; // MANAGER +function flashLoanTokenBlacklist(address token) external view returns (bool); +``` + +`vaultBlacklist` blocks a blacklisted `onBehalf` from receiving new supply. `flashLoanTokenBlacklist` disables `flashLoan` for a specific token. + +### Providers & brokers routing + +```solidity +function setProvider(Id id, address provider, bool isAddition) external; // MANAGER +function providers(Id id, address token) external view returns (address); +function setMarketBroker(Id id, address broker, bool isAddition) external; // MANAGER +function brokers(Id id) external view returns (address); +``` + +A **provider** is registered per market and token, and its effect depends on which token it backs. A **collateral-token** provider is an exclusive gate: when set, only the provider may call `supplyCollateral`/`withdrawCollateral` (and on withdraw the `receiver` must be the provider). A **loan-token** provider is *not* an exclusive gate — it only constrains the borrow receiver: if the loan-token provider is the `borrow` caller, `receiver` must be the provider; ordinary authorized borrowers are unaffected, and plain `supply`/`withdraw` of the loan asset are never provider-gated. A **broker** is registered per market and, when present, gates borrow and repay origination — only the broker may `borrow` or `repay`. For a broker market, `_isHealthy` still prices collateral with the **plain market price** (`_getPrice` with `user = address(0)`) but replaces the borrower's debt with the broker's total debt (Moolah principal + interest accrued at the broker); `liquidateBrokerPosition` is a separate broker-only bad-debt write-down that performs **no** health check (health responsibility sits with the broker). `setMarketBroker` validates that the broker's `LOAN_TOKEN`, `COLLATERAL_TOKEN`, and `MARKET_ID` match the market. See [Integration Patterns](integration-patterns.md) for how these layers compose. + +### Resilient oracle routing + +Markets read prices through the `oracle` in `MarketParams`, which exposes `peek(address asset)` returning an 8-decimal price. Lista typically points this at a **Resilient Oracle** that aggregates a main / pivot / fallback source per asset with a freshness tolerance, and at asset-specific adaptors for tokens such as `slisBNB`. When a broker is present and a user address is supplied, price resolution can route through the broker so the effective price reflects the user's fixed-term/fixed-rate position; health checks otherwise use the plain oracle price. Oracle selection is a per-market safety responsibility. See [Multi-Oracle](../multi-oracle.md). + +### Reentrancy guard + +The core mutating entry points — `supply`, `withdraw`, `borrow`, `repay`, `supplyCollateral`, `withdrawCollateral`, `liquidate`, and `liquidateBrokerPosition` — are `nonReentrant`. `flashLoan` is intentionally **not** `nonReentrant` (its safety comes from the same-transaction repayment invariant). All of the above are additionally `whenNotPaused`. + +### Role-based access & pausing + +Moolah uses OpenZeppelin `AccessControlEnumerable`. Privileged operations are gated by role, never by a hard-coded address: + +| Role | Scope (examples) | +| --- | --- | +| `DEFAULT_ADMIN_ROLE` | Authorizes contract upgrades; root of role administration. | +| `MANAGER` | Enables IRMs/LLTVs, sets fees, whitelists/blacklists, providers/brokers, `minLoanValue`, unpause. | +| `PAUSER` | Pauses the contract (halting the `whenNotPaused` functions). | +| `OPERATOR` | When any `OPERATOR` exists, gates `createMarket`. | + +Pausing (`pause`/`unpause`) freezes the guarded entry points without affecting views. + +### Upgradeability + +Moolah is a UUPS-upgradeable contract (`UUPSUpgradeable`), initialized once via `initialize(admin, manager, pauser, minLoanValue)` with the constructor disabled. Upgrades are authorized only by `DEFAULT_ADMIN_ROLE` (`_authorizeUpgrade`). Operationally, upgrade authority is held behind a timelock; see [Protocol Extensions](protocol-extensions.md). + +--- + +## See also + +- [Integration Patterns](integration-patterns.md) — provider and broker layers, and how direct calls route through them. +- [Events & Callbacks](events-and-callbacks.md) — the `onMoolah*` callback interfaces and emitted events for indexers. +- [Protocol Extensions](protocol-extensions.md) — conceptual overview of the Lista-specific controls. +- [Smart Contract](smart-contract.md) — deployed contract addresses. +- [Moolah Lending SDK](../sdk.md) — the TypeScript path that wraps these calls. diff --git a/for-developer/lista-lending/events-and-callbacks.md b/for-developer/lista-lending/events-and-callbacks.md new file mode 100644 index 0000000..6320229 --- /dev/null +++ b/for-developer/lista-lending/events-and-callbacks.md @@ -0,0 +1,178 @@ +# Events & Callbacks + +This page is the integration reference for the two ways Moolah hands control back to your contracts and off-chain systems: + +- **Callbacks** — synchronous, in-transaction hooks that Moolah invokes on the caller mid-execution (before it pulls the tokens it is owed), enabling atomic flows such as leverage loops, flash-liquidations, and position migrations. +- **Events** — the logs Moolah, the IRM, and the vault layer emit for off-chain indexers, subgraphs, and monitoring. + +For function signatures, structs, and the `Id`/`MarketParams` types referenced below, see the [Contract & Interface Reference](contract-reference.md). For the higher-level provider/broker model, see [Integration Patterns](integration-patterns.md). + +--- + +## Callbacks + +Moolah follows the Morpho-style callback pattern: several core entry points optionally call back into `msg.sender` after updating internal accounting but **before** transferring in the assets the caller owes. This lets an integrator source those assets inside the same transaction — for example, minting/swapping collateral, or repaying a loan out of seized collateral — so the whole operation is atomic and reverts as a unit if any leg fails. + +### Firing rule + +Each entry point takes a trailing `bytes data` argument. For **supply**, **repay**, **supplyCollateral**, and **liquidate**, the callback fires **only when `data` is non-empty** (`data.length > 0`); pass empty `data` to skip it. **`flashLoan` is the exception**: its callback is always invoked, because the flash loan has no purpose without it. + +The callback is always made on `msg.sender`, so the contract that calls Moolah must be the contract that implements the interface. + +### Callback interfaces + +All five interfaces are declared in `moolah/interfaces/IMoolahCallbacks.sol`. + +| Interface | Method | Fired by | Fires when | +| --- | --- | --- | --- | +| `IMoolahSupplyCallback` | `onMoolahSupply(uint256 assets, bytes data)` | `supply` | `data` non-empty | +| `IMoolahRepayCallback` | `onMoolahRepay(uint256 assets, bytes data)` | `repay` | `data` non-empty | +| `IMoolahSupplyCollateralCallback` | `onMoolahSupplyCollateral(uint256 assets, bytes data)` | `supplyCollateral` | `data` non-empty | +| `IMoolahLiquidateCallback` | `onMoolahLiquidate(uint256 repaidAssets, bytes data)` | `liquidate` | `data` non-empty | +| `IMoolahFlashLoanCallback` | `onMoolahFlashLoan(uint256 assets, bytes data)` | `flashLoan` | always | + +```solidity +interface IMoolahSupplyCallback { + function onMoolahSupply(uint256 assets, bytes calldata data) external; +} + +interface IMoolahRepayCallback { + function onMoolahRepay(uint256 assets, bytes calldata data) external; +} + +interface IMoolahSupplyCollateralCallback { + function onMoolahSupplyCollateral(uint256 assets, bytes calldata data) external; +} + +interface IMoolahLiquidateCallback { + function onMoolahLiquidate(uint256 repaidAssets, bytes calldata data) external; +} + +interface IMoolahFlashLoanCallback { + function onMoolahFlashLoan(uint256 assets, bytes calldata data) external; +} +``` + +The first argument is the settled amount for that action: assets supplied/repaid/supplied-as-collateral, `repaidAssets` for a liquidation, or the flash-loaned amount. The `data` argument is the same opaque payload you passed into the originating call, so you can encode a route, a target market, or step parameters and decode them inside the callback. + +### Execution order (the atomic window) + +Within the originating call, Moolah: + +1. Updates its own accounting. +2. Emits the corresponding event (`Supply`, `Repay`, `SupplyCollateral`, `Liquidate`, `FlashLoan`). +3. For the actions where Moolah owes **you** assets, transfers them out now — the seized collateral for `liquidate`, the loaned token for `flashLoan`. (`supply`, `repay`, and `supplyCollateral` have no outbound transfer; you are the one paying in.) +4. Calls back into `msg.sender` (subject to the firing rule above), handing you control while the position is mid-update. +5. On return, pulls in the assets **you** owe via `transferFrom` (the supplied/repaid/collateral token, or the flash-loaned amount plus zero fee). If your balance or allowance is short, the whole transaction reverts. + +Your callback body runs in step 4, so it must leave `msg.sender` holding enough of the inbound token (and allowance to Moolah) for step 5 to succeed. + +### Typical atomic flows + +| Flow | Callback used | Sketch | +| --- | --- | --- | +| **Leverage loop** | `onMoolahSupplyCollateral` | Deposit seed collateral with non-empty `data`; in the callback, borrow the loan token, swap it to more collateral, and top up so the net collateral requirement is met on return — opening a levered position in one transaction. | +| **Flash-liquidation** | `onMoolahLiquidate` | Call `liquidate` with `data`; Moolah sends you the seized collateral first, then calls back. Swap that collateral to the loan token (e.g. via [Lista DEX](../dex/README.md) or any DEX) so you can cover the `repaidAssets` pulled on return — no upfront capital. | +| **Debt / position migration** | `onMoolahFlashLoan` | Flash-loan the loan token, use it to repay a position elsewhere, withdraw the freed collateral, re-supply it into a Moolah market, borrow, and repay the flash loan — all atomically. | +| **Zero-capital deleverage** | `onMoolahRepay` | Repay a borrow with `data`; in the callback withdraw collateral and swap part of it to fund the repayment that Moolah pulls on return. | + +> Callbacks are a low-level primitive. If you are integrating in TypeScript and do not need custom atomic routing, the [Moolah Lending SDK](../sdk.md) builds standard supply/borrow/repay transactions for you. + +--- + +## Events + +### Core market events (`Moolah`) + +Emitted by the Moolah core contract; declared in `moolah/libraries/EventsLib.sol`. `Id` is the market identifier (the keccak hash of `MarketParams`). Indexed parameters are marked below. + +| Event | Parameters (indexed in **bold**) | +| --- | --- | +| `CreateMarket` | **`Id id`**, `MarketParams marketParams` | +| `Supply` | **`Id id`**, **`address caller`**, **`address onBehalf`**, `uint256 assets`, `uint256 shares` | +| `Withdraw` | **`Id id`**, `address caller`, **`address onBehalf`**, **`address receiver`**, `uint256 assets`, `uint256 shares` | +| `SupplyCollateral` | **`Id id`**, **`address caller`**, **`address onBehalf`**, `uint256 assets` | +| `WithdrawCollateral` | **`Id id`**, `address caller`, **`address onBehalf`**, **`address receiver`**, `uint256 assets` | +| `Borrow` | **`Id id`**, `address caller`, **`address onBehalf`**, **`address receiver`**, `uint256 assets`, `uint256 shares` | +| `Repay` | **`Id id`**, **`address caller`**, **`address onBehalf`**, `uint256 assets`, `uint256 shares` | +| `Liquidate` | **`Id id`**, **`address caller`**, **`address borrower`**, `uint256 repaidAssets`, `uint256 repaidShares`, `uint256 seizedAssets`, `uint256 badDebtAssets`, `uint256 badDebtShares` | +| `FlashLoan` | **`address caller`**, **`address token`**, `uint256 assets` | +| `AccrueInterest` | **`Id id`**, `uint256 prevBorrowRate`, `uint256 interest`, `uint256 feeShares` | +| `SetAuthorization` | **`address caller`**, **`address authorizer`**, **`address authorized`**, `bool newIsAuthorized` | +| `IncrementNonce` | **`address caller`**, **`address authorizer`**, `uint256 usedNonce` | +| `SetFee` | **`Id id`**, `uint256 newFee` | +| `SetFeeRecipient` | **`address newFeeRecipient`** | +| `EnableIrm` | **`address irm`** | +| `EnableLltv` | `uint256 lltv` | + +Indexing notes for integrators: + +- **Position reconstruction.** A supplier's balance evolves through `Supply`/`Withdraw`; a borrower's through `Borrow`/`Repay`/`Liquidate`; collateral through `SupplyCollateral`/`WithdrawCollateral`. All are keyed by the indexed `Id` and the indexed `onBehalf` (or `borrower`) address. +- **Interest.** `AccrueInterest` carries the `prevBorrowRate` used for the elapsed period, the `interest` added to the market, and the `feeShares` minted to the fee recipient. Note that the fee recipient can receive shares during accrual **without** a `Supply` event, so reconstruct fee-recipient balances from `AccrueInterest`, not `Supply`. +- **Repay/Liquidate rounding.** `repaidAssets` on both `Repay` and `Liquidate` may exceed the market's `totalBorrowAssets` by 1 due to rounding — account for this when reconciling. +- **Bad debt.** On `Liquidate`, non-zero `badDebtAssets`/`badDebtShares` mean the position was underwater and the loss was socialized to suppliers of that market. +- **Authorization.** Watch `SetAuthorization` (and `IncrementNonce` for signature-based authorizations) to track which managers can act on a position via the `authorized`/`onBehalf` model. + +Lista-specific administrative events (provider/broker wiring, whitelists, blacklists, min-loan, default fee) are also declared in the same `EventsLib` and documented alongside the functions that emit them in the [Contract & Interface Reference](contract-reference.md). + +### Interest-rate model events (`InterestRateModel`) + +The adaptive-curve IRM (`src/interest-rate-model/InterestRateModel.sol`) emits its own events. `BorrowRateUpdate` is emitted each time Moolah accrues interest against a market that uses this IRM (Moolah calls `IIrm.borrowRate` during `_accrueInterest`), so it is the finest-grained rate signal available to indexers. + +| Event | Parameters (indexed in **bold**) | +| --- | --- | +| `BorrowRateUpdate` | **`Id id`**, `uint256 avgBorrowRate`, `uint256 rateAtTarget` | +| `BorrowRateCapUpdate` | **`Id id`**, `uint256 oldRateCap`, `uint256 newRateCap` | +| `BorrowRateFloorUpdate` | **`Id id`**, `uint256 oldRateFloor`, `uint256 newRateFloor` | +| `MinCapUpdate` | `uint256 oldMinCap`, `uint256 newMinCap` | + +`avgBorrowRate` is the average per-second rate (scaled by WAD) applied over the accrual interval; `rateAtTarget` is the model's rate at target utilization after the update. Fixed-rate broker markets use a separate `FixedRateIrm`, which emits `SetBorrowRate(Id indexed id, int256 newBorrowRate)` when its rate is set. + +### Vault events (`MoolahVault`) + +The ERC-4626 curator vault layer emits its own lifecycle and configuration events; declared in `moolah-vault/libraries/EventsLib.sol`. In addition to the standard ERC-4626 `Deposit`/`Withdraw`/`Transfer` events, integrators typically index: + +| Event | Parameters (indexed in **bold**) | +| --- | --- | +| `CreateMoolahVault` | **`address moolahVault`**, `address implementation`, `address managerTimeLock`, `address curatorTimeLock`, `uint256 timeLockDelay`, **`address caller`**, `address manager`, `address curator`, `address guardian`, **`address asset`**, `string name`, `string symbol` | +| `SetCap` | **`address caller`**, **`Id id`**, `uint256 cap` | +| `SubmitCap` | **`address caller`**, **`Id id`**, `uint256 cap` | +| `SetSupplyQueue` | **`address caller`**, `Id[] newSupplyQueue` | +| `SetWithdrawQueue` | **`address caller`**, `Id[] newWithdrawQueue` | +| `ReallocateSupply` | **`address caller`**, **`Id id`**, `uint256 suppliedAssets`, `uint256 suppliedShares` | +| `ReallocateWithdraw` | **`address caller`**, **`Id id`**, `uint256 withdrawnAssets`, `uint256 withdrawnShares` | +| `AccrueInterest` | `uint256 newTotalAssets`, `uint256 feeShares` | +| `UpdateLastTotalAssets` | `uint256 updatedTotalAssets` | +| `SetCurator` | **`address newCurator`** | +| `SetIsAllocator` | **`address allocator`**, `bool isAllocator` | +| `SetFee` | **`address caller`**, `uint256 newFee` | +| `SetFeeRecipient` | **`address newFeeRecipient`** | +| `Skim` | **`address caller`**, **`address token`**, `uint256 amount` | + +Timelocked/governance actions on the vault emit the matching `Submit*` / `Set*` / `Revoke*` pairs (`SubmitTimelock`/`SetTimelock`, `SubmitGuardian`/`SetGuardian`, `SubmitMarketRemoval`/`RevokePendingMarketRemoval`, etc.), also in the same `EventsLib`. + +To reconstruct how a vault allocates deposits across underlying Moolah markets, track `SetSupplyQueue`/`SetWithdrawQueue` for ordering, `SetCap`/`SubmitCap` for per-market limits, and `ReallocateSupply`/`ReallocateWithdraw` for actual moves. + +### Vault allocator events (`VaultAllocator`) + +The public reallocation helper (`src/vault-allocator/VaultAllocator.sol`) emits, from `vault-allocator/libraries/EventsLib.sol`: + +| Event | Parameters (indexed in **bold**) | +| --- | --- | +| `PublicWithdrawal` | **`address sender`**, **`address vault`**, **`Id id`**, `uint256 withdrawnAssets` | +| `PublicReallocateTo` | **`address sender`**, **`address vault`**, **`Id supplyMarketId`**, `uint256 suppliedAssets` | +| `SetFlowCaps` | **`address sender`**, **`address vault`**, `FlowCapsConfig[] config` | +| `SetAdmin` | **`address sender`**, **`address vault`**, `address admin` | +| `SetFee` | **`address sender`**, **`address vault`**, `uint256 fee` | +| `TransferFee` | **`address sender`**, **`address vault`**, `uint256 amount`, **`address feeRecipient`** | + +A public reallocation emits one `PublicWithdrawal` per market it pulls from and a `PublicReallocateTo` for the market it supplies into, all correlated by the indexed `vault`. + +--- + +## See also + +- [Contract & Interface Reference](contract-reference.md) — full function signatures, structs, and Lista-specific extensions. +- [Integration Patterns](integration-patterns.md) — provider vs. broker integration models. +- [Moolah Lending SDK](../sdk.md) — TypeScript builder for standard supply/borrow/repay flows. +- [Smart Contract](smart-contract.md) — deployed contract addresses. diff --git a/for-developer/lista-lending/irm.md b/for-developer/lista-lending/irm.md new file mode 100644 index 0000000..1957002 --- /dev/null +++ b/for-developer/lista-lending/irm.md @@ -0,0 +1,153 @@ +# Interest Rate Model + +Moolah markets delegate borrow-rate computation to a pluggable Interest Rate Model (IRM). Each market stores an `irm` address, and on every interest accrual Moolah calls that contract to obtain the current borrow rate. + +Two IRM implementations ship with the protocol: + +| IRM | Model | Where used | +| --- | --- | --- | +| `InterestRateModel` | Adaptive-curve rate as a function of utilization, with a rate-at-target that adapts over time | Standard utilization-based markets | +| `FixedRateIrm` | Administratively set fixed per-market rate | Fixed term / fixed rate broker products (see [Integration Patterns](integration-patterns.md)) | + +Every IRM implements the shared `IIrm` interface: + +```solidity +interface IIrm { + /// @notice Borrow rate per second (scaled by WAD); may modify storage. + function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); + + /// @notice Borrow rate per second (scaled by WAD); read-only. + function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); +} +``` + +All rates in the IRM are expressed **per second, scaled by WAD (`1e18`)**. Moolah compounds this per-second rate over the elapsed time between interactions when accruing interest. + +## Adaptive Curve Model + +`InterestRateModel` computes the borrow rate as a curve function of utilization around a target utilization. The curve is anchored by a `rateAtTarget` value that itself adapts over time: when utilization sits above target, `rateAtTarget` drifts upward; when below target, it drifts downward. This pushes the market back toward the target utilization without governance intervention. + +### Utilization and error + +Utilization is total borrows divided by total supply: + +```text +utilization = totalBorrowAssets / totalSupplyAssets (WAD-scaled, 0 if no supply) +``` + +The model measures how far utilization is from target as a normalized error `err`, mapped so that `err = -1` at zero utilization, `err = 0` at target utilization, and `err = +1` at full (100%) utilization: + +```text +errNormFactor = utilization > TARGET_UTILIZATION ? (WAD - TARGET_UTILIZATION) : TARGET_UTILIZATION +err = (utilization - TARGET_UTILIZATION) / errNormFactor +``` + +### The curve + +Given the current `rateAtTarget` and the error, the instantaneous rate is a piecewise-linear curve. Below target the rate is scaled down toward `rateAtTarget / CURVE_STEEPNESS`; above target it is scaled up toward `rateAtTarget * CURVE_STEEPNESS`: + +```text +r = ((1 - 1/C) * err + 1) * rateAtTarget if err < 0 + ((C - 1) * err + 1) * rateAtTarget if err >= 0 +``` + +where `C = CURVE_STEEPNESS`. At `err = 0` (utilization exactly at target) the borrow rate equals `rateAtTarget`. + +### Adaptation of `rateAtTarget` + +`rateAtTarget` is stored per market (`mapping(Id => int256) rateAtTarget`) and is updated only on stateful interactions. Between two updates it moves according to `ADJUSTMENT_SPEED * err` per second, applied through a continuous-compounding (exponential) adaptation over the elapsed time: + +```text +linearAdaptation = (ADJUSTMENT_SPEED * err) * elapsed +endRateAtTarget = clamp(startRateAtTarget * exp(linearAdaptation), MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET) +``` + +The average rate returned to Moolah over the elapsed interval is approximated with a trapezoidal rule (using the start, mid, and end `rateAtTarget`), so the reported rate reflects the whole interval rather than only its endpoints. On a market's first interaction (`rateAtTarget == 0`), the model seeds both the average and end rate-at-target with `INITIAL_RATE_AT_TARGET`. + +The resulting average rate is additionally bounded by a per-market cap and floor (`rateCap`, `rateFloor`) and a protocol-wide minimum cap (`minCap`); when no explicit cap is set, `DEFAULT_RATE_CAP` applies. These bounds are manager/bot-adjustable on-chain values. + +## Current on-chain constants + +The following constants are compiled into `ConstantsLib` for the adaptive-curve model. They are fixed in the deployed implementation (changing them requires a contract upgrade), and are stated here as current on-chain values. All rate constants are **per second, WAD-scaled**; the human-readable annualized figures come from the source comments. + +| Constant | On-chain value | Meaning | +| --- | --- | --- | +| `CURVE_STEEPNESS` | `4 ether` (`4e18`) | Curve steepness `C = 4`. Rate ranges from `rateAtTarget / 4` at 0% utilization to `rateAtTarget * 4` at 100%. | +| `TARGET_UTILIZATION` | `0.9 ether` (`0.9e18`) | Target utilization = 90%. | +| `ADJUSTMENT_SPEED` | `50 ether / 365 days` | Adaptation speed; `rateAtTarget` moves at `ADJUSTMENT_SPEED * err` per second (≈ 50 / year at full error). | +| `INITIAL_RATE_AT_TARGET` | `0.04 ether / 365 days` | Seed rate-at-target on first interaction; 4% APR-equivalent at target (rate spans ~1%–16% across the curve). | +| `MIN_RATE_AT_TARGET` | `0.001 ether / 365 days` | Lower clamp on `rateAtTarget`; 0.1% at target (curve minimum ~0.025%). | +| `MAX_RATE_AT_TARGET` | `2.0 ether / 365 days` | Upper clamp on `rateAtTarget`; 200% at target (curve maximum ~800%). | +| `DEFAULT_RATE_CAP` | `uint256(0.3 ether) / 365 days` | Default per-second cap on the returned borrow rate (30% annualized) when a market has no explicit `rateCap`. | + +## `borrowRateView` vs `borrowRate` + +Both entry points evaluate the same adaptive-curve math, but differ in whether they persist the updated `rateAtTarget`: + +| | `borrowRateView` | `borrowRate` | +| --- | --- | --- | +| Mutability | `view` (no state change) | State-changing | +| Caller | Anyone | `MOOLAH` only (reverts otherwise) | +| Effect | Computes the average rate for the current `market` snapshot without writing | Computes the rate, writes `rateAtTarget[id] = endRateAtTarget`, emits `BorrowRateUpdate` | +| Use case | Off-chain quoting, simulations, front-ends | Called by Moolah during interest accrual on `supply` / `borrow` / `repay` / etc. | + +```solidity +function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); +function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); +``` + +Because `borrowRate` requires `msg.sender == MOOLAH`, integrators reading a rate directly should call `borrowRateView`. The value it returns is the average per-second rate that *would* be applied for the given `market` snapshot; it does not advance `rateAtTarget`. Note that `borrowRateView` uses the stored `rateAtTarget` and the elapsed time implied by `market.lastUpdate`, so its result reflects adaptation that has not yet been committed on-chain. + +## Reading the current rate on-chain + +Two complementary reads are available: + +1. **The anchor rate.** `rateAtTarget(Id id)` returns the stored per-second rate-at-target for a market (the height of the curve). It is `0` for a market that has never accrued interest. + + ```solidity + int256 anchor = IInterestRateModel(irm).rateAtTarget(id); + ``` + +2. **The current borrow rate.** Fetch the market's live `MarketParams` and `Market` structs from Moolah, then call `borrowRateView`: + + ```solidity + uint256 ratePerSecond = IIrm(irm).borrowRateView(marketParams, market); // WAD-scaled + ``` + +### Per-second → APY conversion + +The returned rate is a per-second rate scaled by WAD. Moolah accrues interest with continuous (per-second Taylor-compounded) growth, so the borrow APY is: + +```text +ratePerSecondFloat = ratePerSecond / 1e18 +borrowAPY = exp(ratePerSecondFloat * secondsPerYear) - 1 // secondsPerYear = 365 * 24 * 60 * 60 = 31_536_000 +``` + +```typescript +const SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31_536_000 + +// ratePerSecond is the WAD-scaled uint returned by borrowRateView +const rateFloat = Number(ratePerSecond) / 1e18; +const borrowAPY = Math.expm1(rateFloat * SECONDS_PER_YEAR); // e.g. 0.05 == 5% +``` + +The supply APY is lower than the borrow APY, scaled by utilization and net of the market fee; it is not returned by the IRM and must be derived from market state. + +## FixedRateIrm + +For products that do not use the utilization-based curve, brokers can point a market at `FixedRateIrm`. Instead of computing a rate from utilization, it returns a per-market rate that has been set administratively. + +| Element | Signature / value | Notes | +| --- | --- | --- | +| `MAX_BORROW_RATE` | `8.0 ether / 365 days` | Maximum settable per-second rate (800% annualized). | +| `borrowRateStored(Id id)` | `int256` | The fixed per-second rate stored for a market. | +| `setBorrowRate(Id id, int256 newBorrowRate)` | — | Sets the fixed rate. Access-controlled (bot role); must be `>= 0` and `<= MAX_BORROW_RATE`, and within any configured `rateCap` / `rateFloor`. | +| `borrowRateView(...)` / `borrowRate(...)` | `uint256` | Both return the stored rate (clamped to `MAX_BORROW_RATE`, then to the market cap and floor). Unlike the adaptive model, `borrowRate` here is also `view` and holds no adaptive state. A market whose rate has never been set has `borrowRateStored == 0`, so these return `0` (subject to any configured floor) rather than reverting. | + +For a market that has never had a rate set, `borrowRateStored` defaults to `0`; `borrowRateView` only requires the stored rate to be `>= 0`, so an unset market returns `0` (subject to any configured floor) rather than reverting. A `0` rate means no interest accrues, so the fixed rate should be configured with `setBorrowRate` before the market is put into use. This IRM backs the fixed term / fixed rate broker products described in [Integration Patterns](integration-patterns.md). + +## Related + +* [Protocol Extensions](protocol-extensions.md) — market-level controls (`minLoan`, reentrancy, upgradeability, oracle architecture). +* [Integration Patterns](integration-patterns.md) — provider and broker integration layers. +* [Smart Contract](smart-contract.md) — deployed contract addresses (auto-synced). diff --git a/for-developer/lista-lending/stableswap-integration.md b/for-developer/lista-lending/stableswap-integration.md new file mode 100644 index 0000000..dc7d835 --- /dev/null +++ b/for-developer/lista-lending/stableswap-integration.md @@ -0,0 +1,246 @@ +# Smart Lending / Lista StableSwap Integration + +Smart Lending lets a user deposit a **Lista StableSwap LP position as collateral** into Moolah, borrow against it, and keep earning trading fees on the underlying pool at the same time. The LP tokens never leave the system idle: they are held by a `SmartProvider`, wrapped 1:1 into a collateral token, and supplied to a Moolah market, while the pool they represent continues to accrue swap fees. + +This page is for **DEX aggregators and integrators** who want to: + +- discover Lista StableSwap pools and route/quote swaps against them, and +- move StableSwap LP into a Moolah Smart Lending market via `SmartProvider`. + +Every function, event, and constant below is taken from the on-chain StableSwap and `SmartProvider` contracts. For deployed addresses, see [BSC Smart Lending](smart-contract-bsc-smart-lending.md) (do not hard-code addresses from this page — the address tables are the source of truth). For how providers fit into Moolah, see [Integration Patterns](integration-patterns.md). + +Lista StableSwap is a two-coin Curve-style stable pool: every pool has exactly two coins (`N_COINS = 2`), indexed `0` and `1` in sorted-address order. + +--- + +## Factory: pool discovery + +`StableSwapFactory` is the registry of all deployed pools. Use it to enumerate pools and resolve a token pair to its pool and LP token. + +| Read | Signature | Returns | +| --- | --- | --- | +| Pool count | `pairLength() → uint256` | Number of registered pools. | +| Pool by index | `swapPairContract(uint256 index) → address` | The pool (swap) contract at `index`, for `index` in `[0, pairLength)`. | +| Pools for a pair | `getPairInfos(address tokenA, address tokenB) → StableSwapPairInfo[]` | All pools registered for the pair (order-independent — inputs are sorted internally). | +| Sort helper | `sortTokens(address tokenA, address tokenB) → (address token0, address token1)` | Canonical `(token0, token1)` ordering; reverts `IDENTICAL_ADDRESSES` if equal. | + +`StableSwapPairInfo` is: + +```solidity +struct StableSwapPairInfo { + address swapContract; // the StableSwap pool + address token0; // sorted coin 0 + address token1; // sorted coin 1 + address LPContract; // the StableSwapLP (ERC-20) minted by the pool +} +``` + +Coins are always stored in sorted order (`token0 < token1`). A single token pair can have more than one pool (for example a standard and a narrow-spread pool), which is why `getPairInfos` returns an array. + +New pools emit: + +```solidity +event NewStableSwapPair(address indexed swapContract, address tokenA, address tokenB, address lp); +``` + +Index by `NewStableSwapPair` (or poll `pairLength` / `swapPairContract`) to keep an up-to-date pool list. + +```solidity +// Enumerate every pool +uint256 n = factory.pairLength(); +for (uint256 i = 0; i < n; i++) { + address pool = factory.swapPairContract(i); + // read coins / balances / lp from `pool` (see below) +} + +// Resolve a specific pair +StableSwapFactory.StableSwapPairInfo[] memory infos = factory.getPairInfos(tokenA, tokenB); +``` + +--- + +## Pool reads + +Read pool state directly from the `StableSwapPool` (the `swapContract` address). + +| Read | Signature | Notes | +| --- | --- | --- | +| Coin address | `coins(uint256 i) → address` | `i ∈ {0, 1}`. For a native-BNB pool the BNB side returns the sentinel `0xEeee…eEeE` (see [Native BNB](#native-bnb-handling)). | +| Pool balance | `balances(uint256 i) → uint256` | Pool-tracked balance of coin `i`, in that coin's own decimals. | +| LP token | `token() → address` | The `StableSwapLP` ERC-20 for this pool. | +| Amplification | `A() → uint256` | Current amplification coefficient (already de-scaled). | +| Swap fee | `fee() → uint256` | Fee numerator over `FEE_DENOMINATOR = 1e10`. | +| Admin fee | `admin_fee() → uint256` | Share of `fee` taken as protocol fee, over `1e10`. | +| Native support | `support_BNB() → bool` | `true` if one coin is native BNB. | +| Virtual price | `get_virtual_price() → uint256` | Invariant value per LP token, `1e18`-scaled. Reverts on reentrancy. | +| Precision multipliers | `PRECISION_MUL(uint256 i) → uint256` | `10 ** (18 - decimals_i)`; scales coin `i` to 18-decimal internal units. | + +`StableSwapPoolInfo` is a stateless helper that reads a pool for you and adds convenience views: + +| Helper | Signature | Returns | +| --- | --- | --- | +| Balances | `balances(address pool) → uint256[2]` | Both pool balances. | +| LP → coins | `calc_coins_amount(address pool, uint256 lpAmount) → uint256[2]` | Coin amounts a proportional withdrawal of `lpAmount` LP would yield. | +| Holder → coins | `get_coins_amount_of(address pool, address account) → uint256[2]` | Same, for an account's full LP balance. | +| Mint preview | `get_add_liquidity_mint_amount(address pool, uint256[2] amounts) → uint256` | LP that `add_liquidity(amounts)` would mint (net of deposit fee). | +| Reverse quote | `get_dx(address pool, uint256 i, uint256 j, uint256 dy, uint256 max_dx) → uint256` | Input of coin `i` required to receive `dy` of coin `j` (fee-inclusive). Reverts `Excess balance` / `Exchange resulted in fewer coins than expected`. | + +--- + +## Quoting a swap: `get_dy` + +`get_dy` returns the **fee-inclusive** output amount for a swap, in the output coin's own decimals: + +```solidity +function get_dy(uint256 i, uint256 j, uint256 dx) external view returns (uint256); +``` + +- `i` — input coin index, `j` — output coin index (`{0,1}`, `i != j`). +- `dx` — input amount, in coin `i`'s decimals. +- Returns the amount of coin `j` out **after** the pool's swap fee. + +Convention: token0 → token1 is `i = 0, j = 1`; token1 → token0 is `i = 1, j = 0`. + +```solidity +// USDC (coin 0) in, USDT (coin 1) out +uint256 amountOut = pool.get_dy(0, 1, dxUSDC); +``` + +`get_dy_without_fee(i, j, dx)` returns the pre-fee output if you need to isolate the fee component. The executed swap is: + +```solidity +function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external payable; +``` + +`min_dy` is your slippage floor; the swap reverts `Exchange resulted in fewer coins than expected` if the realized output is below it. Quote with `get_dy` immediately before execution and derive `min_dy` from it with your slippage tolerance. + +--- + +## Oracle price-difference guard + +Each pool holds a resilient-oracle reference and enforces a **price-difference guard** on state-changing operations. On `exchange`, `add_liquidity`, and every `remove_liquidity*` variant, the pool calls `checkPriceDiff()` (unless the guard is disabled — see below), which reverts when the pool's implied price for either coin diverges from the oracle price beyond a per-coin threshold: + +- `Price difference for token0 exceeds threshold` +- `Price difference for token1 exceeds threshold` + +Relevant reads: + +| Read | Signature | Notes | +| --- | --- | --- | +| Oracle prices | `fetchOraclePrice() → uint256[2]` | Oracle price of each coin, `1e18`-scaled. | +| Guard check | `checkPriceDiff()` | `view`; reverts if either coin's price diff exceeds its threshold. Safe to call as a pre-flight probe. | +| Skip flag | `skipPriceDiff() → bool` | When `true`, the guard is not enforced on pool operations. | +| Thresholds | `price0DiffThreshold()`, `price1DiffThreshold() → uint256` | Per-coin thresholds, `1e18`-scaled (e.g. `3e16` = 3%). | + +These thresholds and the skip flag are current on-chain values that are manager-adjustable on-chain; read them at call time rather than assuming a fixed number. For a large or price-sensitive route, call `checkPriceDiff()` as a `staticcall` before submitting so you can surface a clear error instead of a failed transaction. + +> Because the guard runs on liquidity operations too, an LP withdrawal (including via `SmartProvider`) can revert when the pool price has drifted from the oracle. Treat "price difference exceeds threshold" as a transient, retry-when-repegged condition, not a permanent failure. + +--- + +## Native BNB handling + +A pool "supports BNB" (`support_BNB() == true`) when one of its coins is native BNB, represented by the sentinel address: + +``` +0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE +``` + +Handling rules when interacting with a BNB pool: + +- `coins(i)` returns the sentinel for the BNB side; it is **not** an ERC-20 — do not call `approve`/`transferFrom` on it. +- For the BNB coin, send the amount as `msg.value`. The pool requires `msg.value` to exactly equal the BNB input amount (reverts `Inconsistent quantity` otherwise). +- For the ERC-20 coin, approve the pool and it will `transferFrom` you as usual. +- On a **non-BNB** pool, any non-zero `msg.value` is rejected (`Inconsistent quantity`) to prevent accidentally sending BNB. +- BNB payouts use a bounded gas stipend, so a receiving contract's `receive()`/fallback must stay within that budget or the transfer reverts `BNB transfer failed`. + +Detect the pool type from `support_BNB()` and by comparing each `coins(i)` against the sentinel before deciding whether to use `msg.value` or ERC-20 transfers. + +--- + +## Supplying LP into Moolah via `SmartProvider` + +`SmartProvider` is the [provider](integration-patterns.md) that bridges StableSwap LP into a Moolah market. It holds the LP, mints a 1:1 collateral token (`StableSwapLPCollateral`), and supplies that to Moolah on the user's behalf — the LP keeps earning pool fees while it backs the loan. Each `SmartProvider` is bound at deploy time to one pool and one collateral token; resolve the right instance for your pool from [BSC Smart Lending](smart-contract-bsc-smart-lending.md). + +There are two entry points, depending on whether the user already holds LP. + +### `supplyDexLp` — supply existing LP + +Use when the user already holds StableSwap LP tokens. + +```solidity +function supplyDexLp( + MarketParams calldata marketParams, + address onBehalf, + uint256 lpAmount +) external; +``` + +Flow: the provider pulls `lpAmount` LP from `msg.sender` (approve the provider on the LP token first), mints the collateral token 1:1, and calls `MOOLAH.supplyCollateral` for `onBehalf`. `marketParams.collateralToken` must equal the provider's collateral token (reverts `invalid collateral token`); `lpAmount` must be non-zero (`zero lp amount`). + +### `supplyCollateral` — zap tokens into LP, then supply + +Use when the user holds the underlying tokens and wants the provider to add liquidity for them in one call. + +```solidity +function supplyCollateral( + MarketParams calldata marketParams, + address onBehalf, + uint256 amount0, + uint256 amount1, + uint256 minLpAmount +) external payable; +``` + +Flow: the provider pulls `amount0`/`amount1` (approve each ERC-20 coin on the provider first), calls the pool's `add_liquidity([amount0, amount1], minLpAmount)`, mints the resulting LP 1:1 as collateral, and supplies it for `onBehalf`. + +- At least one of `amount0`/`amount1` must be > 0 (`invalid amounts`). +- `minLpAmount` is the slippage floor for the mint; add-liquidity reverts `Slippage screwed you` if fewer LP would be minted. +- For a BNB pool, pass the BNB coin's amount as `msg.value` (it must equal that coin's amount; `amount0 should equal msg.value` / `amount1 should equal msg.value`). On a non-BNB pool `msg.value` must be 0 (`msg.value must be 0`). + +Both paths emit: + +```solidity +event SupplyCollateral( + address indexed onBehalf, + address indexed collateralToken, + uint256 collateralAmount, + uint256 amount0, + uint256 amount1 +); +``` + +Once collateral is in the market, borrowing and repayment happen against the Moolah market as usual. Withdrawal is symmetric: the provider exposes `withdrawDexLp` (return LP), `withdrawCollateral` (proportional token0/token1), `withdrawCollateralImbalance` (exact token amounts), and `withdrawCollateralOneCoin` (single coin) — each burns the collateral token and removes liquidity, subject to the same price-difference guard as any pool operation. + +For the TypeScript builders that assemble these calls as ready-to-send steps (`buildSmartSupplyDexLpParams`, `buildSmartSupplyCollateralParams`, and the matching withdraw/repay builders), see the [Moolah Lending SDK](../sdk.md). + +--- + +## Revert checklist + +Common reverts when integrating, with the contract that raises them: + +| Revert string | Where | Cause / fix | +| --- | --- | --- | +| `IDENTICAL_ADDRESSES` | Factory | `sortTokens`/lookup called with `tokenA == tokenB`. | +| `Exchange resulted in fewer coins than expected` | Pool `exchange` | Realized output below `min_dy`. Re-quote with `get_dy` and widen slippage. | +| `Slippage screwed you` | Pool `add_liquidity` / `remove_liquidity_imbalance` | Mint below `min_mint_amount` / burn above `max_burn_amount`. Adjust the slippage bound. | +| `Withdrawal resulted in fewer coins than expected` | Pool `remove_liquidity` | A `min_amounts[i]` floor not met. | +| `Not enough coins removed` | Pool `remove_liquidity_one_coin` | Output below `min_amount`. | +| `Price difference for token0 exceeds threshold` / `…token1…` | Pool `checkPriceDiff` | Pool price drifted from oracle beyond threshold. Retry once repegged. | +| `Inconsistent quantity` | Pool | BNB amount ≠ `msg.value`, or `msg.value` sent to a non-BNB pool. | +| `BNB transfer failed` | Pool | BNB recipient reverted or exceeded the gas stipend. | +| `Reentrant call` | Pool `get_virtual_price` | Called during an in-progress pool operation. | +| `invalid collateral token` | SmartProvider | `marketParams.collateralToken` ≠ the provider's collateral token. | +| `zero lp amount` / `invalid amounts` | SmartProvider | `supplyDexLp` with 0 LP / `supplyCollateral` with both amounts 0. | +| `amount0 should equal msg.value` / `amount1 should equal msg.value` / `msg.value must be 0` | SmartProvider | Native-BNB `msg.value` mismatch on `supplyCollateral`. | +| `no lp minted` | SmartProvider | `add_liquidity` produced no LP (e.g. dust amounts). | +| `unauthorized sender` | SmartProvider | Withdrawing for an `onBehalf` you are not authorized on. | + +--- + +## See also + +- [BSC Smart Lending](smart-contract-bsc-smart-lending.md) — deployed factory, pool, LP, collateral, and `SmartProvider` addresses. +- [Integration Patterns](integration-patterns.md) — how providers and brokers plug into Moolah. +- [Moolah Lending SDK](../sdk.md) — TypeScript builders for Smart Lending supply/withdraw/repay. diff --git a/for-developer/multi-oracle/consuming-prices.md b/for-developer/multi-oracle/consuming-prices.md new file mode 100644 index 0000000..31578d6 --- /dev/null +++ b/for-developer/multi-oracle/consuming-prices.md @@ -0,0 +1,220 @@ +# Consuming Oracle Prices + +Two different price functions exist in the Lista stack, and they are frequently confused. They live at different layers, take different arguments, and return numbers on different scales. Reading one where the other is expected produces wrong health factors and mispriced liquidations. + +| Function | Layer | Signature | Returns | Scale | +| --- | --- | --- | --- | --- | +| `peek` | Oracle (Resilient Oracle / any `IOracle`) | `peek(address asset) → uint256` | Price of **one `asset`, quoted in USD** | Feed-dependent (see [Decimals](#decimals-what-peek-returns)) | +| `getPrice` | Moolah market | `getPrice(MarketParams marketParams) → uint256` | Price of **one collateral token, quoted in the loan token** | Fixed `36 + loanDecimals − collateralDecimals` (see [The scale factor](#the-market-scale-factor)) | + +Rule of thumb: `peek` answers "what is this asset worth in USD?"; `getPrice` answers "how much loan token is one unit of collateral worth, on the scale Moolah's math expects?". Health checks and liquidations use `getPrice`. Use `peek` only when you genuinely need a single asset's USD value (for example, to reconstruct the two legs of a market price, or to sanity-check a feed). + +This page shows how to read a market's oracle, call both functions read-only, verify which feeds back an asset, and compute a position's health and liquidation price with the exact scale factor from `Moolah.sol`. For the (auto-synced) per-asset oracle address tables, see [Multi-Oracle](../multi-oracle.md). + +--- + +## Layer 1 — the oracle: `peek(address asset)` + +Every oracle a Moolah market can use implements `IOracle`: + +```solidity +struct TokenConfig { + address asset; + address[3] oracles; // [main, pivot, fallback] + bool[3] enableFlagsForOracles; // enabled state, same order + uint256 timeDeltaTolerance; // max staleness in seconds +} + +interface IOracle { + function peek(address asset) external view returns (uint256); + function getTokenConfig(address asset) external view returns (TokenConfig memory); +} +``` + +`peek(asset)` returns the USD price of one unit of `asset`. It is a `view` function — free to call, no state change. Lista's production oracle behind most collateral is the **Resilient Oracle**, whose `peek` implementation is described below, but a market curator may point a market at any contract that satisfies `IOracle`. + +### Decimals: what `peek` returns + +Moolah markets assume the oracle's per-asset USD price has **8 decimals** — the same precision Moolah uses internally for `minLoanValue`. Most upstream feeds (Chainlink, Atlas, RedStone on BNB Chain) publish 8-decimal answers, so this holds directly. Some adaptors normalize to a higher precision — a common pattern multiplies an 8-decimal feed by `1e10` to publish an 18-decimal answer — so the raw magnitude you see from a specific `peek` depends on the feed and adaptor behind that asset. + +You do **not** need to guess these decimals when working at the market layer: `getPrice` (Layer 2) reads both legs and normalizes them against the token decimals for you. When you call `peek` directly, always confirm the decimals of the specific feed you are reading rather than assuming a fixed magnitude. + +### The Resilient Oracle in brief + +The Resilient Oracle aggregates up to three sources per asset and cross-validates them so a single bad feed cannot move a price. It configures three roles per asset: + +| Role | Purpose | +| --- | --- | +| **main** | The most trustworthy price source. Must be set (non-zero). | +| **pivot** | A loose sanity checker used to validate the main (and fallback) price. Optional. | +| **fallback** | Backup source used when the main price fails validation. Optional. | + +`peek` resolves a price by this precedence (see `ResilientOracle._getPrice`): + +1. If **main** is enabled and validates against **pivot** (via `BoundValidator`), return the main price. If no pivot is configured/enabled, the main price is returned directly. +2. Otherwise, if **fallback** is enabled and validates against **pivot**, return the fallback price. +3. Otherwise, if both main and fallback are available and validate against each other, return the main price. +4. Otherwise revert with `"invalid resilient oracle price"`. + +A source is skipped if it is disabled, missing, reverts, or is **stale** — `getPriceFromOracle` treats a Chainlink-style answer whose `updatedAt` is older than the asset's `timeDeltaTolerance` as invalid (`INVALID_PRICE = 0`). + +**BoundValidator.** Validation compares a reported price against an anchor price. With `anchorRatio = anchorPrice * 1e18 / reportedPrice`, the reported price is accepted only when: + +``` +lowerBoundRatio <= anchorRatio <= upperBoundRatio +``` + +Bounds are configured per asset — `BoundValidator` stores no default, so consult the per-asset limits in the auto-synced [Multi-Oracle](../multi-oracle.md) tables (commonly a ±1% band, i.e. `0.99` / `1.01`). A reported price of `0` fails validation gracefully (returns `false`); an anchor price of `0` instead reverts (`anchor price is not valid`). + +> Which sources and bounds are configured for each asset (and the Resilient Oracle address per chain) are published in the auto-synced tables on [Multi-Oracle](../multi-oracle.md). Do not hard-code them. + +### Verifying which feeds back an asset + +To see the sources behind a collateral before you trust a market, read the token config from the oracle directly: + +```solidity +// oracle = a market's marketParams.oracle +TokenConfig memory cfg = IOracle(oracle).getTokenConfig(collateralToken); +// cfg.oracles[0] = main, [1] = pivot, [2] = fallback +// cfg.enableFlagsForOracles[i] tells you which roles are live +// cfg.timeDeltaTolerance is the staleness window in seconds +``` + +The Resilient Oracle also exposes `getOracle(asset, role) → (address oracle, bool enabled)` for reading a single role. + +--- + +## Layer 2 — the market: `getPrice(MarketParams)` + +A Moolah market's price is the collateral asset priced **in the loan asset**, not in USD. This is the number Moolah's health and liquidation math actually consumes. + +```solidity +struct MarketParams { + address loanToken; + address collateralToken; + address oracle; + address irm; + uint256 lltv; +} + +interface IMoolah { + function idToMarketParams(Id id) external view returns (MarketParams memory); + function getPrice(MarketParams calldata marketParams) external view returns (uint256); + function isHealthy(MarketParams calldata marketParams, Id id, address borrower) external view returns (bool); +} +``` + +`getPrice` is `view`. Under the hood it reads both legs from the market's oracle (`oracle.peek(collateralToken)` and `oracle.peek(loanToken)`) and combines them with a fixed scale factor. + +### The market scale factor + +From `Moolah.getPrice` / `_getPrice`, with `base = collateralToken` and `quote = loanToken`: + +```solidity +uint256 scaleFactor = 10 ** (36 + quoteTokenDecimals - baseTokenDecimals); +return scaleFactor.mulDivDown(basePrice, quotePrice); +// = scaleFactor * basePrice / quotePrice (rounded down) +``` + +where `basePrice = peek(collateralToken)`, `quotePrice = peek(loanToken)`, `baseTokenDecimals = IERC20Metadata(collateralToken).decimals()`, and `quoteTokenDecimals = IERC20Metadata(loanToken).decimals()`. + +Because the same feed decimals appear in both `basePrice` and `quotePrice`, they cancel in the ratio, and the `36 + quoteDecimals − baseDecimals` exponent normalizes the result to Moolah's canonical price scale. The result is deliberately targeted at the constant: + +```solidity +uint256 constant ORACLE_PRICE_SCALE = 1e36; +``` + +Read `getPrice` as a **raw-unit conversion factor, not a human-readable price**: multiplying a raw collateral amount (in the collateral token's own decimals) by `getPrice` and dividing by `1e36` yields the equivalent debt in **raw loan-token units** — `rawCollateral × getPrice / 1e36 = rawLoan`. Because the scale factor carries `10**(quoteDecimals − baseDecimals)`, `getPrice / 1e36` equals the per-whole-token price *only when both tokens share the same decimals*; for a human "1 collateral = X loan tokens" price across different decimals, apply the token decimals yourself (or derive it from the two USD legs via `peek`). The health and liquidation math below works entirely in raw units, so you never need the human price for on-chain-accurate results. + +> Broker markets note. `getPrice(marketParams)` calls the internal price with `user = address(0)`, which always returns the plain market price. For fixed-term/credit **broker** markets, an account-specific price can deviate from the market price; the protocol uses the market price for the standard health check so liquidators can act in time. Unless you are integrating a broker product, `getPrice(marketParams)` is the number you want. See [Integration Patterns](../lista-lending/integration-patterns.md). + +--- + +## Computing health with the scale factor + +Moolah's health check (`Moolah._isHealthy`) is: + +```solidity +uint256 maxBorrow = uint256(position.collateral) + .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) // collateral * price / 1e36 + .wMulDown(marketParams.lltv); // * lltv / 1e18 + +bool healthy = maxBorrow >= borrowed; // borrowed rounded up +``` + +where: + +- `collateralPrice = getPrice(marketParams)` (the `1e36`-scaled collateral-in-loan price). +- `position.collateral` is the raw collateral amount in the collateral token's own decimals. +- `borrowed` is the borrower's debt in loan-token units (borrow shares converted to assets, rounded **up** in the protocol's favor). +- `lltv` is WAD-scaled (`1e18` = 100%), and `wMulDown(x, lltv) = x * lltv / 1e18`. + +So the collateral's borrowing power, in loan-token units, is: + +``` +maxBorrow = collateral * getPrice / 1e36 * lltv / 1e18 +``` + +The position is healthy while `maxBorrow >= borrowed`. Note the two independent scale divisions: `1e36` (the price scale) and `1e18` (the LLTV WAD). Dropping either one is the most common integration error. + +You do not have to reproduce this off-chain to answer "is this position healthy?" — Moolah exposes `isHealthy(marketParams, id, borrower)` as a `view` function that returns the same boolean the protocol enforces on `borrow` and `withdrawCollateral`. + +### Read-only example (viem) + +```typescript +import { createPublicClient, http } from "viem"; + +const client = createPublicClient({ transport: http(RPC_URL) }); + +// 1. Resolve the market's params from its id (bytes32). +const marketParams = await client.readContract({ + address: MOOLAH, + abi: MOOLAH_ABI, + functionName: "idToMarketParams", + args: [marketId], +}); + +// 2. Collateral price, quoted in the loan token, scaled by 1e36. +const price = await client.readContract({ + address: MOOLAH, + abi: MOOLAH_ABI, + functionName: "getPrice", + args: [marketParams], +}); // bigint + +// 3. Borrowing power of `collateral` units, in loan-token units. +const ORACLE_PRICE_SCALE = 10n ** 36n; +const WAD = 10n ** 18n; +const maxBorrow = (collateral * price) / ORACLE_PRICE_SCALE * marketParams.lltv / WAD; +const healthy = maxBorrow >= borrowed; +``` + +`collateral` and `borrowed` here are raw on-chain integers in each token's own decimals; do not pre-scale them. `getPrice` already accounts for the decimal difference between the two tokens. + +## Liquidation price + +The health boundary is where `maxBorrow == borrowed`. Solving the health inequality for the collateral price gives the **liquidation price** — the `getPrice` value at which a position with fixed `collateral` and `borrowed` becomes eligible for liquidation: + +``` +liquidationPrice = borrowed * 1e36 * 1e18 / (collateral * lltv) +``` + +(all values raw, in their native units/scales). The position becomes liquidatable once `getPrice(marketParams)` falls **strictly below** `liquidationPrice`: the on-chain check is `maxBorrow >= borrowed` (rounded in the protocol's favor), so at exactly the boundary the position is still healthy, and liquidation requires `!_isHealthy`. + +For the liquidation mechanics themselves, note how the same scale appears when collateral is seized (`Moolah.liquidate`): the loan-token value of seized collateral is `seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE)`, i.e. `seizedAssets * getPrice / 1e36`. The liquidation incentive factor and cursor are **compile-time constants** (`LIQUIDATION_CURSOR`, `MAX_LIQUIDATION_INCENTIVE_FACTOR` in `ConstantsLib`), not governance-tunable parameters, and are not covered here. + +--- + +## Checklist for integrators + +- **Pricing a position's health / a liquidation:** use `getPrice(marketParams)` and divide by `ORACLE_PRICE_SCALE` (`1e36`), then apply `lltv` with the `1e18` WAD. Never mix in a raw `peek` value here. +- **Need a single asset's USD value:** use `oracle.peek(asset)` and confirm that feed's decimals before scaling. +- **Auditing a market's oracle:** read `marketParams.oracle`, then `getTokenConfig(collateralToken)` to see the main/pivot/fallback sources, enabled flags, and staleness tolerance; cross-reference the addresses on [Multi-Oracle](../multi-oracle.md). +- **All of these are `view` calls** — safe to call from any RPC without a transaction. + +## Related pages + +- [Multi-Oracle](../multi-oracle.md) — per-asset oracle sources, bound-validator limits, and Resilient Oracle addresses (auto-synced). +- [Oracle](../../introduction/lista-lending/oracle.md) — conceptual overview of oracles in Lista Lending. +- [Integration Patterns](../lista-lending/integration-patterns.md) — provider and broker layers, including broker-specific pricing. +- [Moolah Lending SDK](../sdk.md) — TypeScript helpers that read market data and prices for you. From 0e5c5d4db0d8fcb417448e3f7eb4fd1aa0482855 Mon Sep 17 00:00:00 2001 From: tyler-tsai <15355143+tyler-tsai@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:01:11 +0800 Subject: [PATCH 2/4] docs(lending-api): REST API pages aligned to live controllers + conventions Moolah Lending REST API pages, for backend review: - overall/vault/market/position-liquidation-emission rewritten to match the live NestJS controllers: string chain (not numeric chainId), sort/order; real endpoints; order required on /borrow/markets; totalBorrow history is protocol-wide; exact emission signature-message format - new API Conventions page (response envelope, chain, pagination, signature-gated endpoints, caching) Co-Authored-By: Claude Opus 4.8 --- .../services/lending-api/conventions.md | 157 +++++++ for-developer/services/lending-api/market.md | 267 ++++++++---- for-developer/services/lending-api/overall.md | 95 ++++- .../position-liquidation-emission.md | 384 +++++++++++++++--- for-developer/services/lending-api/vault.md | 168 +++++--- 5 files changed, 875 insertions(+), 196 deletions(-) create mode 100644 for-developer/services/lending-api/conventions.md diff --git a/for-developer/services/lending-api/conventions.md b/for-developer/services/lending-api/conventions.md new file mode 100644 index 0000000..1e068e3 --- /dev/null +++ b/for-developer/services/lending-api/conventions.md @@ -0,0 +1,157 @@ +# API Conventions + +Cross-cutting conventions shared by every [Moolah Lending API](README.md) endpoint: the base URL, the response envelope, the chain selector, pagination and sorting, signature-gated endpoints, and caching. Each endpoint page assumes these and only documents what is specific to it. + +**Base URL:** all paths are served under `/api/moolah` (for example `GET /api/moolah/borrow/markets`). + +--- + +## Response envelope + +Every response is wrapped in a uniform JSON envelope. The endpoint's own payload is carried in `data`; the surrounding fields are the same on success and on error. + +| Field | Type | Description | +|-------|------|-------------| +| `code` | string | Status code. `"000000000"` on success. Any other value indicates an error. | +| `msg` | string | Human-readable message for `code`, localized to the request language. | +| `data` | any | The endpoint payload — an object, an array, or `null`. Shape is documented per endpoint. | +| `timestamp` | number | Server time when the response was built, in **milliseconds** since the Unix epoch. | + +Success example: + +```json +{ + "code": "000000000", + "msg": "success", + "data": { "total": 12, "list": [] }, + "timestamp": 1751414400000 +} +``` + +Error example: + +```json +{ + "code": "400", + "msg": "Invalid params", + "data": null, + "timestamp": 1751414400000 +} +``` + +### Reading responses + +- **Check `code`, not the HTTP status alone.** Successful responses return HTTP `200` with `code = "000000000"`. Client errors (bad or missing parameters, unknown resource) return HTTP `400` with a non-success `code`; server-side failures return HTTP `500`. Always branch on `code === "000000000"` and read the payload from `data`. +- **`timestamp` is in milliseconds**, not seconds — note this when comparing against the `startTime` / `endTime` history parameters, which are in **seconds**. + +### Common error codes + +Codes are strings. The ones an integrator is most likely to encounter: + +| `code` | Meaning | +|--------|---------| +| `000000000` | Success. | +| `400` | Invalid request parameters. | +| `404` | Resource not found. | +| `401` | Signed message expired (see [Signature-gated endpoints](#signature-gated-endpoints)). | +| `1005` | Invalid signature. | +| `500` | Server error. | +| `-1` | Custom error; read `msg` for the specific reason (e.g. a required query parameter was omitted). | + +--- + +## Chain selector + +Endpoints that span networks accept a `chain` query parameter. It is a **string network key**, not a numeric chain ID: + +| Value | Network | +|-------|---------| +| `bsc` | BNB Smart Chain (mainnet) | +| `ethereum` | Ethereum (mainnet) | +| `bscTest` | BSC testnet | + +- **Comma-separated where supported.** Several list endpoints accept multiple keys in one call, e.g. `chain=bsc,ethereum`. The value is split on commas and each key is matched independently. +- **Default is environment-dependent.** When `chain` is omitted, endpoints default to the live network — `bsc` in production. Do not rely on the default; pass `chain` explicitly for deterministic behavior. +- The `chain` string is also echoed back on most market/vault objects so you can tell which network a record belongs to when querying more than one at a time. + +--- + +## Pagination + +List endpoints use 1-based page pagination: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | number | Page number, **1-based**. Values missing or `≤ 0` are treated as `1`. | +| `pageSize` | number | Items per page. Defaults to `10`. Capped per endpoint — requesting more than the cap silently clamps to it. | + +The `pageSize` cap is endpoint-specific: + +| Endpoint | `pageSize` cap | +|----------|----------------| +| `GET /borrow/markets` | `50` | +| `GET /emission/userRewardHistory` | `50` | +| `GET /market/vault/:marketId` (vaults by market) | `20` | + +Paginated endpoints return `{ total, list }`, where `total` is the full count matching the filters (before pagination) and `list` holds the current page. To detect the last page, compare `page * pageSize` against `total` rather than assuming a full page. + +> Some snapshot-style endpoints use keyset (cursor) pagination instead of `page`/`pageSize` — see the individual endpoint page (e.g. the holder snapshot in [Positions, Liquidation & Emission](position-liquidation-emission.md)). + +--- + +## Sorting + +Sortable list endpoints take a pair of parameters: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `sort` | string | Sort field **key** (a whitelisted alias, not a raw column). Unrecognized keys fall back to a per-endpoint default. | +| `order` | string | Sort direction: `asc` or `desc`. Case-insensitive. | + +Use the pair `sort` + `order`, **not** `sortBy` / `sortOrder`. + +**`order` is required on `GET /borrow/markets`.** That endpoint applies `order` unconditionally — even when `sort` is omitted and it falls back to its default field. Omitting `order` therefore causes an error rather than defaulting to a direction. Always send `order` (e.g. `order=desc`) on the market list, even if you do not set `sort`. Other endpoints (such as vaults-by-market) default `order` to `desc` when it is omitted; the requirement above is specific to the market list. + +Valid `sort` keys for `GET /borrow/markets`: `rate`, `liquidity`, `lltv`, `loan`, `collateral`. See [Market API](market.md) for what each maps to. + +--- + +## Signature-gated endpoints + +A small number of endpoints return address-specific data (the emission / reward **merkle proofs**) and require the caller to prove control of the address. These endpoints take three additional query parameters: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `address` | string | Yes | The address the data is being requested for. | +| `signature` | string | Yes | A wallet signature over `message`. | +| `message` | string | Yes | The signed message. Its **first line is a timestamp**, and the message must be no older than **7 days**. | +| `type` | string | No | `safe` to validate as an ERC-1271 contract wallet (e.g. Safe); omitted/other validates as a standard EOA signature. | + +How verification works: + +- **EOA (default):** the signer is recovered from `signature` over `message` and must equal `address` (case-insensitive). +- **`type=safe`:** the signature is validated against the address as an ERC-1271 contract wallet. +- An **invalid signature** returns code `1005`; a **message older than 7 days** returns code `401`. + +This is a standard wallet-signature challenge — **no server-side secret, API key, or token is involved.** The signature-gated endpoints are documented in [Positions, Liquidation & Emission](position-liquidation-emission.md#5-emission-rewards--merkle-proofs). + +--- + +## Caching and freshness + +Most list and detail endpoints are served from a **short-lived server-side cache** (on the order of tens of seconds). Two consequences for integrators: + +- **Values reflect the last sync, not live chain state.** List and detail responses (liquidity, rates, totals, prices, APYs) are as of the most recent indexer sync and can lag the chain by up to the cache window. Treat them as display/decision data, not as a substitute for reading the chain at transaction time. +- **For values you will sign a transaction against, read on-chain.** Build transactions from immutable market parameters — use `GET /api/moolah/allMarkets` (raw on-chain `MarketParams`-equivalent fields: `loanToken`, `collateralToken`, `oracle`, `irm`, `lltv`) and read live totals/prices directly from the Moolah contract. See [Market API](market.md) and the [Lista Lending Smart Contract](../../lista-lending/smart-contract.md) reference. + +USD and asset amounts are returned as fixed-point **decimal strings** unless an endpoint notes otherwise; fields whose name ends in `Wei` carry the raw on-chain integer. Parse amounts with a big-number library rather than native floats. + +--- + +## See also + +- [Moolah Lending API](README.md) — index of endpoint pages. +- [Market API](market.md) — market list/detail, `allMarkets`, search. +- [Vault](vault.md) — vault listing and detail endpoints. +- [Positions, Liquidation & Emission](position-liquidation-emission.md) — user positions and signature-gated emission proofs. +- [Integration Patterns](../../lista-lending/integration-patterns.md) — end-to-end integration flows. diff --git a/for-developer/services/lending-api/market.md b/for-developer/services/lending-api/market.md index 0400fda..29b63b7 100644 --- a/for-developer/services/lending-api/market.md +++ b/for-developer/services/lending-api/market.md @@ -1,6 +1,10 @@ # Market API -A lending market is defined by a collateral/loan asset pair, LLTV, and oracle. All paths are under **Base URL** `/api/moolah`. +A lending market is defined by a collateral/loan asset pair, an LLTV, an interest rate model (IRM), and an oracle. These endpoints expose market listings, per-market detail, the vaults that fund a market, historical borrow/supply series, and the raw on-chain market parameters used to build transactions. + +All paths are under **Base URL** `/api/moolah`. List and detail responses are served from a short-lived server-side cache, so values reflect the last sync rather than live on-chain state. USD and asset amounts are returned as fixed-point decimal strings (18 decimal places) unless noted. + +The `chain` query parameter is a **string network key** (`bsc`, `ethereum`, `bscTest`), not a numeric chain ID. When omitted it defaults to the live network (`bsc` in production). List sorting uses the pair `sort` (a field key) + `order` (`asc` | `desc`), not `sortBy`/`sortOrder`. --- @@ -8,7 +12,7 @@ A lending market is defined by a collateral/loan asset pair, LLTV, and oracle. A ### GET /api/moolah/borrow/markets -Paginated list of borrow markets with filters. +Paginated list of borrow markets with sorting and filtering. | | | |--|--| @@ -19,37 +23,42 @@ Paginated list of borrow markets with filters. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `chainId` | number / string | No | Filter by chain. | -| `collateralAsset` | string | No | Collateral token address. | -| `loanAsset` | string | No | Loan token address. | -| `page` | number | No | Page number. | -| `pageSize` | number | No | Items per page. | -| `sortBy` | string | No | e.g. `totalBorrow`, `borrowApy`. | -| `sortOrder` | string | No | `asc` or `desc`. | +| `page` | number | No | Page number (1-based). Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `10`, capped at `50`. | +| `sort` | string | No | Sort key: `rate`, `liquidity`, `lltv`, `loan`, or `collateral`. Unrecognized values fall back to borrow rate. | +| `order` | string | **Yes** | Sort direction: `asc` or `desc` (case-insensitive). Required for this endpoint — always send it, even when relying on the default `sort`. | +| `keyword` | string | No | Free-text search over loan/collateral symbols. Max length 50. | +| `loans` | string[] | No | Filter by loan token symbol(s). Repeat the param for multiple values (e.g. `loans=USDT&loans=USDC`). | +| `collaterals` | string[] | No | Filter by collateral token symbol(s). Repeatable. | +| `zone` | string | No | Comma-separated zone id(s). Defaults to `0`. | +| `chain` | string | No | Network key (`bsc`, `ethereum`, `bscTest`). Defaults to `bsc`. | #### Response | Field | Type | Description | |-------|------|-------------| -| `list` | array | Market objects. | -| `total` | number | Total count. | +| `total` | number | Total count matching the filters (before pagination). | +| `list` | array | Market objects for the current page. | **Item in `list`:** | Field | Type | Description | |-------|------|-------------| -| `marketId` | string | Market identifier. | -| `chainId` | number | Chain ID. | -| `collateralAsset` | string | Collateral token address. | -| `collateralSymbol` | string | Collateral symbol. | -| `loanAsset` | string | Loan token address. | -| `loanSymbol` | string | Loan symbol. | -| `lltv` | string | Liquidation LTV (e.g. 0.8). | -| `totalSupply` | string | Total supplied. | -| `totalBorrow` | string | Total borrowed. | +| `id` | string | Market identifier (bytes32). | +| `lltv` | string | Liquidation loan-to-value. | +| `liquidity` | string | Available liquidity in loan-token units (decimal-adjusted). | +| `liquidityUsd` | string | Available liquidity in USD. | +| `loan` | string | Loan token name/symbol. | +| `loanIcon` | string | Loan token icon URL. | +| `collateral` | string | Collateral token name/symbol. | +| `rate` | string | Borrow rate. | | `supplyApy` | string | Supply APY. | -| `borrowApy` | string | Borrow APY. | -| `utilization` | string | Utilization ratio. | +| `vaults` | array | Vaults supplying this market, each `{ name, address, icon }`. | +| `icon` | string | Market icon URL. | +| `zone` | number | Market zone. | +| `chain` | string | Network key. | +| `rewards` | array | Reward token configuration entries. | +| `smartCollateralConfig` | object \| null | Smart-collateral configuration when the market uses one. | --- @@ -57,7 +66,7 @@ Paginated list of borrow markets with filters. ### GET /api/moolah/market/:marketId -Full details for one market, including oracles. +Full details for one market, including curator metadata and oracle configuration. | | | |--|--| @@ -68,44 +77,38 @@ Full details for one market, including oracles. | Parameter | Type | Description | |-----------|------|-------------| -| `marketId` | string | Market identifier (e.g. bytes32 or address). | +| `marketId` | string | Market identifier (bytes32). | #### Response +Returns the market object, or empty when the id is unknown. + | Field | Type | Description | |-------|------|-------------| | `marketId` | string | Market identifier. | -| `chainId` | number | Chain ID. | -| `collateralAsset` | string | Collateral token address. | -| `collateralSymbol` | string | Symbol. | -| `loanAsset` | string | Loan token address. | -| `loanSymbol` | string | Symbol. | -| `lltv` | string | Liquidation LTV. | -| `totalSupply` | string | Total supplied. | -| `totalBorrow` | string | Total borrowed. | +| `description` | string | English market description. | +| `descriptionZh` | string | Chinese market description. | +| `curator` | string | Curator name. | +| `curatorIcon` | string | Curator icon URL. | +| `performanceFeeRate` | number | Market performance fee rate. | +| `borrowRate` | number | Current borrow rate. | | `supplyApy` | string | Supply APY. | -| `borrowApy` | string | Borrow APY. | -| `utilization` | string | Utilization. | -| `collateralOracles` | array | Oracle config(s) for collateral. | -| `loanOracles` | array | Oracle config(s) for loan asset. | - -**Oracle item** (in `collateralOracles` / `loanOracles`): - -| Field | Type | Description | -|-------|------|-------------| -| `address` | string | Oracle contract address. | -| `url` | string | Optional explorer or doc URL. | -| `descEn` | string | English description. | -| `descCn` | string | Chinese description. | - -For **PT-style (principal token)** collateral, items may also include: +| `loanToken` | string | Loan token address. | +| `loanTokenName` | string | Loan token name/symbol. | +| `loanTokenIcon` | string | Loan token icon URL. | +| `loanTokenPrice` | string | Loan token price. | +| `collateralToken` | string | Collateral token address. | +| `collateralTokenName` | string | Collateral token name/symbol. | +| `collateralTokenIcon` | string | Collateral token icon URL. | +| `oracle` | string | Oracle contract address. | +| `zone` | number | Market zone. | +| `chain` | string | Network key. | +| `rewards` | array | Reward token configuration entries. | +| `collateralOracles` | array | Oracle config(s) resolving the collateral price. | +| `loanOracles` | array | Oracle config(s) resolving the loan price. | +| `smartCollateralConfig` | object \| null | Smart-collateral configuration when present. | -| Field | Type | Description | -|-------|------|-------------| -| `discountOracle` | string | Discount oracle address. | -| `baseTokenOracle` | string | Base token oracle address. | -| `baseToken` | string | Base token address. | -| `baseTokenSymbol` | string | Base token symbol. | +The response also carries deprecated price-logic fields (`collateralPriceLogic`, `collateralPriceLogicCn`, `loanPriceLogic`, `loanPriceLogicCn`); prefer `collateralOracles` / `loanOracles` for oracle information and treat the legacy fields as soft-deprecated. --- @@ -130,23 +133,29 @@ Paginated list of vaults that supply liquidity to the given market. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `page` | number | No | Page number. | -| `pageSize` | number | No | Items per page. | +| `page` | number | No | Page number. Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `10`, capped at `20`. | +| `order` | string | No | Sort direction: `asc` or `desc`. Defaults to `desc`. Results are always ordered by each vault's supply to the market (`totalSupply`). | #### Response | Field | Type | Description | |-------|------|-------------| +| `total` | number | Number of entries returned. | | `list` | array | Vault entries. | -| `total` | number | Total count. | **Item in `list`:** | Field | Type | Description | |-------|------|-------------| -| `vaultId` | string | Vault identifier. | -| `allocatedAmount` | string | Amount supplied to this market. | -| `share` | string | Share of market supply (e.g. proportion). | +| `address` | string | Vault contract address. | +| `name` | string | Vault name. | +| `icon` | string | Vault icon URL. | +| `curator` | string | Curator name. | +| `curatorIcon` | string | Curator icon URL. | +| `totalSupply` | string | Amount this vault supplies to the market. | +| `supplyShare` | string | This vault's share of the market's total supply (proportion). | +| `collateralPrice` | string | Collateral token price for the market. | --- @@ -154,7 +163,7 @@ Paginated list of vaults that supply liquidity to the given market. ### GET /api/moolah/market/borrowRate/:marketId -Historical borrow rate (or APY) for the market. +Historical borrow rate and supply APY series for a market. Points are bucketed by day. | | | |--|--| @@ -171,9 +180,8 @@ Historical borrow rate (or APY) for the market. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `startTime` | number / string | No | Start timestamp. | -| `endTime` | number / string | No | End timestamp. | -| `interval` | string | No | e.g. `day`, `hour`. | +| `startTime` | number | No | Start time, Unix seconds. Defaults to one week ago. | +| `endTime` | number | No | End time, Unix seconds. Defaults to now. | #### Response @@ -181,52 +189,121 @@ Array of rate points: | Field | Type | Description | |-------|------|-------------| -| `timestamp` | number | Point time. | -| `borrowRate` | string | Borrow rate (per second or annual). | -| `borrowApy` | string | Borrow APY. | +| `rate` | string | Borrow rate at the point. | +| `supplyApy` | string | Supply APY at the point. | +| `chartTime` | number | Point time, Unix seconds. | --- -## 5. All markets (on-chain) +## 5. Total-borrow history (protocol-wide) -### GET /api/moolah/allMarkets +### GET /api/moolah/market/totalBorrow/:marketId + +Historical **protocol-wide** total borrowed (in USD), bucketed by day. -Returns all markets with **on-chain base parameters** (for building transaction params). No pagination. +> **Note:** despite the `:marketId` path segment, the current implementation does **not** filter by market — it sums `borrow_usd` across all markets per day, and `:marketId` only varies the response cache key. Every `marketId` therefore returns the same protocol-wide series. | | | |--|--| | **Method** | `GET` | -| **Path** | `/api/moolah/allMarkets` | +| **Path** | `/api/moolah/market/totalBorrow/:marketId` | + +#### Path parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `marketId` | string | Market identifier. | #### Query parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `chainId` | number / string | No | Filter by chain. Omit for all chains. | +| `startTime` | number | No | Start time, Unix seconds. Defaults to one week ago. | +| `endTime` | number | No | End time, Unix seconds. Defaults to now. | #### Response -Array of market config objects. Each item typically includes: +Array of points: | Field | Type | Description | |-------|------|-------------| -| `marketId` | string | Market id (e.g. bytes32). | -| `chainId` | number | Chain ID. | -| `collateralAsset` | string | Collateral token address. | -| `loanAsset` | string | Loan token address. | +| `totalBorrow` | string | Protocol-wide total borrowed in USD at the point. | +| `chartTime` | number | Point time, Unix seconds. | + +--- + +## 6. All markets (on-chain parameters) + +### GET /api/moolah/allMarkets + +Returns every active, liquidatable market with its raw on-chain parameters — the values needed to build Moolah transactions (`MarketParams`-equivalent) and to read totals directly. No pagination and **no query parameters**. + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/moolah/allMarkets` | + +#### Response + +Array of market parameter objects: + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Market id (bytes32). | +| `loanToken` | string | Loan token address. | +| `collateralToken` | string | Collateral token address. | | `oracle` | string | Oracle contract address. | -| `lltv` | string | Liquidation LTV. | -| `irm` | string | Interest rate model address. | +| `irm` | string | Interest rate model contract address. | +| `lltv` | string | Liquidation LTV, scaled to 1e18. | +| `totalSupplyAssets` | string | Total supplied assets (on-chain). | +| `totalSupplyShares` | string | Total supply shares (on-chain). | +| `totalBorrowAssets` | string | Total borrowed assets (on-chain). | +| `totalBorrowShares` | string | Total borrow shares (on-chain). | +| `fee` | string | Market fee (on-chain). | +| `lastUpdate` | number | Last on-chain accrual timestamp. | +| `chain` | string | Network key. | +| `zone` | number | Market zone. | + +The first five fields (`loanToken`, `collateralToken`, `oracle`, `irm`, `lltv`) are the immutable market parameters; pair them with the on-chain Moolah contract to construct supply/borrow/withdraw calls. See [Smart Contract](../../lista-lending/smart-contract.md) for the contract reference. + +--- + +## 7. User supply APY + +### GET /api/moolah/supply/apy + +Per-market supply APY for the markets where a given user currently holds collateral. Markets with a zero collateral balance or inactive status are omitted. + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/moolah/supply/apy` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `userAddress` | string | Yes | The user's address. | -Exact fields follow the contract’s `MarketParams` or equivalent. +#### Response + +Array of per-market entries: + +| Field | Type | Description | +|-------|------|-------------| +| `address` | string | Market id (bytes32). | +| `asset` | string | Collateral token address. | +| `apy` | string | Supply APY for the market. | +| `amount` | string | User's collateral amount in the market. | +| `usdValue` | string | User's collateral value in USD. | --- -## 6. Market search / filters +## 8. Market search filters ### GET /api/moolah/market/search/:typeId -Returns filter options or search results for markets by type (e.g. collateral type, category). +Returns the distinct loan or collateral tokens available across markets — used to populate filter dropdowns for [`/borrow/markets`](#1-market-list). | | | |--|--| @@ -237,8 +314,32 @@ Returns filter options or search results for markets by type (e.g. collateral ty | Parameter | Type | Description | |-----------|------|-------------| -| `typeId` | string | Filter type (e.g. `collateral`, `category`). | +| `typeId` | string | Must be `loan` or `collateral`. Any other value returns `400`. | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `chain` | string | No | Network key (`bsc`, `ethereum`, `bscTest`). Defaults to `bsc`. Comma-separated values accepted. | +| `zone` | string | No | Comma-separated zone id(s). | #### Response -Shape is implementation-specific: often an array of options `{ value, label }` or a list of market IDs matching the type. +Array of token options: + +| Field | Type | Description | +|-------|------|-------------| +| `name` | string | Display name (`WBNB` is surfaced as `BNB`). | +| `value` | string | Token symbol to pass back as a filter value. | +| `icon` | string | Token icon URL. | +| `isLista` | boolean | `true` when the token is the asset of a Lista DAO-curated vault (loan type only). | + +--- + +## See also + +- [Overall](overall.md) — protocol-wide snapshot. +- [Vault](vault.md) — vault listing and detail endpoints. +- [Position, Liquidation, Emission](position-liquidation-emission.md) — user positions and liquidatable accounts. +- [Lista Lending Smart Contract](../../lista-lending/smart-contract.md) — on-chain Moolah reference. +- [Integration Patterns](../../lista-lending/integration-patterns.md) — end-to-end integration flows. diff --git a/for-developer/services/lending-api/overall.md b/for-developer/services/lending-api/overall.md index 11bfc4e..e3d0726 100644 --- a/for-developer/services/lending-api/overall.md +++ b/for-developer/services/lending-api/overall.md @@ -1,32 +1,93 @@ # Overall (Protocol Snapshot) -## GET /api/moolah/overall - -Returns a protocol-level data snapshot (e.g. TVL, total supplied, total borrowed across chains). +A single protocol-wide summary of Lista Lending (Moolah): aggregate deposits, borrows, and collateral, the best vault APY, the lowest market borrow rate, and the top loan/collateral tokens by size. All paths are under **Base URL** `/api/moolah`. -**Method:** `GET` -**Path:** `/api/moolah/overall` +The response is a pre-computed snapshot served from a server-side cache and refreshed periodically by a background job, so values reflect the last sync rather than live on-chain state. Use the `updateAt` timestamp to judge freshness. USD amounts are returned as fixed-point decimal strings (18 decimal places) to avoid floating-point loss. --- -### Query parameters +## GET /api/moolah/overall -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `chainId` | number / string | No | Filter by chain (e.g. 56 = BSC, 1 = Ethereum). Omit for all chains. | +Returns the protocol snapshot. This endpoint takes **no query parameters**. ---- +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/moolah/overall` | +| **Query parameters** | None | ### Response -Object with protocol-wide metrics. Typical fields: +| Field | Type | Description | +|-------|------|-------------| +| `totalBorrowed` | string | Total borrowed across all markets, in USD (fixed-point, 18 decimals). | +| `totalCollateral` | string | Total collateral posted across all markets, in USD (fixed-point, 18 decimals). | +| `totalDeposits` | string | Total assets deposited across all vaults, in USD (fixed-point, 18 decimals). | +| `maxVaultApy` | string | Highest APY among active vaults (fixed-point, 18 decimals; e.g. `0.05` = 5%). | +| `minBorrowRate` | string | Lowest borrow rate among active markets (fixed-point, 18 decimals). | +| `loanTokens` | array | Top loan/deposit tokens by deposited USD, sorted descending (up to 10). See below. | +| `collateralTokens` | array | Top collateral tokens by available liquidity in USD, sorted descending (up to 10). See below. | +| `updateAt` | number | Unix timestamp (seconds) of the snapshot. | + +**Item in `loanTokens`:** | Field | Type | Description | |-------|------|-------------| -| `totalSupplyUsd` | string | Total supplied value in USD (all vaults/markets). | -| `totalBorrowUsd` | string | Total borrowed value in USD. | -| `tvlUsd` | string | Total value locked in USD. | -| `chainList` | array | List of chain IDs with data. | -| `updatedAt` | number / string | Timestamp of snapshot. | +| `tokenAddress` | string | Token contract address (lowercase). | +| `tokenSymbol` | string | Token symbol. | +| `tokenIcon` | string | Icon URL. | +| `amountInUSD` | number | Deposited amount for this token, in USD. | + +**Item in `collateralTokens`:** + +| Field | Type | Description | +|-------|------|-------------| +| `tokenAddress` | string | Token contract address (lowercase). | +| `tokenSymbol` | string | Token symbol. | +| `tokenIcon` | string | Icon URL. | +| `liquidityInUSD` | number | Available loan-side liquidity in markets using this collateral, in USD. | + +When the snapshot has not yet been populated, the endpoint returns a zeroed object: all amount/rate fields as `"0"`, `loanTokens` and `collateralTokens` as empty arrays, and `updateAt` as `0`. + +### Example + +```bash +curl https:///api/moolah/overall +``` + +```json +{ + "totalBorrowed": "12345678.900000000000000000", + "totalCollateral": "23456789.000000000000000000", + "totalDeposits": "34567890.100000000000000000", + "maxVaultApy": "0.084000000000000000", + "minBorrowRate": "0.021000000000000000", + "loanTokens": [ + { + "tokenAddress": "0x...", + "tokenSymbol": "USD1", + "tokenIcon": "https://...", + "amountInUSD": 20000000.0 + } + ], + "collateralTokens": [ + { + "tokenSymbol": "BTCB", + "tokenAddress": "0x...", + "tokenIcon": "https://...", + "liquidityInUSD": 8000000.0 + } + ], + "updateAt": 1730000000 +} +``` + +> Values above are illustrative. Token addresses are returned by the API; do not hard-code them. + +--- + +## Related -Additional keys (e.g. per-chain breakdown, vault count, market count) depend on the deployed API. +- [Vault API](vault.md) — per-vault list, detail, APY/deposit history, allocation. +- [Market API](market.md) — per-market data, borrow rates, search. +- [Position, Liquidation, Emission](position-liquidation-emission.md) — user-level data. diff --git a/for-developer/services/lending-api/position-liquidation-emission.md b/for-developer/services/lending-api/position-liquidation-emission.md index 00a92de..e73a31c 100644 --- a/for-developer/services/lending-api/position-liquidation-emission.md +++ b/for-developer/services/lending-api/position-liquidation-emission.md @@ -1,89 +1,379 @@ -# Position, Liquidation, Emission & CDP +# Positions, Liquidation & Emission API -APIs for **user position**, **liquidation**, **emission (rewards)**, and **CDP-style markets**. Paths and response schemas are implementation-specific; this page summarises typical usage and points to the underlying data/design docs. +Read endpoints for **user positions**, **liquidatable / at-risk positions**, **liquidation history**, and **emission (reward) merkle proofs** in Lista Lending (Moolah). -**Base URL:** `/api/moolah` +These endpoints are served by the Lista API across three route namespaces: + +| Namespace | Purpose | +|-----------|---------| +| `/api/moolah/*` | Moolah-market position and emission data. | +| `/api/v2/liquidations/*`, `/api/v2/liquidated`, `/api/liquidation/zone/*` | Liquidation feeds (at-risk lists, history, auction lookup). | +| `/api/v2/position/*` | Aggregated lending positions per collateral token. | + +> **CDP markets are separate.** The traditional single-collateral CDP markets (keyed by `ilk`, not a Moolah `marketId`) are served by a distinct controller at `/api/cdp/market/*` and are documented on their own page (see the end of this page). They are **not** a filter on the Moolah endpoints below. + +> Amounts are returned as decimal strings. Where a field name ends in `Wei` the value is the raw on-chain integer; otherwise the value has already been scaled by the token's decimals. Token addresses and oracle/IRM addresses are returned verbatim from the indexed market config. --- -## User position +## 1. Liquidatable positions (Moolah) + +### GET /api/moolah/redPositions + +Returns Moolah positions that are currently liquidatable for a given market — i.e. positions whose stored liquidation rate (`liqRate`) is above the live on-chain price returned by the market's oracle. Results are ordered by `liqRate` descending (most under-water first). + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/moolah/redPositions` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `id` | string | Yes | Market identifier (`marketId`). Unknown markets return an empty array. | +| `start` | number | No | Offset into the result set. Defaults to `0`. | +| `count` | number | No | Page size. Defaults to `20`. | + +#### Response + +Array of position objects: + +| Field | Type | Description | +|-------|------|-------------| +| `user` | string | Borrower address. | +| `collateral` | string | Collateral amount, scaled by the collateral token decimals. | +| `borrowed` | string | Borrowed amount, scaled by the loan token decimals. | +| `borrowShares` | string | Borrow shares (raw). | +| `totalBorrowAssets` | string | Market total borrow assets. | +| `totalBorrowShares` | string | Market total borrow shares. | +| `collateralToken` | string | Collateral token address. | +| `collateralDecimal` | number | Collateral token decimals. | +| `loanToken` | string | Loan token address. | +| `oracle` | string | Oracle contract address used for this market. | +| `lltv` | string | Liquidation LTV. | +| `collateralPrice` | string | Live oracle price used to select the position (raw, as returned by the on-chain `getPrice` call). | + +The liquidation condition is, in effect, `borrowed > collateral * price * lltv`. `borrowShares` / `totalBorrowAssets` / `totalBorrowShares` let an integrator recompute the exact current debt from shares before submitting a liquidation. + +--- + +## 2. Liquidation zone (Moolah) + +A higher-level liquidation feed for Moolah markets, used to surface positions that are at risk or have already been liquidated. All paths are under `/api/liquidation/zone`. + +### GET /api/liquidation/zone/list + +Positions on the liquidation whitelist (eligible / flagged for liquidation). + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/liquidation/zone/list` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `page` | number | No | Page number. Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `20`, capped at `50`. | +| `collaterals` | string[] | No | Filter by collateral symbol(s). Max 10. | +| `loans` | string[] | No | Filter by loan symbol(s). Max 10. | +| `loanInUsd` | number | No | Minimum borrow value in USD (rounded down to the nearest 1,000). | -APIs that return a user’s positions across markets (collateral, borrowed amount, health factor, liquidation price). +#### Response -**Typical paths:** +| Field | Type | Description | +|-------|------|-------------| +| `total` | number | Total matching rows. | +| `list` | array | Position objects (see below). | + +**Item in `list`:** + +| Field | Type | Description | +|-------|------|-------------| +| `type` | string | Entry type. | +| `marketId` | string | Market identifier. | +| `user` | string | Borrower address. | +| `collateral` | string | Collateral amount. | +| `borrowed` | string | Borrowed amount. | +| `borrowShares` | string | Borrow shares. | +| `collateralToken` | string | Collateral token address. | +| `collateralDecimal` | number | Collateral token decimals. | +| `collateralSymbol` | string | Collateral symbol. | +| `collateralIcon` | string | Collateral icon URL. | +| `loanValueUsd` | string | Borrow value in USD. | +| `loanToken` | string | Loan token address. | +| `loanDecimal` | number | Loan token decimals. | +| `loanSymbol` | string | Loan symbol. | +| `oracle` | string | Market oracle address. | +| `lltv` | string | Liquidation LTV. | +| `time` | number | Entry time (unix seconds). | +| `chain` | string | Chain identifier of the market. | + +### GET /api/liquidation/zone/history + +Completed Moolah liquidations. + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/liquidation/zone/history` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `page` | number | No | Page number. Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `20`, capped at `50`. | +| `collaterals` | string[] | No | Filter by collateral symbol(s). Max 10. | +| `loans` | string[] | No | Filter by loan symbol(s). Max 10. | +| `userAddress` | string | No | Filter by borrower address. | +| `loanInUsd` | number | No | Minimum borrow value in USD (rounded down to the nearest 1,000). | -- `GET /api/moolah/position/list` — list positions for a user (query: `userAddress`, `chainId`, `page`, `pageSize`). -- `GET /api/moolah/position/detail` — single position (query: `userAddress`, `marketId`, optional `chainId`). -- `GET /api/moolah/user/:userAddress/positions` — alternative: path param for user, query for chain/page. +#### Response -**Typical response fields per position:** +`{ total, list }`, where each item describes a settled liquidation: | Field | Type | Description | |-------|------|-------------| -| `userAddress` | string | Wallet address. | +| `type` | string | Entry type. | | `marketId` | string | Market identifier. | -| `chainId` | number | Chain ID. | -| `collateralAmount` | string | Supplied collateral. | -| `borrowedAmount` | string | Actual borrowed amount. | -| `liquidationPrice` / `liquidationPriceRatio` | string | Price (or ratio) at which position becomes liquidatable. | -| `healthFactor` | string | Health factor (if provided). | +| `user` | string | Borrower (liquidated) address. | +| `liquidator` | string | Liquidator address. | +| `repaidShares` | string | Borrow shares repaid by the liquidator. | +| `repaidAssets` | string | Debt assets repaid. | +| `repaidInUsd` | string | Repaid value in USD. | +| `seizedAssets` | string | Collateral seized. | +| `seizedInUsd` | string | Seized value in USD. | +| `collateralMarketPrice` | string | Collateral price at liquidation. | +| `collateralToken` / `collateralSymbol` / `collateralDecimal` / `collateralIcon` | string / number | Collateral token metadata. | +| `loan` | string | Loan amount. | +| `loanInUsd` | string | Loan value in USD. | +| `loanToken` / `loanSymbol` / `loanDecimal` | string / number | Loan token metadata. | +| `lltv` | string | Liquidation LTV. | +| `time` | number | Liquidation time (unix seconds). | +| `chain` | string | Chain identifier. | -Data source and formulas are described in [Position data maintenance](../position-data-maintenance.md). +### GET /api/liquidation/zone/closeToLiquidate + +Healthy-but-at-risk positions: open positions whose safety factor (`marketLiqRate / positionLiqRate`) is below `1.5`. Ordered by safety factor ascending (closest to liquidation first). + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/liquidation/zone/closeToLiquidate` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `page` | number | No | Page number. Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `20`, capped at `50`. | +| `collaterals` | string \| string[] | No | Filter by collateral symbol(s). | +| `loans` | string \| string[] | No | Filter by loan symbol(s). | +| `userAddress` | string | No | Filter by borrower address. | +| `loanInUsd` | number | No | Minimum borrow value in USD. | + +#### Response + +`{ total, list }`, where each item includes: + +| Field | Type | Description | +|-------|------|-------------| +| `marketId` | string | Market identifier. | +| `user` | string | Borrower address. | +| `collateral` | string | Collateral amount. | +| `borrowed` | string | Borrowed amount. | +| `collateralToken` / `collateralSymbol` / `collateralIcon` | string | Collateral token metadata. | +| `collateralPrice` | string | Collateral price. | +| `loanToken` / `loanSymbol` / `loanIcon` | string | Loan token metadata. | +| `loanPrice` | string | Loan price. | +| `lltv` | string | Liquidation LTV. | +| `safeFactor` | string | Safety factor (`< 1.5`; smaller is closer to liquidation). | +| `loanInUsd` | string | Borrow value in USD. | +| `time` | number | Position update time. | +| `chain` | string | Chain identifier. | --- -## Liquidation +## 3. Liquidation feeds (legacy CDP collaterals) + +These `/api/v2/liquidations/*` and `/api/v2/liquidated` endpoints serve the **CDP (single-collateral) borrow product**, not Moolah markets. They read from the borrower index and key results by collateral token address. Use the Moolah endpoints above for Moolah-market liquidations. -Endpoints for liquidatable positions or liquidation history. +### GET /api/v2/liquidations/red -**Typical paths:** +Positions that are currently liquidatable (current price has crossed the position's liquidation price). -- `GET /api/moolah/liquidation/liquidatable` — list positions that can be liquidated (query: `chainId`, `marketId`, `page`, `pageSize`). -- `GET /api/moolah/liquidation/history` — past liquidations (query: `userAddress`, `marketId`, `chainId`, `startTime`, `endTime`, pagination). +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `start` | number | No | Offset (snapped to a multiple of 10). | +| `count` | number | No | Page size (snapped to a multiple of 20; min 20, max 100). | -**Typical response fields (liquidatable list):** +Response: `{ users: [...] }`, each entry containing `userAddress`, `tokenName`, `collateralCurrency`, `collateral`, `liquidationPrice`, `liquidationCost`, `rangeFromLiquidation`. + +### GET /api/v2/liquidations/orange + +Positions approaching the liquidation threshold (within the danger band, but not yet liquidatable). Same parameters and response shape as `/red`; `rangeFromLiquidation` reflects the remaining buffer to liquidation. + +### GET /api/v2/liquidations/auctionUser + +Look up the borrower(s) and clipper (auction contract) for a given liquidation auction. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `auctionId` | number | No | Auction identifier. | +| `token` | string | No | Collateral token address. | + +Response: `{ users: [{ userAddress, clipperAddress }] }`. + +### GET /api/v2/liquidated + +Recently liquidated CDP positions. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `start` | number | No | Offset. Defaults to `0`. | +| `count` | number | No | Page size. Defaults to and capped at `20`. | + +Related sub-paths on the same controller: `GET /api/v2/liquidated/:user` (liquidations for one address), `GET /api/v2/liquidated/:user/latest?collateral=` (volume-weighted average liquidation price for an address + collateral), and `GET /api/v2/liquidated/lending/history` (Moolah lending liquidation history, paginated by `page` / `pageSize` with `collaterals` / `loans` / `userAddress` / `loanInUsd` filters). + +--- + +## 4. Aggregated lending positions + +### GET /api/v2/position/tokenLendingUserPositions + +Returns, for a whitelisted collateral token, each address's total collateral supplied across all Moolah markets that use that collateral. Used for token-holder / position snapshots. The response is keyset-paginated by address. + +| | | +|--|--| +| **Method** | `GET` | +| **Path** | `/api/v2/position/tokenLendingUserPositions` | + +#### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `lastAddress` | string | Yes | Return addresses strictly greater than this (keyset cursor). Use the empty/zero address for the first page. | +| `collateral` | string | Yes | Collateral token address. Must be a whitelisted token, otherwise the request is rejected. | +| `limit` | number | Yes | Page size, clamped to min 20 / max 500. | + +#### Response + +Array, ordered by `address` ascending: | Field | Type | Description | |-------|------|-------------| -| `userAddress` | string | Borrower. | -| `marketId` | string | Market. | -| `borrowedAmount` | string | Debt. | -| `collateralAmount` | string | Collateral. | -| `liquidationPrice` | string | Trigger price. | -| `currentPrice` | string | Current oracle price. | +| `address` | string | Holder address. | +| `amount` | string | Total collateral supplied by that address across markets using `collateral`. | + +To page, pass the last `address` of the previous response as `lastAddress` on the next call. -How risk and liquidation are determined: [Liquidation logic](../liquidation-logic.md). +> A sibling endpoint `GET /api/v2/position/tokenUserPositions` (same parameters) returns the equivalent aggregate for the legacy CDP borrower index rather than Moolah positions. --- -## Emission (rewards) +## 5. Emission (rewards) — merkle proofs + +Lista Lending distributes emission rewards via a **weekly merkle-root** model: an off-chain job publishes a merkle root per week, and each eligible user fetches their leaf (amount + merkle proof) from the API and claims on-chain. These endpoints therefore return a **proof to claim**, not a pre-credited balance. + +> **Authentication (wallet signature required).** The proof endpoints require the caller to prove control of the address. Each request carries `address`, `signature`, and `message`. The `message` must be an exact **two-line** body, validated by a strict regex: an ISO-8601 UTC timestamp (e.g. `2026-06-30T12:00:00Z`) on the first line, then the literal second line `Thank you for your support of listaDAO.` — and the timestamp must be no older than 7 days. Verification is performed two ways depending on `type`: +> - `type=safe` — the signature is validated against the address as an ERC-1271 contract wallet (Safe), via `isValidSignature`. +> - otherwise — the signature is recovered as a standard EOA wallet signature and must match `address`. +> +> An invalid signature returns an "invalid signature" error; an expired message returns a "token expired" error. No server-side secret or key is involved — this is a standard wallet-signature challenge. -Endpoints for reward rates, claimable amounts, and distribution config. +### GET /api/moolah/emission/userProof -**Typical paths:** +Returns the LISTA-emission merkle proof for the latest active weekly root. -- `GET /api/moolah/emission/rates` — reward rates per vault/market. -- `GET /api/moolah/emission/claimable` — claimable rewards for a user (query: `userAddress`, `chainId`). -- `GET /api/moolah/rewards/config` — distribution config (if exposed). +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `address` | string | Yes | Claiming address. | +| `signature` | string | Yes | Wallet signature over `message`. | +| `message` | string | Yes | Signed message: an ISO-8601 UTC timestamp line, then the literal line `Thank you for your support of listaDAO.` (strict regex; timestamp ≤ 7 days old). | +| `type` | string | No | `safe` for ERC-1271 (Safe) wallets; omit/other for EOA. | -**Typical response fields (claimable):** +#### Response | Field | Type | Description | |-------|------|-------------| -| `userAddress` | string | Wallet. | -| `vaultId` / `marketId` | string | Scope of rewards. | -| `claimableAmount` | string | Claimable reward amount. | -| `asset` / `token` | string | Reward token address. | +| `rootId` | string | Week identifier of the root the proof belongs to. | +| `amount` | string | Claimable amount (scaled). | +| `amountWei` | string | Claimable amount (raw integer). | +| `proof` | string[] | Merkle proof nodes. | +| `currentAmount` | string | Amount attributable to the current week. | -Rewards are derived from the position table and reward distributor contracts; see [Position data maintenance](../position-data-maintenance.md). +When the user has no leaf for the latest root, an empty proof is returned (`rootId: ""`, `amount: "0"`, `amountWei: "0"`, `proof: ""`). + +### GET /api/moolah/emission/userMultiProof + +Returns per-token emission proofs (the multi-token reward stream), each entry pairing a claimable merkle proof with an estimated-reward breakdown. + +Parameters: same auth parameters as `/userProof` (`address`, `signature`, `message`, `type`). + +#### Response + +Array, one entry per reward token: + +| Field | Type | Description | +|-------|------|-------------| +| `token` | string | Reward token address. | +| `tokenSymbol` | string | Reward token symbol. | +| `tokenIcon` | string | Reward token icon URL. | +| `amount` | string | Claimable amount (scaled); `0` if only an estimate exists. | +| `amountWei` | string | Claimable amount (raw); `0` if estimate-only. | +| `proof` | string[] | Merkle proof nodes; empty if estimate-only. | +| `currentAmount` | string | Amount for the current period. | +| `estRewards` | object | Map of `symbol → estimated USD value`. | +| `estRewardDetails` | array | Per-symbol `{ symbol, estRewards, icon, amount }`. | + +Tokens with only an accruing estimate (no finalized leaf yet) appear with empty `proof` / zero `amount`. + +### GET /api/moolah/emission/userRewardHistory + +Paginated history of an address's finalized per-token emission rewards. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `address` | string | Yes | Claiming address. | +| `signature` | string | Yes | Wallet signature over `message`. | +| `message` | string | Yes | Signed message: an ISO-8601 UTC timestamp line, then the literal line `Thank you for your support of listaDAO.` (strict regex; timestamp ≤ 7 days old). | +| `type` | string | No | `safe` for ERC-1271 (Safe) wallets; omit/other for EOA. | +| `page` | number | No | Page number. Defaults to `1`. | +| `pageSize` | number | No | Items per page. Defaults to `10`, capped at `50`. | + +#### Response + +| Field | Type | Description | +|-------|------|-------------| +| `total` | number | Total history rows. | +| `list` | array | History entries (see below). | + +**Item in `list`:** + +| Field | Type | Description | +|-------|------|-------------| +| `token` | string | Reward token address. | +| `tokenSymbol` | string | Reward token symbol. | +| `tokenIcon` | string | Reward token icon URL. | +| `amount` | string | Reward amount (scaled). | +| `amountWei` | string | Reward amount (raw). | +| `currentAmount` | string | Amount attributable to that week. | +| `weeks` | string | Week identifier. | --- -## CDP market (traditional collateralized debt) +## 6. CDP markets (separate controller) -CDP-style markets (single-collateral borrow against a stablecoin) may be exposed as: +Traditional single-collateral CDP markets are keyed by an `ilk` (collateral type) rather than a Moolah `marketId`, and live in their own namespace at `/api/cdp/market/*` (`/search`, `/list`, `/info`, `/borrowRate/history`, `/userBorrow/history`). They are documented separately — do not query them through the Moolah position/liquidation endpoints above. + +--- -- A **market type** or **filter** in [Market list](market.md) (e.g. `type=cdp` or `market/search/cdp`). -- Same [Market detail](market.md) path with a `marketId` that refers to a CDP market. +## Related pages -Contract layout can differ from standard Moolah markets; use [Market detail](market.md) and [All markets (on-chain)](market.md#5-all-markets-on-chain) to get exact on-chain config. +- [Market API](market.md) — markets, oracles, borrow-rate history, on-chain market config. +- [Vault](vault.md) — vault list, detail, allocation. +- [Position Data Maintenance](../position-data-maintenance.md) — how positions and liquidation rates are indexed off-chain. +- [Liquidation Logic (Service)](../liquidation-logic.md) — how at-risk and liquidatable positions are determined. diff --git a/for-developer/services/lending-api/vault.md b/for-developer/services/lending-api/vault.md index ea1ba77..c0f30b0 100644 --- a/for-developer/services/lending-api/vault.md +++ b/for-developer/services/lending-api/vault.md @@ -1,6 +1,12 @@ # Vault API -Vaults are aggregate contracts where LPs deposit assets; liquidity is allocated across multiple lending markets. All paths are under **Base URL** `/api/moolah`. +A vault is an aggregate contract where suppliers deposit a single loan asset; a curator allocates that liquidity across multiple lending [markets](market.md). The Vault API exposes the vault list, per-vault detail, historical deposit/APY snapshots, and the per-market allocation breakdown. + +All paths are under **Base URL** `/api/moolah`. + +> **Chain selector.** Endpoints that span chains take a **string** `chain` parameter — `bsc`, `bscTest`, or `ethereum` — **not** a numeric chain ID. `/vault/list` accepts a comma-separated list (e.g. `chain=bsc,ethereum`); when omitted it defaults to the live network. Detail and history endpoints resolve the chain from the vault `address` and take no `chain` parameter. + +> **List conventions.** Paginated list endpoints sort with the pair `sort` (a field key from the whitelist below) + `order` (`asc` | `desc`, default `desc`). `pageSize` defaults to `10` and is capped at `50`; `page` is 1-based. An unrecognized `sort` falls back to the endpoint default. --- @@ -8,7 +14,7 @@ Vaults are aggregate contracts where LPs deposit assets; liquidity is allocated ### GET /api/moolah/vault/list -Paginated list of vaults with filters and sorting. +Paginated list of vaults with filtering and sorting. | | | |--|--| @@ -19,31 +25,48 @@ Paginated list of vaults with filters and sorting. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `chainId` | number / string | No | Filter by chain (e.g. 56, 1). | -| `page` | number | No | Page number (1-based). Default: 1. | -| `pageSize` | number | No | Items per page. Default: 10 or 20. | -| `sortBy` | string | No | Sort field (e.g. `totalAssets`, `apy`, `createdAt`). | -| `sortOrder` | string | No | `asc` or `desc`. | +| `chain` | string | No | `bsc` \| `bscTest` \| `ethereum`. Comma-separated for multiple (e.g. `bsc,ethereum`). Defaults to the live network. | +| `page` | number | No | 1-based page number. Default `1`. | +| `pageSize` | number | No | Items per page. Default `10`, max `50` (values above 50 are clamped). | +| `assets` | string[] | No | Filter by deposit-asset **symbol** (matched against the vault's asset symbol, e.g. `assets=USD1&assets=WBNB`). | +| `curators` | string[] | No | Filter by curator address. | +| `keyword` | string | No | Free-text search over vault name/keywords. Max 50 chars; ignored if empty. | +| `sort` | string | No | Sort field key — one of `deposits`, `apy`, `utilization`. Unknown values fall back to `deposits`. | +| `order` | string | No | `asc` or `desc`. Default `desc`. | +| `zone` | number | No | Zone (segment) filter. Default `0`. | + +> Results are always pre-ordered by an internal `priority` first, then by the requested `sort`/`order`. #### Response | Field | Type | Description | |-------|------|-------------| -| `list` | array | List of vault objects. | -| `total` | number | Total count (for pagination). | +| `total` | number | Total matching vaults (for pagination). | +| `list` | array | Vault summary objects. | **Item in `list`:** | Field | Type | Description | |-------|------|-------------| -| `vaultId` | string | Vault identifier (e.g. contract address). | -| `chainId` | number | Chain ID. | -| `asset` | string | Deposit asset address or symbol. | -| `assetSymbol` | string | Symbol (e.g. WBNB, USD1). | -| `totalAssets` | string | Total assets in vault (human-readable). | -| `totalAssetsUsd` | string | Total value in USD. | +| `address` | string | Vault contract address (lower-cased). | +| `name` | string | Vault display name. | +| `icon` | string | Vault icon URL. | | `apy` | string | Current supply APY. | -| `marketCount` | number | Number of markets the vault allocates to. | +| `emissionApy` | string | Additional APY from token emissions/rewards. | +| `emissionEnabled` | boolean | Whether reward emissions are active for this vault. | +| `emissionDetail` | array | Reward breakdown (token + rate metadata) derived from the current reward config. | +| `deposits` | string | Total assets deposited (token units). | +| `depositsUsd` | string | Total deposits valued in USD. | +| `asset` | string | Deposit (loan) asset token address. | +| `assetSymbol` | string | Deposit asset symbol (e.g. `USD1`, `WBNB`). | +| `assetIcon` | string | Deposit asset icon URL. | +| `displayDecimal` | string | Decimals to use when displaying amounts. | +| `curator` | string | Curator address. | +| `curatorIcon` | string | Curator icon URL. | +| `collaterals` | array | Collateral assets reachable through this vault's markets (`{ id, name, icon }`). | +| `zone` | number | Zone (segment) the vault belongs to. | +| `chain` | string | Chain the vault is deployed on (`bsc` \| `ethereum` \| `bscTest`). | +| `utilization` | string | Fraction of deposits currently lent out, clamped to `[0, 1]` (18-decimal string). | --- @@ -51,7 +74,7 @@ Paginated list of vaults with filters and sorting. ### GET /api/moolah/vault/info -Full details for a single vault. +Full details for a single vault, including its curator metadata and the collateral markets it allocates to. | | | |--|--| @@ -62,31 +85,52 @@ Full details for a single vault. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `vaultId` | string | Yes | Vault identifier (e.g. contract address). | -| `chainId` | number / string | No | Chain ID (required if vault exists on multiple chains). | +| `address` | string | Yes | Vault contract address. The chain is resolved from the vault record. | #### Response | Field | Type | Description | |-------|------|-------------| -| `vaultId` | string | Vault identifier. | -| `chainId` | number | Chain ID. | -| `asset` | string | Deposit asset address. | -| `assetSymbol` | string | Symbol. | -| `totalAssets` | string | Total assets. | -| `totalAssetsUsd` | string | Total value in USD. | +| `address` | string | Vault contract address. | +| `name` | string | Vault display name. | +| `icon` | string | Vault icon URL. | +| `description` | string | Vault description (English). | +| `descriptionZh` | string | Vault description (Chinese). | +| `deposits` | string | Total assets deposited (token units). | | `apy` | string | Current supply APY. | -| `marketIds` | array | List of market IDs this vault supplies to. | -| `allocationSummary` | object / array | Per-market allocation (amount or proportion). | +| `liquidity` | string | Idle (un-allocated) liquidity in the vault. | +| `emissionApy` | string | Additional APY from token emissions/rewards. | +| `emissionEnabled` | boolean | Whether reward emissions are active. | +| `emissionDetail` | array | Reward breakdown derived from the current reward config. | +| `asset` | string | Deposit (loan) asset token address. | +| `assetSymbol` | string | Deposit asset symbol. | +| `assetIcon` | string | Deposit asset icon URL. | +| `assetPrice` | string | Current USD price of the deposit asset (8-decimal string). | +| `displayDecimal` | string | Decimals to use when displaying amounts. | +| `curator` | string | Curator address. | +| `curatorIcon` | string | Curator icon URL. | +| `curatorDesc` | string | Curator description (English). | +| `curatorDescZh` | string | Curator description (Chinese). | +| `curatorX` | string | Curator X (Twitter) handle/URL. | +| `curatorUrl` | string | Curator website URL. | +| `createAt` | number | Vault creation timestamp. | +| `utilization` | string | Fraction of deposits currently lent out (18-decimal string). | +| `zone` | number | Zone (segment) the vault belongs to. | +| `chain` | string | Chain the vault is deployed on. | +| `status` | number | Vault status code. | +| `collaterals` | array | Markets the vault supplies to — each `{ id, collateral, name, icon }` where `id` is the market ID and `collateral` the collateral token address. | +| `styleType` | number | UI style hint. Defaults to `1`. | + +> An unknown `address` returns an empty object `{}`. --- ## 3. Vault deposit / APY history -### GET /api/moolah/vault/deposit/history +### GET /api/moolah/vault/deposit/history ### GET /api/moolah/vault/apy/history -Both return daily snapshots of deposit size and APY for a vault over a time range. Response shape is the same. +Daily snapshots of a vault's total deposits and APY over a time range. Both endpoints share the same handler and return the same shape; `/vault/apy/history` is an alias of `/vault/deposit/history`. | | | |--|--| @@ -97,22 +141,21 @@ Both return daily snapshots of deposit size and APY for a vault over a time rang | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `vaultId` | string | Yes | Vault identifier. | -| `chainId` | number / string | No | Chain ID. | -| `startTime` | number / string | No | Start timestamp (seconds or ms). | -| `endTime` | number / string | No | End timestamp. | +| `address` | string | Yes | Vault contract address. | +| `startTime` | number | No | Range start, UNIX timestamp in **seconds** (UTC day boundary). | +| `endTime` | number | No | Range end, UNIX timestamp in **seconds** (UTC day boundary). | #### Response -Array of daily snapshot objects: +Array of daily snapshot objects, ordered ascending by date: | Field | Type | Description | |-------|------|-------------| -| `date` | string | Date (e.g. YYYY-MM-DD). | -| `timestamp` | number | Start-of-day timestamp. | -| `totalDeposit` | string | Total deposit in vault at that day. | -| `totalDepositUsd` | string | Value in USD. | -| `apy` | string | APY on that day. | +| `chartTime` | number | Start-of-day UNIX timestamp (seconds). | +| `apy` | string | Base supply APY for that day. | +| `emissionApy` | string | Reward/emission APY for that day. | +| `totalAssets` | string | Total assets in the vault that day (token units). | +| `totalAssetsUsd` | string | Total assets valued in USD that day. | --- @@ -120,7 +163,7 @@ Array of daily snapshot objects: ### GET /api/moolah/vault/allocation -Paginated list of how a vault’s liquidity is allocated across markets. +Paginated breakdown of how a vault's liquidity is allocated across its lending markets. | | | |--|--| @@ -131,23 +174,50 @@ Paginated list of how a vault’s liquidity is allocated across markets. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `vaultId` | string | Yes | Vault identifier. | -| `chainId` | number / string | No | Chain ID. | -| `page` | number | No | Page number. | -| `pageSize` | number | No | Items per page. | +| `address` | string | Yes | Vault contract address. | +| `page` | number | No | 1-based page number. Default `1`. | +| `pageSize` | number | No | Items per page. Default `10`, max `50`. | +| `sort` | string | No | Sort field key — one of `allocation`, `totalSupply`, `liquidity`, `utilization`, `cap`, `borrowRate`. Default `totalSupply`. | +| `order` | string | No | `asc` or `desc`. Default `desc`. | +| `keyword` | string | No | Free-text search over the allocated market's collateral name/keywords. | +| `zone` | string | No | Comma-separated zone (segment) filter applied to the underlying markets. | + +> Rows are pre-ordered by an internal market `priority` first, then by the requested `sort`/`order`. An unknown vault `address` returns `{ total: 0, list: [] }`. #### Response | Field | Type | Description | |-------|------|-------------| +| `total` | number | Total allocated markets (for pagination). | | `list` | array | Allocation rows. | -| `total` | number | Total count. | **Item in `list`:** | Field | Type | Description | |-------|------|-------------| -| `marketId` | string | Market identifier. | -| `allocatedAssets` | string | Amount allocated to this market. | -| `proportion` | string | Proportion (e.g. 0.5 = 50%). | -| `apy` | string | Supply APY for this market. | +| `id` | string | Market identifier the liquidity is allocated to. | +| `name` | string | Market display name. | +| `collateralSymbol` | string | Collateral token symbol. | +| `collateralIcon` | string | Collateral token icon URL. | +| `loanSymbol` | string | Loan token symbol. | +| `loanIcon` | string | Loan token icon URL. | +| `icon` | string | Market icon URL. | +| `allocation` | string | Amount of vault liquidity allocated to this market. | +| `totalSupply` | string | Vault-supplied amount tracked for this market. | +| `cap` | string | Supply cap the vault has set for this market. | +| `liquidity` | string | Market liquidity available to borrow (18-decimal string). | +| `price` | string | USD price of the loan asset (8-decimal string). | +| `supplyApy` | string | Supply APY for this market. | +| `zone` | number | Zone (segment) of the market. | +| `smartCollateralConfig` | object | Smart-collateral configuration for the market, if any. | +| `utilization` | string | Market utilization (borrowed / supplied). | +| `borrowRate` | string | Current borrow rate for the market. | +| `rewards` | array | Reward token entries attached to the market. | + +--- + +## Notes + +- **Amounts are strings.** Token-denominated and APY/USD values are returned as decimal strings to preserve precision; use the `displayDecimal` hint for presentation. +- **Economic values are live, on-chain-derived.** APY, utilization, deposits, caps, and emission figures reflect the current protocol state and are governance/manager-adjustable on-chain — treat them as snapshots, not fixed terms. +- For the per-market interest-rate, LLTV, and oracle details behind an allocation, see the [Market API](market.md). For protocol-wide totals, see [Overall](overall.md). From f22ac7628f50d80d06e9cafd704da75fb1625cc2 Mon Sep 17 00:00:00 2001 From: tyler-tsai <15355143+tyler-tsai@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:01:11 +0800 Subject: [PATCH 3/4] docs(sdk): add Moolah Lending SDK integration guide New canonical @lista-dao/moolah-lending-sdk + moolah-sdk-core page: install, MoolahSDK init, read methods, StepParam[] builder pattern, simulation, Decimal utility, MoolahApiClient. (Re-export scope: lending SDK re-exports a curated subset; simulate fns/ABIs come from sdk-core.) Co-Authored-By: Claude Opus 4.8 --- for-developer/sdk.md | 325 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 for-developer/sdk.md diff --git a/for-developer/sdk.md b/for-developer/sdk.md new file mode 100644 index 0000000..3b84707 --- /dev/null +++ b/for-developer/sdk.md @@ -0,0 +1,325 @@ +# Moolah Lending SDK (TypeScript) + +The Moolah Lending SDK is the canonical TypeScript integration path for Lista Lending (Moolah) on BNB Chain and Ethereum. It is published on public npm under the `@lista-dao` scope and is MIT-licensed. + +The SDK is **wallet-agnostic and transport-light**: read methods call the chain (via [viem](https://viem.sh)) or the public Lista API, and write methods return an array of plain transaction-step descriptors (`StepParam[]`) that you execute with your own wallet client. The SDK never holds keys, never signs, and never broadcasts on your behalf. + +For the contract-level mechanics behind these calls, see [Integration Patterns](lista-lending/integration-patterns.md) and the [Smart Contract](lista-lending/smart-contract.md) reference. For the underlying REST surface, see the [Moolah Lending API](services/lending-api/README.md). + +--- + +## Packages + +| Package | Version | Description | +| --- | --- | --- | +| [`@lista-dao/moolah-sdk-core`](https://www.npmjs.com/package/@lista-dao/moolah-sdk-core) | `1.0.12` | Core types, pure calculation/simulation functions, contract ABIs, the `Decimal` precision utility, and `MoolahApiClient`. No wallet or chain dependency for the pure functions. | +| [`@lista-dao/moolah-lending-sdk`](https://www.npmjs.com/package/@lista-dao/moolah-lending-sdk) | `1.0.11` | The high-level `MoolahSDK` builder. Reads chain/API data and returns transaction steps (`StepParam[]`). Re-exports a curated subset of the core package (`Decimal`, `MoolahApiClient`, contract-address helpers, builder step functions, and types). | + +`moolah-lending-sdk` depends on `moolah-sdk-core`, so installing the lending SDK pulls the core package in and re-exports a curated convenience subset of it — the core types, `Decimal`, `MoolahApiClient`, and the contract-address helpers. The pure **simulate functions**, interest-rate helpers, and contract **ABIs** are *not* re-exported; import those directly from `@lista-dao/moolah-sdk-core` (as the examples below do). Install `moolah-sdk-core` on its own if you only want the pure calculation/type layer without the builder. + +## Installation + +```bash +# For integrators building transactions with their own wallet +pnpm add @lista-dao/moolah-lending-sdk + +# For core utilities / types / ABIs only +pnpm add @lista-dao/moolah-sdk-core +``` + +The SDK targets ES modules (`"type": "module"`) and ships its own type declarations. `viem` is a peer of the transaction-building flow; install it in your app if you plan to execute the returned steps. + +```bash +pnpm add viem +``` + +## Initializing the SDK + +`MoolahSDK` is configured with per-chain RPC URLs keyed by numeric chain ID. + +```typescript +import { MoolahSDK } from "@lista-dao/moolah-lending-sdk"; + +const sdk = new MoolahSDK({ + rpcUrls: { + 56: "https://bsc-dataseed.binance.org", // BSC mainnet + 1: "https://eth.llamarpc.com", // Ethereum mainnet + }, +}); +``` + +| Config field | Type | Notes | +| --- | --- | --- | +| `rpcUrls` | `Record` | RPC endpoint(s) per chain ID. Pass an array to use multiple endpoints for a chain. **Required.** | +| `apiBaseUrl` | `string` | Override the Lista API base URL used by API-backed read methods. Defaults to the public production API. | +| `publicClients` | `Record` | Supply your own viem `PublicClient` per chain ID instead of having the SDK build one from `rpcUrls`. | +| `transport` / `transportByChainId` | `SdkTransportConfig` | Optional `timeout`, `retryCount`, `retryDelay` tuning, globally or per chain ID. | + +> **Networks.** Lista Lending runs on BNB Chain (chain ID `56`) and Ethereum (chain ID `1`). The SDK accepts a chain ID anywhere a `chainId` argument is shown below. + +`sdk.getApiChain(chainId)` maps a chain ID to the string the API expects (`"bsc"` or `"ethereum"`), which you pass to the API-backed list methods. + +```typescript +const chain = sdk.getApiChain(56); // "bsc" +``` + +--- + +## Read operations + +Read methods are split by source: **Chain** methods read on-chain state through your RPC; **API** methods read from the public Lista API. Human-scaled economic amounts are returned as [`Decimal`](#decimal-utility) rather than `number` or `bigint`; raw on-chain integers — share amounts, timestamps, rate caps/floors, and the `MarketParams` struct — remain `bigint`. + +| Method | Source | Returns | +| --- | --- | --- | +| `getMarketExtraInfo(chainId, marketId)` | Chain | `MarketExtraInfo` — market on-chain state (rates, liquidity, LLTV, price rate). | +| `getMarketUserData(chainId, marketId, user)` | Chain | `MarketUserData` — a user's position in a market. | +| `getMarketUserDataWithBroker(chainId, marketId, user, broker)` | Chain | `MarketUserData` merged with fixed-term broker positions. | +| `getMarketRuntimeData(chainId, marketId, wallet)` | Chain | `MarketRuntimeData` — combined extra info + write config + user data in one call. | +| `getWriteConfig(chainId, marketId)` | Chain | `WriteMarketConfig` — the config needed to build write transactions. | +| `getVaultInfo(chainId, address)` | Chain | `VaultInfo` — ERC-4626 vault on-chain state. | +| `getVaultUserData(chainId, address, user)` | Chain | `VaultUserData` — a user's vault position. | +| `getSmartMarketExtraInfo(chainId, marketId)` | Chain | `SmartMarketExtraInfo` — Smart Lending (LP-collateral) market state. | +| `getSmartMarketUserData(chainId, marketId, user)` | Chain | `SmartMarketUserData` — a user's Smart Lending position. | +| `getBrokerFixedTerms(chainId, broker)` | Chain | `FixedTermAndRate[]` — available fixed terms/rates for a broker. | +| `getBrokerUserPositions(chainId, broker, user)` | Chain | `BrokerUserPositionsData` — a user's broker positions. | +| `getMarketInfo(chainId, marketId)` | API | `MarketInfo` — market metadata (curator, descriptions, display APY). | +| `getMarketList(params)` | API | `ApiMarketList` — paginated market discovery. | +| `getVaultList(params)` | API | `ApiVaultList` — paginated vault discovery. | +| `getVaultMetadata(address)` | API | `ApiVaultInfo` — vault metadata (name, APY). | +| `getMarketVaultDetails(marketId, params)` | API | Vaults that allocate to a given market. | +| `getHoldings(params)` | API | A user's holdings (`type: "vault"` or `type: "market"`). | + +```typescript +const chainId = 56; + +// On-chain reads +const marketExtra = await sdk.getMarketExtraInfo(chainId, marketId); +const userData = await sdk.getMarketUserData(chainId, marketId, userAddress); + +// API reads (paginated discovery) +const chain = sdk.getApiChain(chainId); // "bsc" +const markets = await sdk.getMarketList({ chain, page: 1, pageSize: 20, order: "desc" }); +const vaults = await sdk.getVaultList({ chain, page: 1, pageSize: 20 }); +``` + +`MarketExtraInfo` and `MarketUserData` expose the market's current on-chain economic state — `LLTV`, `borrowRate`, `utilRate`, `priceRate`, `minLoan`, the user's `collateral`, `borrowed`, `loanable`, `withdrawable`, and `LTV`. These are the live, governance/manager-adjustable on-chain values at read time; treat them as current state, not fixed parameters. + +--- + +## Building transactions: the `StepParam[]` pattern + +Write operations are **builders**, not senders. Each `build*Params` method reads whatever it needs from chain/config and returns an ordered `StepParam[]`. You iterate the steps and execute them with your own wallet client. This keeps key custody and signing entirely in your control. + +A `StepParam` is a plain descriptor: + +```typescript +interface StepParam { + step: + | "approve" | "supply" | "borrow" | "repay" | "withdraw" + | "depositVault" | "withdrawVault" + | "supplySmartDexLp" | "supplySmartCollateral" + | "withdrawSmartDexLp" | "withdrawSmartCollateral" + | "withdrawSmartCollateralFixed" | "repaySmartMarket" + | "brokerBorrow" | "brokerRepay"; + params: { + to: Address; + abi: Abi; + functionName: string; + args: readonly unknown[]; + value?: bigint; + chainId: number | string; + data: `0x${string}`; // encoded calldata, ready to send + }; + meta?: { token?: Address; spender?: Address; amount?: bigint; reset?: boolean }; +} +``` + +A builder may prepend an `"approve"` step when an ERC-20 allowance is required, so always execute the whole array in order. + +```typescript +import { parseUnits } from "viem"; + +const supplySteps = await sdk.buildSupplyParams({ + chainId: 56, + marketId, + assets: parseUnits("100", 18), + walletAddress, +}); + +const borrowSteps = await sdk.buildBorrowParams({ + chainId: 56, + marketId, + assets: parseUnits("50", 18), + walletAddress, +}); + +// Execute every step with your own wallet/public client (e.g. viem) +for (const step of borrowSteps) { + const hash = await walletClient.writeContract(step.params); + await publicClient.waitForTransactionReceipt({ hash }); +} +``` + +### Builder methods + +| Method | Builds | +| --- | --- | +| `buildSupplyParams(params)` | Supply collateral to a market. | +| `buildBorrowParams(params)` | Borrow loan assets against collateral. | +| `buildRepayParams(params)` | Repay borrowed assets (by `assets`, `shares`, or `repayAll`). | +| `buildWithdrawParams(params)` | Withdraw collateral (by `assets` or `withdrawAll`). | +| `buildVaultDepositParams(params)` | Deposit into an ERC-4626 vault. | +| `buildVaultWithdrawParams(params)` | Withdraw from a vault (by `assets`, `shares`, or `withdrawAll`). | +| `buildSmartSupplyDexLpParams(params)` | Supply existing DEX LP as Smart Lending collateral. | +| `buildSmartSupplyCollateralParams(params)` | Supply token A/B (zap to LP) as Smart Lending collateral. | +| `buildSmartWithdrawDexLpParams(params)` | Withdraw Smart Lending collateral as LP. | +| `buildSmartWithdrawCollateralParams(params)` | Withdraw Smart Lending collateral as token A/B. | +| `buildSmartWithdrawCollateralFixedParams(params)` | Withdraw Smart Lending collateral burning a fixed LP amount. | +| `buildSmartRepayParams(params)` | Repay a Smart Lending market position. | +| `buildBrokerBorrowParams(params)` | Borrow through a broker (optionally a fixed `termId`). | +| `buildBrokerRepayParams(params)` | Repay a broker position. | + +Common builder inputs are `chainId`, `marketId` / `vaultAddress` / `brokerAddress`, `walletAddress`, and a raw `bigint` amount (`assets`, `shares`, `lpAmount`, etc.). Most builders also accept an optional `onBehalf`/`receiver` address and an optional pre-fetched config object to skip a round-trip. See each method's parameter type (`BuildSupplyParams`, `BuildBorrowParams`, …) for the full shape. + +--- + +## Simulation + +The SDK offers two simulation tiers, both pure (no transaction is sent). + +### On-chain-backed simulation (`MoolahSDK`) + +These fetch live market state and project the resulting position, so you can preview health/LTV before building a transaction. + +| Method | Description | +| --- | --- | +| `simulateBorrowPosition(params)` | Project a position after supplying and/or borrowing, using live chain data. | +| `simulateRepayPosition(params)` | Project a position after repaying and/or withdrawing, using live chain data. | + +```typescript +const preview = await sdk.simulateBorrowPosition({ + chainId: 56, + marketId, + walletAddress, + supplyAssets: parseUnits("1", 18), + borrowAssets: parseUnits("500", 18), +}); +// preview.simulation -> projected collateral, borrowed, LTV, loanable, liqPriceRate +``` + +### Pure simulation functions (`@lista-dao/moolah-sdk-core`) + +These take a `Decimal`-typed snapshot you supply (market state + user position) and return the projected outcome with no chain access at all — ideal for client-side what-if calculators. + +| Function | Description | +| --- | --- | +| `simulateMarketBorrow(params)` | Project a market position after a borrow. | +| `simulateMarketRepay(params)` | Project a market position after a repay. | +| `simulateVaultDeposit(params)` | Project vault locked/balance (and optional yearly/monthly earnings) after a deposit. | +| `simulateVaultWithdraw(params)` | Project vault locked/balance after a withdraw. | +| `simulateSmartMarketBorrow(params)` | Project a Smart Lending position after a borrow. | +| `simulateSmartMarketRepay(params)` | Project a Smart Lending position after a repay. | + +```typescript +import { Decimal, simulateMarketBorrow } from "@lista-dao/moolah-sdk-core"; + +const result = simulateMarketBorrow({ + supplyAmount: Decimal.parse("0.5", 18), + borrowAmount: Decimal.parse("500", 18), + userPosition: { + collateral: Decimal.parse("1", 18), + borrowed: Decimal.ZERO, + }, + marketState: { + totalSupply: Decimal.parse("10000", 18), + totalBorrow: Decimal.parse("5000", 18), + LLTV: Decimal.parse("0.8", 18), + priceRate: Decimal.parse("2000", 18), + loanDecimals: 18, + collateralDecimals: 18, + }, +}); +// result -> { collateral, borrowed, LTV, loanable, liqPriceRate, borrowRate? } +``` + +The core package also exposes interest-rate helpers — `getAnnualBorrowRate(rate)` (per-second → annual), `getBorrowRateInfo(params)`, and `getInterestRates(params)` (curve data) — for rendering rate information from the values you read on chain. + +--- + +## Decimal utility + +Human-scaled economic amounts are returned as `Decimal` (from `@lista-dao/moolah-sdk-core`) instead of `number` or `bigint`, to avoid JavaScript floating-point error. Raw on-chain integers (share amounts, timestamps, rate caps/floors, `MarketParams`) stay `bigint`. + +```typescript +import { Decimal, RoundingMode } from "@lista-dao/moolah-sdk-core"; + +// Create +const amount = Decimal.parse("123.456", 18); // from string (recommended) +const raw = new Decimal(123456000000000000000000n, 18); // from raw bigint + decimals +Decimal.ZERO; Decimal.ONE; + +// Arithmetic (BigNumber.js-style aliases also available) +amount.add(b); amount.sub(b); amount.mul(b); amount.div(b); + +// Rounding / precision +amount.dp(2, RoundingMode.FLOOR); +amount.floor(2); amount.ceiling(2); amount.round(2); amount.roundDown(2); + +// Compare +amount.eq(b); amount.gt(b); amount.lt(b); amount.isZero(); amount.isPositive(); + +// Format +amount.toString(4); // trims trailing zeros +amount.toFixed(8); // keeps trailing zeros +amount.toFormat(2); // thousand separators +``` + +When you need a raw `bigint` for a transaction amount, either take `.roundDown(decimals).numerator` from a `Decimal`, or use viem's `parseUnits` directly on user input. + +```typescript +import { parseUnits } from "viem"; +const rawValue = parseUnits("100.5", 18); // 100500000000000000000n +``` + +| Read type | Decimal fields (examples) | +| --- | --- | +| `MarketExtraInfo` | `LLTV`, `totalSupply`, `totalBorrow`, `borrowRate`, `priceRate`, `utilRate`, `minLoan` | +| `MarketUserData` | `collateral`, `borrowed`, `loanable`, `withdrawable`, `LTV`, `liqPriceRate` | +| `VaultInfo` | `totalAssets`, `totalSupply` | +| `VaultUserData` | `locked`, `shares`, `balance` | +| `SmartMarketUserData` | `collateral`, `lpTokenA`, `lpTokenB`, `borrowed`, `loanable` | + +--- + +## REST access: `MoolahApiClient` + +`MoolahApiClient` is a typed wrapper over the public Lista Moolah REST API (paths under `/api/moolah`). `MoolahSDK`'s API-backed read methods use it internally; you can also use it directly when you only need API data. + +```typescript +import { MoolahApiClient } from "@lista-dao/moolah-lending-sdk"; + +const api = new MoolahApiClient(); // defaults to the public production API + +const markets = await api.getMarketList({ chain: "bsc", page: 1, pageSize: 20 }); +const vaults = await api.getVaultList({ chain: "bsc", page: 1, pageSize: 20 }); +const market = await api.getMarketInfo(marketId, "bsc"); +``` + +| Config field | Notes | +| --- | --- | +| `baseUrl` | API base URL. Defaults to the public production API. | +| `fetch` | Custom `fetch` implementation (e.g. for SSR or a proxy). | + +The client unwraps the standard Lista API envelope, returning the `data` payload on success and throwing on a non-success code. For the full endpoint reference — paths, query parameters, and response schemas — see the [Moolah Lending API](services/lending-api/README.md) section, in particular [Market](services/lending-api/market.md) and [Vault](services/lending-api/vault.md). + +--- + +## End-to-end shape + +A typical integration is: pick a chain ID, **read** market/user state (chain or API), optionally **simulate** the resulting position, **build** the `StepParam[]` for the operation, then **execute** each step with your own wallet client. The SDK only ever reads and encodes — signing and broadcasting remain in your application. + +## See also + +- [Integration Patterns](lista-lending/integration-patterns.md) — provider and broker integration at the contract layer. +- [Smart Contract](lista-lending/smart-contract.md) — Moolah contract interface and address reference. +- [Moolah Lending API](services/lending-api/README.md) — the REST surface wrapped by `MoolahApiClient`. From 3a61bb4b796d793de8a029aea16d48386bd3fdc8 Mon Sep 17 00:00:00 2001 From: tyler-tsai <15355143+tyler-tsai@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:01:11 +0800 Subject: [PATCH 4/4] docs(for-developer): Overview product-map hub + SUMMARY navigation - overview.md: rewritten from the legacy Helio-era narrative into a product map + audience routing + networks - SUMMARY.md: navigation entries for all new/updated for-developer pages Co-Authored-By: Claude Opus 4.8 --- SUMMARY.md | 8 ++++++ for-developer/overview.md | 51 +++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index a08366f..e44efaf 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -99,6 +99,7 @@ * [Overview](for-developer/overview.md) * [Multi-Oracle](for-developer/multi-oracle.md) + * [Consuming Oracle Prices](for-developer/multi-oracle/consuming-prices.md) * [LISTA Governance](for-developer/lista-governance/README.md) * [Smart Contract](for-developer/lista-governance/smart-contract.md) * [Collateral Debt Position](for-developer/collateral-debt-position/README.md) @@ -117,6 +118,11 @@ * [Lista Lending](for-developer/lista-lending/README.md) * [Protocol Extensions](for-developer/lista-lending/protocol-extensions.md) * [Integration Patterns](for-developer/lista-lending/integration-patterns.md) + * [Moolah Lending SDK](for-developer/sdk.md) + * [Contract & Interface Reference](for-developer/lista-lending/contract-reference.md) + * [Events & Callbacks](for-developer/lista-lending/events-and-callbacks.md) + * [Interest Rate Model (IRM)](for-developer/lista-lending/irm.md) + * [Smart Lending & StableSwap](for-developer/lista-lending/stableswap-integration.md) * [Smart Contract](for-developer/lista-lending/smart-contract.md) * [BSC Core](for-developer/lista-lending/smart-contract-bsc-core.md) * [BSC Lending Brokers](for-developer/lista-lending/smart-contract-bsc-brokers.md) @@ -125,12 +131,14 @@ * [BSC Credit](for-developer/lista-lending/smart-contract-bsc-credit.md) * [Ethereum](for-developer/lista-lending/smart-contract-ethereum.md) * [V3 Dex](for-developer/dex/README.md) + * [Architecture & Mechanics](for-developer/dex/mechanics.md) * [Smart Contract](for-developer/dex/smart-contract.md) * [Lista Platform Services](for-developer/services/README.md) * [Position Data Maintenance](for-developer/services/position-data-maintenance.md) * [Liquidation Logic (Service)](for-developer/services/liquidation-logic.md) * [Subscription Module](for-developer/services/subscription-module.md) * [Moolah Lending API](for-developer/services/lending-api/README.md) + * [API Conventions](for-developer/services/lending-api/conventions.md) * [Overall](for-developer/services/lending-api/overall.md) * [Vault](for-developer/services/lending-api/vault.md) * [Market](for-developer/services/lending-api/market.md) diff --git a/for-developer/overview.md b/for-developer/overview.md index f2292fe..b09e1b8 100644 --- a/for-developer/overview.md +++ b/for-developer/overview.md @@ -2,33 +2,48 @@
-### Lista Mechanism: Earning Rewards through Strategic Asset Utilization +## Lista DAO for developers -In the Lista mechanism, users can strategically utilize their assets—such as BNB, ETH, slisBNB, wBETH, and BTCB—to earn various rewards. The process involves several key steps: +Lista DAO is a BNB Chain DeFi protocol spanning lending, liquid staking, a decentralized stablecoin, and a concentrated-liquidity DEX. The current developer-facing product set is anchored by **Lista Lending (Moolah)** — a Morpho-based isolated-market lending protocol — alongside **slisBNB** liquid staking, the **lisUSD** Collateral Debt Position, **Smart Lending & the V3 DEX**, **RWA** markets, **Credit Loans**, the **LISTA** governance token, **lisAster**, and the **slisBNBx** Launchpool certificate. This section is the entry point for integrators building on Lista, auditors reviewing the contracts, and developers learning how the pieces fit together. Each product below links to its developer README and its on-chain contract reference. -#### 1. **Deposit Assets into the Interaction (CDP) Module** +## Product map -* Users start by depositing their assets into the Interaction (CDP) module, where these assets serve as collateral. -* By using this collateral, users can borrow **LisUSD**. +| Product | What it is | Developer docs | Contracts | +| ------- | ---------- | -------------- | --------- | +| **Lista Lending (Moolah)** | Morpho-based isolated lending markets and curated vaults, extended with Lista controls (oracle routing, min-loan floor, role-based access). | [README](lista-lending/README.md) · [Integration Patterns](lista-lending/integration-patterns.md) | [Smart Contract](lista-lending/smart-contract.md) | +| **Liquid Staking (slisBNB)** | Stake BNB to mint the yield-bearing `slisBNB` liquid staking token, usable as collateral across Lista. | [README](liquid-staking-slisbnb/README.md) | [Smart Contract](liquid-staking-slisbnb/smart-contract.md) | +| **Collateral Debt Position (lisUSD)** | The legacy Helio-era CDP: deposit collateral into the Interaction module to borrow the `lisUSD` stablecoin. Now a secondary product alongside Lista Lending. | [README](collateral-debt-position/README.md) · [Mechanics](collateral-debt-position/mechanics.md) | [Smart Contract](collateral-debt-position/smart-contract.md) | +| **Smart Lending & V3 DEX** | Smart Lending routes lending collateral into DEX liquidity positions; the V3 DEX is a Uniswap V3-style concentrated-liquidity AMM (positions as NFTs, swaps via `SwapRouter`). | [V3 DEX README](dex/README.md) · [Smart Lending (concept)](../introduction/smart-lending.md) | [DEX Smart Contract](dex/smart-contract.md) · [Smart Lending contracts](lista-lending/smart-contract-bsc-smart-lending.md) | +| **RWA** | Tokenized access to U.S. short-term Treasury / bond strategies: subscribe with `USDT` and receive NAV-bearing pool shares via `RWAEarnPool`. | [README](rwa/README.md) | [Smart Contract](rwa/smart-contract.md) | +| **Credit Loans** | Fixed-term, fixed-rate `CreditBroker` lending gated by off-chain credit scoring represented on-chain via Merkle roots and a non-transferable `CreditToken`. | [README](credit-loans/README.md) · [Loan Lifecycle](credit-loans/loan-lifecycle.md) | [Smart Contract](credit-loans/smart-contract.md) | +| **Governance (LISTA)** | The `LISTA` token and governance contracts. Note: the veLISTA voting-escrow mechanism is being deprecated under LIP-024; governance voting has moved to plain LISTA on Snapshot. | [README](lista-governance/README.md) | [Smart Contract](lista-governance/smart-contract.md) | +| **lisAster** | ASTER staking aggregator: deposit ASTER to mint the transferable `lisAster` ERC-20, then stake it for epoch-based rewards. | [README](lisaster/README.md) | [Smart Contract](lisaster/smart-contract.md) | +| **slisBNBx** | Non-transferable collateral certificate (`SlisBNBxMinter`) that lets a Moolah collateral position also join Binance Launchpool. Formerly `clisBNB`. | [README](clisbnb/README.md) · [Delegation](clisbnb/delegation.md) | [Smart Contract](clisbnb/smart-contract.md) | -#### 2. **Borrow and Stake LisUSD** +Cross-cutting references: the [Multi-Oracle](multi-oracle.md) resilient price layer that backs lending and CDP, and [Lista Platform Services](services/README.md) for the off-chain APIs and data services. -* After borrowing LisUSD, users have the option to stake it in the **Jar Contract**. -* Both the staked amount and the borrowed LisUSD are tracked by the **ListaDistributor Contract** to calculate rewards. +## Find your path -#### 3. **Stake BNB for slisBNB and Earn Rewards** +### Integrators — building on Lista +- Start with [Lista Lending Integration Patterns](lista-lending/integration-patterns.md) for the Provider and Broker integration flows. +- Use the **Moolah Lending API** ([Overall](services/lending-api/overall.md) · [Vault](services/lending-api/vault.md) · [Market](services/lending-api/market.md) · [Position / Liquidation / Emission](services/lending-api/position-liquidation-emission.md)) for read-side market, vault, and position data. +- For typed read access and transaction-step building, use the [Moolah Lending SDK](sdk.md) (`@lista-dao/moolah-lending-sdk` / `@lista-dao/moolah-sdk-core`). -* Users can stake BNB through the **Stake Manager** to receive **slisBNB**. -* slisBNB not only earns bi-weekly **LISTA token rewards** but can also be used as collateral to borrow more LisUSD, which can yield additional rewards. +### Auditors / security researchers +- Each product's **Smart Contract** page (linked in the product map above) lists the on-chain addresses and contract roles. +- The [Multi-Oracle](multi-oracle.md) page documents the resilient price layer. +- Published audit reports are in [Security → Audit Reports](../security/audit-reports.md). -#### 4. **Direct BNB Rewards** +### General developers — learning the protocol +- Begin with the conceptual [Introduction](../README.md): [Lista Lending](../introduction/lista-lending/README.md), [Liquid Staking](../introduction/liquid-staking-slisbnb/README.md), [Smart Lending & Swap](../introduction/smart-lending.md), the [lisUSD CDP](../introduction/collateral-debt-position-lisusd/README.md), [RWA Markets](../introduction/rwa-markets.md), [Lista Credit](../introduction/lista-credit.md), and [lisAster](../introduction/lisaster.md). -* Users can also directly earn BNB rewards by staking BNB. +## Networks -#### 5. **Lock LISTA Tokens in veLista for Additional Rewards** +Lista contracts are deployed on: -* By locking LISTA tokens in the **veLista contract**, users are eligible to receive additional LISTA token rewards. +| Network | chainId | +| ------- | ------- | +| BNB Smart Chain | 56 | +| Ethereum | 1 | - - -
+The full per-product address sets (BSC and, where applicable, Ethereum) are on each product's Smart Contract page above.