# Gearbox Protocol developer documentation full export > Full text export for agents and retrieval systems. Use https://docs.gearbox.finance/developers/llms.txt for compact routing before loading this file. Source hierarchy and freshness rules: - Static documentation describes protocol mechanics, integration patterns, and operational procedures. - Mutable market facts such as APY, utilization, market availability, supported assets, supported chains, rates, addresses, or live incidents must be verified through Gearbox app, Gearbox Data, governance, security, deployment, or owner-reviewed sources before answering. - Legal, tax, investment, eligibility, suitability, compliance, and privacy questions require Terms, Privacy, Risks, or owner-reviewed materials. Documentation alone is not legal, financial, tax, or investment advice. - URLs shown in each section are canonical docs routes for this exported content. # Developer documentation SDK, contracts, Credit Accounts, multicalls, market data, integrations, deployment addresses, and automation guides. ## Overview Source: https://docs.gearbox.finance/developers/intro-overview File: content/developers/intro-overview.mdx Build on Gearbox Protocol — permissionless lending rails for onchain credit. Gearbox provides the infrastructure for creating and operating lending markets. A single liquidity pool funds multiple isolated credit strategies, each managed by independent Market Curators. Developers can integrate at every level: from reading market data and building UIs, to creating new adapters for DeFi protocols, to extending the core protocol itself. ## What You Can Build | Use Case | Description | Start Here | | --- | --- | --- | | **Frontend / Dashboard** | Display market data, account positions, pool stats | [Getting Started: TypeScript](https://docs.gearbox.finance/developers/gm-start-ts) | | **Trading Bot** | Monitor health factors, execute liquidations | [Liquidation Bots Guide](https://docs.gearbox.finance/developers/gm-guide-bots) | | **Analytics Backend** | Index events, track historical rates, build APIs | [Backend Services Guide](https://docs.gearbox.finance/developers/gm-guide-backend) | | **Protocol Integration** | Compose with Credit Accounts from your contracts | [Protocol Integration](https://docs.gearbox.finance/developers/gp-integration) | | **Adapter Development** | Add new DeFi protocol support to Gearbox | [Adapter Development](https://docs.gearbox.finance/developers/gp-adapters) | | **Core Extension** | Custom interest rate models, pool hooks | [Core Extension](https://docs.gearbox.finance/developers/gp-extension) | ## Documentation Structure ### [Gearbox Markets](https://docs.gearbox.finance/developers/gm-overview) The main product. Learn the concepts, get started with the TypeScript SDK, query market data, operate credit accounts, and build multicall transactions. - **Concepts** — How markets, credit accounts, and the multicall system work - **Getting Started** — Install the SDK and make your first call - **Markets** — Query pools, rates, and insurance state - **Credit Accounts** — Open, manage, and close leveraged positions - **Smart Contracts** — Contract reference (methods, events, parameters) - **Guides** — End-to-end tutorials for common use cases ### [Gearbox Permissionless](https://docs.gearbox.finance/developers/gp-overview) The infrastructure layer. Understand how the protocol achieves institutional-grade security while remaining fully permissionless. - **Concepts** — Role separation, Bytecode Repository (BCR), Cross-Chain Multisig (CCM) - **Building** — Create adapters, integrate protocols, extend the core ### [Resources](https://docs.gearbox.finance/developers/res-addresses) Deployment addresses, security & audits, glossary. ## Quick Links | Need | Go to | | --- | --- | | Install SDK | [Getting Started: TypeScript](https://docs.gearbox.finance/developers/gm-start-ts) | | Understand the architecture | [Gearbox 101](https://docs.gearbox.finance/developers/intro-101) | | Find contract addresses | [Deployment Addresses](https://docs.gearbox.finance/developers/res-addresses) | | Build multicalls | [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) | | Query credit accounts | [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) | | Pool deposit/withdraw | [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) | ## Risks and T&C Source: https://docs.gearbox.finance/developers/risks-and-t-c File: content/developers/risks-and-t-c.mdx ## Risks and T&C Source: https://docs.gearbox.finance/developers/risks-and-t-c-2 File: content/developers/risks-and-t-c-2.mdx ## Risks and T\&C It is important to distinguish between an interface (application) that is operated by a normal web2 company - and the onchain protocol where all the logic is and that is not controlled by anyone. While the company can block its interface and prohibit its use, the protocol is fully in the hands of governance and is not managed by any single person or entity. It's crucial for the DAO to find robust solutions and keep innovating, seeking to improve Gearbox security. You can find more information on such efforts in the [**Audits**](https://docs.gearbox.finance/core/audits-bug-bounty) section. Companies have their own legal disclaimers and risks associated, which you must go through and accept if you use the interface. **For instance, you must understand that an interface not being accessible can become an obstacle to you managing your position. But it doesn't mean that you cannot access your position or assets via smart contract directly or some other interface.** This section is dedicated to explaining risks related to Gearbox Protocol on the contract level and conceptually. **We believe that outlining risks explicitly creates stronger accountability in the community and gives power back to the users of the protocol.** The risks presented below are general across many DeFi protocols. Ask more on [Discord](https://discord.gg/JssNVvxscK). ### **Protocol Technical Disclosure** Some of the disclosures can mimic interface-related concerns below. **Oracle Risk** In its current stage, Gearbox Protocol uses Chainlink Price Feeds, Redstone Price Feeds as well as some composite smart contract oracles. While the industry in general uses the same service, it is imperative to understand that oracles are a crucial point of DeFi infrastructure. An oracle going rogue or reporting incorrect data, may cause cascading liquidations or incorrect protocol operations. This can result in bad debt and loss of user funds. **Sandwich attacks and MEV** No traders on Ethereum are safe from MEV attacks, and this is not something unique to Gearbox Protocol either. The DAO can find solutions and cooperate with protocols who provide MEV protection. Users should exercise trades with caution and watch out for their slippage. And for example, select better RPCs that protect you from being sandwhiched. **De-peg of base assets or stablecoin risks** It can happen that stablecoin issuers introduce boundaries as to who can use them, or freeze accounts. This can cause issues in the way protocols work, and Gearbox Protocol is no exception to that. In the ethos of decentralization, Gearbox DAO should strive to improve its collateral base to limit cases leading to possible bad debt accrual. However, the current version of Gearbox has isolated lending only, in which case the risk is contained within LP pools. **Liquidations and black-swan events** [Liquidations](https://docs.gearbox.finance/core/liquidation-dynamics) do not depend on the protocol or any member involved. Liquidators are third-party workers open to anyone. It can happen that liquidators malfunction or do not perform, despite [economic incentives for liquidators](https://docs.gearbox.finance/core/liquidation-dynamics) being in place. This can under-collateralize the protocol and make [LPs](https://docs.gearbox.finance/core/pool-the-liquidity-vault) lose their capital. To prevent that, DAO can implement token-backed backstops, increase[ Reserve Fund](https://docs.gearbox.finance/core/insurance-solvency-reserves), onboard more professional liquidators, and so on. **Risks of allowed tokens / contracts lists** [AllowedList](https://docs.gearbox.finance/core/risk-configuration-dictionary#credit-manager-level-rules) defines where Gearbox Protocol users can deploy their leverage funds into. Wrong liquidation thresholds or hacks on the side of those assets can result in the insolvency of LP pools. **Can't withdraw capital as LP due to the entire LP pool being in use** While the protocol can remain sane and over-collateralized, if the entire [LP](https://docs.gearbox.finance/core/pool-the-liquidity-vault) pool is borrowed, as an LP you would not be able to pull out your capital in that moment. This is the same scenario that Aave, Compound, or other protocols have in common and is pretty standard. Usually, in DeFi protocols forced liquidation is not implemented. This can lead to cases of insufficient liquidity for a withdrawal operation. To mitigate this risk, the parameters for managing the interest rate curve of the pool are introduced (interest rate depends on utilization ratio of the pool). If the utilization of the pool stays too high, the Governance can further increase these parameters and thereby make lending more expensive. This will encourage borrowers to close loans and thereby make liquidity available. **Issues with wallets and signing transactions** Wallet connection and signing transactions on the user side can be an obstacle to interacting with the protocol, which must be taken into account. **Hacks and Software Issues** Taking advantage of the protocol in ways it is not intended to be, malfunction of the protocol which wasn't originally intended, and other security breaches which disrupt design of the systems. All of these issues can lead to partial or full loss of funds, which every user must understand and accept. **Governance and Multisig** Before governance transitions to full on-chain voting and execution, the execution of snapshot decisions or even on-chain events is still subject to multisig doing its job the right way. There is a timelock in place to prevent rogue multisig transactions. Moreover, multisig needs to make sure it operates well or its misalignment with the DAO [governance](https://docs.gearbox.finance/core/protocol-dao) can lead to users leaving the protocol. This is a model often used, but the risk should be understood nonetheless. ### **Interface Disclosure** **Gearbox App is in its Beta Stage** Gearbox App is in its beta stage, which means that the Gearbox App and all related software, including blockchain software and smart-contracts, are experimental. Gearbox App is provided on an “as is” and “as available” basis, without warranty of any kind, either expressed or implied, including, without limitation, warranties that the Gearbox App or any related software are free of defects, vulnerabilities, merchantable, fit for a particular purpose or non-infringing. **Risk of Software Weaknesses** Although we make reasonable efforts to ensure that the Gearbox App and related software follow the high-security standards, we do not warrant or represent that the Gearbox App or any related software are secure or safe, or protected from fishing, malware or other malicious attacks. Further, the Gearbox App and related software may contain weaknesses, bugs, vulnerabilities, viruses or other defects which may have a material adverse effect on the operation thereof, or may lead to losses and damages for you, other users of Gearbox App, or third persons. **Risk Inherent in the Underlying Blockchain Networks** Gearbox App interacts with the Protocol deployed on the Ethereum Virtual Machine-compatible blockchain network(s) and more. As a result, any malfunction, breakdown or abandonment of such blockchain(s) may have a material adverse effect on the Gearbox App and Protocol. Moreover, advances in cryptography, or technical advances such as the development of quantum computing, could present risks to the Gearbox App and related blockchain software by rendering ineffective the cryptographic consensus mechanism that underpins the blockchain. The smart-contract concept, the underlying software and software platforms, including the blockchain networks, are still in an early development stage and unproven. Although it is very unlikely, the blockchain, as well as any other blockchain, can be attacked which may result in downtime, consensus split, long reorganization of the chain, 51 percent attack, or other adverse outcomes each of which may lead to complete loss of your assets implemented on such blockchain network. **Risk Inherent in the Protocol** Gearbox App interacts with the Protocol and when using the Gearbox App you may be exposed to certain risks related to the operation and functioning of the Protocol. The Protocol is complex, the majority of its components and functional elements are risky and experimental. You should not use or interact with the Protocol unless you fully understand how it works and the consequences of transactions carried out with the use of the Protocol. Gearbox App derives information from the Protocol, related software and underlying blockchain network(s) in an automated manner, which means that such information is not verified. As a result, such information may not be true, complete, timely, accurate, or sufficient. Furthermore, certain functions within the Protocol may be executed by third parties that may not act in a timely or reliable manner, or as expected or intended, or may fail to act, which can lead to partial or complete loss of your digital assets. That is related to untimely or imperfect [liquidations](https://docs.gearbox.finance/core/liquidation-dynamics). **Risk of Flawed Logic of Gearbox App** The underlying logic of the Gearbox App and related software may be flawed, defective or impaired, which can result in smart-contracts operating incorrectly or not as expected, or transactions being executed in violation of logic which underpins the smart-contracts, which can lead to partial or complete loss of digital assets used in the transaction. **Risk of Confusing User Interface** Certain user interface elements or design decisions of the Gearbox App can be confusing or mislead you, which may result in the execution of a different action or transaction than intended or desired, or connection of a wrong digital wallet, account or network. **Risk of Legal Uncertainty** Just like any other business, the intended activities of Gearbox may be subject to various laws and regulations in the countries where it operates or intends to operate. We might be obliged to obtain different licenses or other permissive documents in some or all jurisdictions where we intend to operate our business, therefore, our business in such jurisdictions shall always be subject to obtaining such licenses or permissive documents, if so directed by applicable laws. Furthermore, regulatory actions, orders or inquiries may adversely affect the Gearbox App and Gearbox, or impair our ability to make the Gearbox App available. Additionally, changes in applicable laws or regulations or evolving interpretations of existing law could, in certain circumstances, result in increased compliance costs or capital expenditures, which could affect our ability to carry on the business model and develop the Gearbox App and related software. **Risk of Theft** We make a commercially reasonable effort to ensure that any transactions carried out via the Gearbox App are secure. Notwithstanding the aforesaid, there is no assurance that there will be no theft of the digital assets as a result of hacks, sophisticated cyber-attacks, distributed denials of service or errors, double-spent attacks, flash-loan attacks, vulnerabilities or defects of the Gearbox App or related software, the Ethereum blockchain network or any other blockchain network, or otherwise. Such events may include, for example, flaws in programming or source code leading to exploitation or abuse thereof. Any of the above may lead to partial or complete theft or loss of digital assets used in transactions carried out through the Gearbox App or with the use of related software. ## The Lending Stack Source: https://docs.gearbox.finance/developers/intro-101 File: content/developers/intro-101.mdx Every product built on Gearbox — leveraged farming, margin trading, RWA settlement, structured credit — starts from the same primitive: the **Credit Account**. ## Credit Account: The Primitive A Credit Account is an isolated smart contract wallet that holds both a user's collateral and borrowed funds in one place. Unlike traditional lending protocols where collateral sits in a shared vault, each Gearbox position is a separate contract with its own state. ```mermaid flowchart LR subgraph "Traditional Lending" User1[User A] --> Vault[Shared Vault] User2[User B] --> Vault User3[User C] --> Vault end subgraph "Gearbox" UserA[User A] --> CA1[Credit Account A] UserB[User B] --> CA2[Credit Account B] UserC[User C] --> CA3[Credit Account C] end ``` This isolation is what makes everything else possible: - **Programmable leverage** — the Credit Account can interact with any whitelisted DeFi protocol (Uniswap, Curve, Lido, Aave) as if it were a regular wallet. Borrowed funds never leave the account — they're deployed *inside* it. - **Check-on-exit** — the protocol doesn't restrict what you do within a transaction. It only checks that the account is solvent at the end. This enables complex multi-step strategies (borrow → swap → farm → rebalance) in a single atomic transaction. - **Composability** — external protocols see the Credit Account as a standard EOA-like contract. No special integrations needed from their side. ## Leverage with Delayed Deposits The Credit Account primitive uniquely solves a problem that traditional lending protocols cannot: **leverage on assets with non-atomic settlement**. ### The Looping Problem Many assets — tokenized securities (ACRED), staked positions, RWA deposits — have settlement delays. A deposit of USDC into ACRED might take 2 days to mint. On traditional platforms (Morpho, Aave), building a 10x leveraged position with such a token requires **looping**: deposit collateral → wait 2 days → use the minted token as collateral → borrow more → deposit again → wait 2 more days → repeat. Getting to 10x leverage takes weeks. ### Phantom Tokens: Instant Leverage Gearbox solves this with **phantom tokens** — on-chain representations of pending positions that act as collateral *during* the settlement period. ```mermaid flowchart LR subgraph "Traditional (Looping)" T1["Deposit $100"] -->|"wait 2 days"| T2["Get Token"] T2 -->|"use as collateral"| T3["Borrow $85"] T3 -->|"deposit again"| T4["wait 2 days..."] T4 -->|"repeat 8x"| T5["~3 weeks for 10x"] end subgraph "Gearbox (Instant)" G1["Deposit $100 + Borrow $900"] -->|"same tx"| G2["Send $1000 to mint"] G2 --> G3["Receive phantom token"] G3 --> G4["10x leverage, Day 0"] end ``` How it works: 1. User opens a Credit Account, deposits \$100 collateral, borrows \$900 2. The Credit Account sends \$1,000 to the issuer's deposit contract 3. While the deposit is pending (2 days), the Credit Account receives a **phantom token** — a futures-like representation of the position 4. The phantom token is accepted as collateral by the Credit Manager (configured by the curator), keeping the account solvent 5. When the deposit matures, the phantom token is replaced by the real asset The user gets 10x leverage **on day zero**, not after weeks of looping. The curator configures the phantom token's liquidation threshold to reflect settlement risk. This is why the Credit Account is the right primitive for RWAs and delayed deposits — it holds the entire position (collateral + debt + pending assets) in one isolated contract, making the settlement state visible and manageable at the protocol level. ## The Lending Stack Capital flows through three layers: from liquidity source, through a Credit Manager, into Credit Accounts that interact with DeFi. ```mermaid flowchart LR subgraph "Liquidity Source" Markets["Gearbox Markets\n(Pooled)"] Intent["Intent-Based\n(P2P)"] end subgraph "Credit Manager" CM["Credit Manager\n(Policy Keeper)"] end subgraph "Credit Accounts" CA1["Credit Account"] CA2["Credit Account"] CA3["Credit Account"] end subgraph "DeFi Protocols" Uniswap["Uniswap"] Curve["Curve"] Lido["Lido"] Aave["Aave"] end Markets -->|"lend"| CM Intent -->|"lend"| CM CM --> CA1 CM --> CA2 CM --> CA3 CA1 -->|"via Adapters"| Uniswap CA2 -->|"via Adapters"| Curve CA2 -->|"via Adapters"| Lido CA3 -->|"via Adapters"| Aave ``` ### Layer 1: Liquidity Source Where the capital comes from. Two models, same Credit Account primitive underneath: - **[Gearbox Markets](https://docs.gearbox.finance/developers/gm-overview)** (pooled) — many lenders deposit into a shared Pool. Dynamic rates based on utilization. Quotas limit per-token exposure. - **Intent-Based** (P2P) — one lender matches one borrower directly. Fixed or reference-based rates (LIBOR + 2%). Custom terms per deal. ### Layer 2: Credit Manager The **policy keeper**. Each Credit Manager defines the rules: which collateral tokens are accepted, which DeFi protocols can be used, what leverage is allowed. The Credit Manager borrows from the liquidity source and allocates to Credit Accounts. ### Layer 3: Credit Accounts → DeFi The **execution layer**. Each Credit Account is an isolated contract holding collateral + borrowed funds. Through **Adapters**, it interacts with external DeFi protocols — swap on Uniswap, stake on Lido, deposit on Curve. **Multicalls** batch all operations into a single atomic transaction with a solvency check at the end. ## Zoom Into the Credit Account The Credit Account is the foundation of the entire stack. Everything — markets, P2P lending, leveraged farming, RWA settlement — is built by composing operations on top of this single primitive. Here's what makes it work. ### Multi-Collateral Solvency A Credit Account can hold **many tokens simultaneously** — WETH, USDC, stETH, CRV LP tokens, phantom tokens. The Credit Manager iterates over all of them to compute the account's value: For each token in the account: 1. Get the **price** from a dual oracle — `min(main feed, reserve feed)` — protection against manipulation 2. Apply the **Liquidation Threshold** — e.g., WETH at 90% means \$100 of ETH counts as \$90 3. Cap the contribution by the token's **Quota** — concentration limit per token 4. Sum into the **Total Weighted Value (TWV)** Then: ``` Health Factor = TWV / Total Debt where Total Debt = principal + accrued interest + quota fees ``` If **HF >= 1** → account is solvent. If **HF < 1** → account is liquidatable. ### Check-on-Exit: The Execution Model The Credit Account doesn't check solvency on every operation — only at the end of a multicall. This is the **check-on-exit** model: > **Example: building a leveraged stETH position** > > 1. `addCollateral` — deposit \$100 USDC > 2. `increaseDebt` — borrow \$900 USDC from Pool > 3. `swap` (via Uniswap adapter) — swap \$1,000 USDC → WETH > 4. `stake` (via Lido adapter) — stake WETH → stETH > 5. `deposit` (via Curve adapter) — deposit stETH into Curve pool > 6. **Final check: HF >= 1?** → Yes: TX succeeds. No: entire TX reverts. Between steps 2 and 5, the account is temporarily insolvent — it has \$900 debt but collateral is mid-transformation. This is fine because all operations are **atomic**: if the final check fails, everything reverts as if nothing happened. No bad debt can ever be created. ### The Building Block This is why every product on Gearbox reduces to the same primitive: | Product | What Happens Inside the Credit Account | | --- | --- | | Leveraged Farming | Borrow → swap → deposit into yield vault | | Margin Trading | Borrow → swap to long/short asset | | RWA Settlement | Borrow → deposit with issuer → hold phantom token | | Structured Credit | P2P terms → borrow → deploy into strategy | The Credit Manager defines *which* operations and tokens are allowed. The Credit Account executes them. The solvency check enforces safety. Everything else — pools, quotas, insurance, oracles — exists to support this core loop. ## Deep Dive - [**Credit Accounts**](https://docs.gearbox.finance/developers/gm-concept-accounts) — The Credit Suite architecture (Manager, Facade, Configurator), account lifecycle, and configuration parameters - [**Multicall System**](https://docs.gearbox.finance/developers/gm-concept-multicalls) — All available operations, the diff pattern, safety controls, and bot permissions - [**Health Factor & Risk**](https://docs.gearbox.finance/developers/gm-concept-risk) — TWV formula, Liquidation Thresholds, Quotas, Dual Oracle, Loss Policy, and liquidation mechanics ## What's Next - [**Gearbox Markets**](https://docs.gearbox.finance/developers/gm-overview) — The pooled lending product built on top of this stack - [**Start building**](https://docs.gearbox.finance/developers/gm-start-ts) — Install the SDK and make your first call ## Gearbox Protocol – Monad LP Opportunity Source: https://docs.gearbox.finance/developers/gearbox-protocol-monad-lp-opportunity File: content/developers/gearbox-protocol-monad-lp-opportunity.mdx ### Overview Gearbox is a composable leverage protocol enabling undercollateralized on-chain borrowing through Credit Accounts. Current focus: yield strategies via Midas-issued collateral. **Two ways to participate:** | | Lending | Leverage | | ------- | -------------------------------------------- | ------------------------------------------- | | APY | 6-9% | Up to 20% | | Role | Passive liquidity provider | Active carry trade | | Risk | Indirect exposure, protected by liquidations | Direct collateral exposure | | Lock-up | None | None (subject to Midas redemption schedule) | *** ### Lending Side (Passive) **How it works:** Deposit USDC into the Gearbox pool. Earn yield from borrowers who use Credit Accounts to execute carry trades, borrowing at pool rates to deploy into higher-yielding RWA collateral. Gearbox solvency guardrails protect against borrower default. **Deposit here:** [Gearbox USDC Pool](https://app.gearbox.finance/pools/143/0x6b343f7b797f1488aa48c49d540690f2b2c89751) *** ### Leverage Side (Active) **How it works:** Open a Credit Account, borrow USDC, and deploy into Midas collaterals. Capture the full carry trade spread with leverage. **Yield source:** Direct exposure to mEDGE yield minus borrow cost. Net APY can reach 20% at max leverage (\~7x). **Zero slippage execution:** Gearbox direct integration allows entry/exit without DEX slippage. Redemptions execute at NAV. → [How Direct Redemptions Work](https://docs.gearbox.finance/core/usecase-direct-redemptions) **Open position here:** [mEDGE Leverage Strategy](https://app.gearbox.finance/strategies/open/143/0x1c8ee940b654bfced403f2a44c1603d5be0f50fa) *** ### Risk Framework #### Collateral Exposure: mEDGE Both sides have exposure to mEDGE (Midas EDGE vault). Current composition: → [View mEDGE Holdings](https://midas.app/medge) #### What happens if mEDGE depegs? | Scenario | Lending Side | Leverage Side | | ----------------- | ------------------------------------------------------- | --------------------------- | | Depeg <13% | Protected: liquidations trigger, borrowers absorb loss. | Position may be liquidated. | | Orderly wind-down | Redemptions via direct integration at NAV | Exit at NAV, no slippage | #### Gearbox Solvency Guardrails * **LTV limits:** Credit Accounts enforce max leverage * **Liquidation threshold:** Positions liquidated before insolvency * **Price feeds:** Oracle-based with sanity checks * **Audits:** [Security repo](https://github.com/Gearbox-protocol/security/tree/main/audits) *** ### Summary | | Lending | Leverage | | --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Target LP | Passive yield seekers | Active DeFi users | | APY | 6-9% | Up to 20% | | Risk | Lower (liquidation buffer) | Higher (direct exposure) | | Effort | Deposit & forget | Manage position | | Deposit | [Pool](https://app.gearbox.finance/pools/143/0x6b343f7b797f1488aa48c49d540690f2b2c89751) | [Strategy](https://app.gearbox.finance/strategies/open/143/0x1c8ee940b654bfced403f2a44c1603d5be0f50fa) | *** ## Gearbox LP Demo Day – Blurb **Project:** Gearbox Protocol **Project Description:** Composable leverage protocol with Credit Accounts enabling undercollateralized on-chain borrowing. Two LP opportunities on RWA yield strategies: * **Lending side:** Passive yield from borrower demand (6-9% APY) * **Leverage side:** Active carry trade via Credit Accounts (up to 20% APY) **Max TVL capacity:** 50M USDC in mid-term (nearest month) until considerable yield dilution.\ Further expansion driven by new collaterals addition. **Yield APY:** 6-9% (lending) / up to 20% (leverage) **Source of yield:** Carry trade between Gearbox borrow rates and Midas RWA collateral (mEDGE). Lenders earn borrow interest + MON incentives; leverage users capture full carry. **Duration of deal:** No lock-up **Audit link:** **Your contact:** Telegram - @OxIlya ## Manual Deleveraging When UI Actions Are Unavailable Source: https://docs.gearbox.finance/developers/manual-deleveraging-when-ui-actions-are-unavailable File: content/developers/manual-deleveraging-when-ui-actions-are-unavailable.mdx If the **Close** or **Swap** action is unavailable or fails, you can exit your position manually by following the steps below. 1 ## Withdraw collateral Withdrawing collateral sends the token to your wallet, where you can swap it freely using external liquidity sources outside of Gearbox. Withdrawing collateral reduces your position’s **Health Factor**. A large withdrawal may push the position into liquidation risk. \ \ Always check the projected **Health Factor** before each withdrawal.
Withdraw collateral in the UI ![Figure](https://docs.gearbox.finance/assets/docs/core/manual-deleveraging-when-ui-actions-are-unavailable/01-withdraw-collateral.png)
If your current **Health Factor** does not allow for a safe withdrawal, repay part of your debt first using the Debt Token from your wallet (see **Step 3**). 2 ## Swap collateral into the Debt Token Swap the withdrawn collateral into the Debt Token using external liquidity sources. Recommended options include: * DEX aggregators * Official liquidity venues provided by the collateral issuer 3 ## Repay debt using Debt Token from your wallet Repay the debt using the **same token the debt is denominated in**, directly from your wallet. Check whether the updated **Health Factor** allows for further withdrawal.
Repay debt in the UI ![Figure](https://docs.gearbox.finance/assets/docs/core/manual-deleveraging-when-ui-actions-are-unavailable/02-repay-debt-using-debt-token-from-your-wallet.png)
4 ## Repeat Steps 1-3 until your Credit Account reaches the desired state or is fully unwound ## Seamless LP migration Source: https://docs.gearbox.finance/developers/seamless-lp-migration File: content/developers/seamless-lp-migration.mdx ### Why migrate (what you gain as an LP) **In short:** better incentives and better market coverage. As Gearbox transitions to the permissionless curation model, rewards and activity shift to the new pools. Old pools will stop receiving rewards and become non-competitive over time. New pools are where curators focus liquidity and opportunities. New Curator pools combine two reward streams: * baseline, time-weighted TVL incentives to bootstrap passive liquidity * performance-based rewards proportional to the Curator’s revenue share. In practice, the aggregate rewards allocated to the permissionless model are about 3x the legacy LM run-rate, with dilution tied more closely to real usage and protocol revenue (supporting buybacks). As a result, effective yield in new pools is expected to be higher than in legacy pools. *** ### Purpose This LP migration contract is designed to allow users migrate liquidity without monitoring the pools for available liquidity: * Designed for the case where immediate migration is not possible due to liquidity constraints. * Users can pre-sign their intention to migrate by granting allowance to the migration contract. * The migration will be executed by the instance owner multisig as soon as liquidity becomes available. In other words, this contract is a safe automation tool: * You lock in your intent to move from the old pool to the new one. * You don’t have to monitor liquidity or time the transaction yourself. * The migration will happen at the first opportunity. *** #### How it Works There are only two functions in the migration contract: 1. User function (allowance setup) * You give the migration contract allowance for your LP tokens in the old pool. * This does not move funds immediately — it only means: > “Whenever possible, please take my LP tokens from the old pool and put them into the new pool.” * After signing, you are done. 2. Instance owner function (execute migration) * Can only be called by the instance owner multisig (chain-specific). However, instance owner can't do anything except for migrating liquidity between the pools specified by user. * Once liquidity becomes available, they trigger the migration: * LP tokens are redeemed from the old pool. * Assets are deposited into the new pool on behalf of the user. * Both *old pool* and *new pool* are fixed parameters of the contract and cannot be changed. *** #### Safety of Allowance * **Immutable destination:** your funds can only move old pool → new pool. * **No arbitrary spending:** allowance is strictly limited to the old pool LP tokens. * **Controlled execution:** the migration logic is minimalistic and fixed in contract. Even if instance owner multisig goes malicious, its actions can't result in user losing money. *** #### Migration Lifecycle 1. User grants allowance * Approves their LP tokens to be used by migration contract. 2. Monitoring phase * Liquidity in old pools may be fully utilized (100%). * Gearbox contributors monitor until withdrawals are possible. 3. Execution phase * Instance owner calls the migration function at the first chance. * Funds are moved automatically on behalf of the user who granted the allowance. 4. Completion * Users now hold LP tokens in the new pool. * Yield continues seamlessly. *** ### Why now (governance & versions) Gearbox is moving from V3.0 to V3.1 to solidify the permissionless governance direction. Under permissionless curation, DAO-approved curators can configure markets and parameters, and the protocol can scale across networks without slow, centralized bottlenecks. **This migration is happening because:** * Maintaining two versions is technically **complex and fragments liquidity.**\ Supporting both legacy pools and new permissionless pools in parallel would complicate operations and potentially reduce yields for everyone. Consolidating liquidity in the new system is safer and more efficient. * Legacy pools had the **DAO as the sole curator - this is changing.**\ In the permissionless model, curators manage markets directly within clear, on-chain constraints. So legacy pools either need to be turned off or migrated to preserve TVL in the ecosystem. * It’s **better for LPs.**\ Rewards and curator attention move to the new pools. Old pools will become non-competitive over time. Specialized curators are expected to maintain attractive rates more consistently than DAO-only governance, because they operate faster and stay deeper in the market. *** #### Batching and timelines To reduce multisig overhead and make the best use of liquidity windows, we process migrations in batches. * **Positions up to $1,000,000.**\ Migrated in full within the nearest suitable batch. * **Positions above $1,000,000.**\ Moved in several chunks. This is normal and helps keep utilization healthy while your position transitions. * **Target batch size.**\ We aim to group requests into roughly $1,000,000 batches to execute efficiently when liquidity allows. * **FIFO queueing.**\ LP requests are processed in chronological order. This keeps the process transparent and predictable. * **Timing.**\ Your migration may wait while a batch fills. The maximum wait time will not exceed 14 days. #### FAQ * **Can the migration contract move my funds anywhere else?**\ No. It can only move LP from the specific old pool to the specific new pool you selected. * **Why would APY be higher in the new pools?**\ New pools stack baseline TVL incentives with performance-based rewards linked to real fee generation. The overall incentive budget for the permissionless phase is roughly 3x legacy LM, with dilution aligned to revenue (supports buybacks), so effective yields are expected to be superior versus legacy pools. * **What if utilization in the old pool is 100% for a while?**\ We keep your request in the FIFO queue and execute at the first safe window. Batching helps reduce delays. * **Do I keep earning yield?**\ Yes. After migration you hold LP in the new pool and continue earning according to its parameters and incentives. ## Credit Account migration Source: https://docs.gearbox.finance/developers/credit-account-migration File: content/developers/credit-account-migration.mdx ### Why migrate (what you gain as a Borrower) **In short:** incentives, better execution and more favorable rates. **Incentives:** * One-time fixed reward paid in GEAR. Distributed retroactively. **Better execution:** * Improved routing and support for new adapters increases capital efficiency and reduces costs. **Favorable rates:** * Vote-based collateral-specific rate discovery is depreciated in favor of curator-controlled rates. ### Requirements for allowing a migration into your Market 1. All the collaterals of old account should be allowed in a credit manager of target Market 2. The position can be only migrated as a whole, target Market should have appropriate debt limits and capacity. 3. If the underlying token is changed during migration, new market must support swaps from new underlying to old one.\ E.g. you can migrate rstETH from an old wstETH pool to a new WETH pool, but WETH -> wstETH swap must be allowed in the new pool. ### How it works 1. New credit account is opened in a target market 2. Amount of tokens enough to repay old account is borrowed from a new account 3. Newly-borrowed tokens are swapped into underlying tokens of old account 4. Old account's debt is repaid and its collateral is transferred to the new account ## Quota increase fee Source: https://docs.gearbox.finance/developers/quota-increase-fee File: content/developers/quota-increase-fee.mdx When user increases Quota (Credit Account specific max amount of debt that can be backed by particular collateral), charge a fixed % fee on the quota difference: works like a one-time fee charged on swaps by exchanges. Added to account's Debt. Distributed as a DAO & Curator fee on repayment according to the fee split rules. ## Key concepts & system overview Source: https://docs.gearbox.finance/developers/key-concepts-system-overview File: content/developers/key-concepts-system-overview.mdx ## Key concepts and system overview With permissionless architecture Gearbox has became even more composable, evolving into a techical stack that allows growing lending businesses, developing DeFi ecosystems and deploy lending markets on any chain by enyone having interest and capacity to do so. is the entrypoint for no-code deployment, curation and collaboration with Gearbox. #### What is permissionless? Anyone can deploy Market Configurator to create and manage Gearbox Markets without needing governance approval. #### What is Gearbox Market? Gearbox Market is a set of modular contracts allowing to facilitate lending, borrowing and productive usage of collaterals at rules set by Curator.\ Properties of a single market include but are not limited to Underlying Token, its Price Feed, Interest Rate Model, collateral-specific Limits and Additional Rates. #### What is Gearbox Instance? **Instance** = **Chain ID** activated by DAO for deployment + Chain-specific address of **DAO Treasury** + **Instance Owne**r multisig that helps configure chain-specific parameters but can't affect Markets configuration. #### What can curator change? The Curator can adjust all Market parameters, with a mandatory 24-hour timelock enforced at the smart-contract level for any changes. #### What is possible with permissionless curation? Each market consists of tens of contracts, including Pool, Oracle, IRM, Loss policy, Credit Managers and Adapters. Such modular architecture allows creating products with market-best flexibility and granular parametrization making Gearbox Protocol the premier platform for crafting sophisticated financial products that address specific market demands and drive long-term value creation. Below is a diagram of the contracts and parameters that a curator can configure, so you can get an idea of how detailed market configuration can be. ![Figure](https://docs.gearbox.finance/assets/docs/curators/key-concepts-system-overview/01-system.png) ## Curator's operations Source: https://docs.gearbox.finance/developers/curator-s-operations File: content/developers/curator-s-operations.mdx Morpho has pioneered the concept of curated lending markets in DeFi, but its approach differs significantly from Gearbox's model. Below is a clear comparison of how curators function in each protocol: #### Morpho: Active Capital Allocation In Morpho, curators are active capital allocators. They: * Distribute depositors' funds across various yield-generating markets. * Operate within markets defined by immutable parameters, such as Loan-to-Value (LTV) ratios and oracles. #### Gearbox: Risk Parameter Management In Gearbox, curators have a more limited role, focusing solely on risk management. They: * Set risk parameters for markets, such as LTV ratios or liquidation thresholds. * Have no authority to move or allocate depositors' funds, which remain under user or protocol control. | Action | Gearbox | Morpho | | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Add exposure to new collateral |
  1. Set collateral limit, LTV and oracle for a new token in Market
  2. Borrowers can now use pool's liquidity
|
  1. Add nonzero supply cap for existing market (LTV and oracle are pre-configured)
  2. Deposit vault's funds to the new market
  3. Borrowers can now use vault's liquidity
| | Modify collateral LTV or Price Feed |
  1. Set new price feed
  2. Start ramp of LTV to target value
  3. Feed & LTV for old and new borrowers are changed
|
  1. Deploy a market with needed LTV and oracle and set nonzero supply cap for it
  2. Feed & LTV are changed only for new borrowers
  3. Start withdrawing liquidity from old market & push borrowers out by raising rate
| | Increase/decrease collateral-specific borrow rate |
  1. Set a new collateral-specific rate in addition to IRM utilization rate
|
  1. Move vault's allocation in/out of the Market to move dynamic IRM
| | Enable 1-click leverage for a collateral |
  1. Allow the list of needed adapters in the Market
|
  1. Contact contango or another strategy provider to integrate your collateral
| ## Gearbox Markets Overview Source: https://docs.gearbox.finance/developers/gm-overview File: content/developers/gm-overview.mdx Gearbox Markets is a pooled lending model where multiple lenders deposit into a shared liquidity Pool, and borrowers open Credit Accounts to access leverage. It is one of two lending models built on the Gearbox [Credit Account primitive](https://docs.gearbox.finance/developers/intro-101) — the other being [Intent-Based (P2P) Lending](#alternative-intent-based-p2p-lending). ## The Wholesale Bank Model Think of a Gearbox Market as a wholesale bank: ```mermaid flowchart TD L1[Lender A] -->|"deposit"| Pool["Pool (ERC-4626)\nUSDC, WETH, etc."] L2[Lender B] -->|"deposit"| Pool L3[Lender C] -->|"deposit"| Pool Pool -->|"debt ceiling: 80M"| CM1["Credit Manager A\n(Blue Chip Strategy)"] Pool -->|"debt ceiling: 10M"| CM2["Credit Manager B\n(Emerging Assets)"] CM1 --> CA1[Credit Account] CM1 --> CA2[Credit Account] CM2 --> CA3[Credit Account] ``` 1. **Pool** — a passive ERC-4626 vault. Many lenders deposit into the same Pool and receive **Diesel Tokens** (dUSDC, dWETH) — yield-bearing receipts whose value appreciates as borrowers pay interest. 2. **Credit Managers** — act as policy keepers. Each Credit Manager defines a specific lending strategy: which collateral tokens are accepted, which DeFi protocols can be used, what leverage is allowed. A single Pool can fund multiple Credit Managers, each with an isolated **Debt Ceiling**. 3. **Credit Accounts** — isolated smart contract wallets where borrowers deposit collateral and deploy borrowed funds. All DeFi operations happen inside the Credit Account. The Credit Manager checks solvency after every operation. ## Risk Isolation Unlike monolithic lending protocols where bad debt in one asset drains the entire pool, Gearbox isolates risk via Debt Ceilings: - A Pool holds \$100M USDC - \$80M allocated to a Blue Chip strategy (low risk) - \$10M allocated to an Emerging Assets strategy (high risk) - If the Emerging Assets strategy fails, the loss is capped at \$10M — the remaining \$90M is mathematically isolated Lenders earn blended yield from all strategies but are protected from tail risk of any single one. ## Quotas: Per-Token Risk Pricing Quotas are unique to Gearbox Markets. They solve two problems at once: **Concentration limits** — a hard cap on how much of any single collateral token can back debt across the Pool. This prevents over-concentration in volatile assets. **Risk-priced interest** — each collateral token carries its own **Quota Rate** on top of the base Pool rate. Riskier tokens cost more to use as collateral. ``` Total Borrow APR = Base Rate (from Pool utilization) + Quota Rate (per collateral token) ``` For example, using WETH as collateral might add +1% to the borrow rate, while using a governance token adds +5%. ## Unified Yield for Lenders Lenders don't need to choose which strategy to back. The Diesel Token abstracts everything: - Deposit USDC into the Pool → receive dUSDC - Interest from all Credit Managers flows back to the Pool - dUSDC exchange rate appreciates over time - Withdraw anytime (subject to available liquidity) ## Market Curators Any entity — institution, DAO, individual — can permissionlessly deploy a Credit Manager and become a **Market Curator**. Curators manage *parameters*, not *funds*: - Collateral tokens and liquidation thresholds - Enabled DeFi adapters (Uniswap, Curve, Lido, etc.) - Fee structure and debt limits - Interest rate curve parameters The system is fully non-custodial. See [Gearbox Permissionless](https://docs.gearbox.finance/developers/gp-overview) for how this works. --- ## Alternative: Intent-Based (P2P) Lending Gearbox Markets uses pooled liquidity. But the Credit Account primitive also enables a fundamentally different model: **peer-to-peer lending** where a single lender and a single borrower agree on terms directly. | | Gearbox Markets (Pooled) | Intent-Based (P2P) | | --- | --- | --- | | **Liquidity** | Many lenders → shared Pool | One lender ↔ one borrower | | **Rates** | Dynamic (utilization-based) | Fixed or reference-based (LIBOR + 2%) | | **Risk** | Socialized across Pool LPs | Isolated per deal | | **Collateral** | Curator-defined allowlist | Custom per agreement | | **Best for** | DeFi-native strategies, retail | Institutional, RWA, bespoke terms | In both models, the borrower operates through a Credit Account — the same solvency checks, adapters, and liquidation mechanics apply. The difference is how the capital is sourced. --- ## What's in This Section - [**Concepts**](https://docs.gearbox.finance/developers/gm-concepts) — Markets, Credit Accounts, Multicall System, Health Factor & Risk - [**Getting Started**](https://docs.gearbox.finance/developers/gm-start) — Set up the TypeScript SDK - [**Markets**](https://docs.gearbox.finance/developers/gm-markets) — Query pools, rates, quotas, and insurance state - [**Credit Accounts**](https://docs.gearbox.finance/developers/gm-accounts) — Open, manage, and close positions - [**Smart Contracts**](https://docs.gearbox.finance/developers/gm-contracts) — Contract reference (methods, events, parameters) - [**Guides**](https://docs.gearbox.finance/developers/gm-guides) — Frontend, backend, and bot tutorials ## Concepts Source: https://docs.gearbox.finance/developers/gm-concepts File: content/developers/gm-concepts.mdx Concepts specific to the Gearbox Markets pooled lending model. For foundational concepts (Credit Accounts, Multicalls, Health Factor) see [The Lending Stack](https://docs.gearbox.finance/developers/intro-101). - [**Markets**](https://docs.gearbox.finance/developers/gm-concept-markets) — The Pool → Credit Manager → Credit Account architecture. How a single liquidity vault funds multiple strategies with isolated risk. Debt Ceilings and Diesel Tokens. Topics covered here but not in The Lending Stack: - **Interest Rate Model** — utilization-based rate curves specific to pooled lending - **Market Curators** — the permissionless operator role that configures risk parameters - **Quotas** — per-token concentration limits and risk-priced interest (exist only in Markets, not P2P) ## Tooling for curators Source: https://docs.gearbox.finance/developers/tooling-for-curators File: content/developers/tooling-for-curators.mdx ## Essential tooling for curators Gearbox goes beyond providing just a protocol by offering a complete ecosystem of tools tailored for curators. These tools enable: * **Market Configuration**: Safely set up and manage lending markets with intuitive interfaces. * **Transaction Integrity**: Ensure the accuracy and security of transactions before they are executed onchain. * **Pre-Deployment Testing**: Test changes in a controlled environment to validate configurations and prevent errors. * **Multi-Chain Support**: Operate seamlessly on almost any EVM-compatible blockchain. #### Curation Supply Chain The curation supply chain in Gearbox is supported by a set of specialized tools designed to streamline market deployment and transaction management while prioritizing security and transparency. **Permissionless Interface** * **URL**: * **Purpose**: Enables curators to create transaction batches for market deployment and configuration using human-readable tables. The interface generates transaction data, which is uploaded to IPFS, with the Content Identifier (CID) signed by the GIP creator to prevent phishing. * **Note**: This interface can't modify onchain state directly; it consists both of a frontend and backend maintained by Gearbox contributors. Therefore it shouldn't be perceived as a final source of truth for onchain state or actions. ![Figure](https://docs.gearbox.finance/assets/docs/curators/tooling-for-curators/01-curation-supply-chain.png) **Permissionless Safe** * **URL:** [**https://safe.gearbox.finance/**](https://safe.gearbox.finance/) * **Repository**: * **Purpose**: An open-source, IPFS-hosted version of the Safe Multisig UI designed to review and sign transactions securely in a human-readable format. It eliminates backend dependencies to mitigate risks like Bybit-type attacks and performs checks of IPFS CID signature to prevent phishing. * **Note**: The open-source nature and IPFS hosting ensure users can verify the code's integrity. ![Figure](https://docs.gearbox.finance/assets/docs/curators/tooling-for-curators/02-curation-supply-chain.png) **Anvil Fork-Based Simulations** * **Purpose**: A unique Gearbox service that allows curators to test market configurations and transaction changes on a fork of the blockchain before onchain execution. This ensures the correctness of state changes and supports testing of various Gearbox components, including liquidators, routers, and frontends. ![Figure](https://docs.gearbox.finance/assets/docs/curators/tooling-for-curators/03-curation-supply-chain.png) ## Markets Source: https://docs.gearbox.finance/developers/gm-concept-markets File: content/developers/gm-concept-markets.mdx A Gearbox Market is a pooled lending system: one Pool, multiple Credit Managers, many Credit Accounts. This page explains how these components connect. ## Architecture ```mermaid flowchart TD L1[Lender A] --> Pool L2[Lender B] --> Pool L3[Lender C] --> Pool Pool[Pool - ERC-4626] --> CM1[Credit Manager A] Pool --> CM2[Credit Manager B] CM1 --> CA1[Credit Account] CM1 --> CA2[Credit Account] CM1 --> CA3[Credit Account] CM2 --> CA4[Credit Account] ``` ## Pool The Pool is a passive ERC-4626 vault holding a single underlying asset (USDC, WETH, DAI). It does not lend directly to borrowers — it allocates capital to Credit Managers. **Diesel Tokens** — when lenders deposit, they receive dTokens (dUSDC, dWETH). These are yield-bearing receipt tokens: - Non-rebasing: value accrues through exchange rate increases - Represent a pro-rata share of all Pool assets - Yield comes from interest paid by *all* connected Credit Managers The lender's experience is simple: deposit → hold dToken → withdraw with yield. No need to pick strategies. ## Credit Manager The Credit Manager is the **policy keeper** of a specific lending strategy. It defines: | Parameter | What It Controls | | --- | --- | | **Collateral tokens** | Which tokens can be held in Credit Accounts | | **Liquidation Thresholds** | Per-token discount factors for solvency calculation | | **Adapters** | Which DeFi protocols Credit Accounts can interact with | | **Debt limits** | Min/max debt per account | | **Fees** | Liquidation premium, protocol fee | A single Pool typically has multiple Credit Managers — each representing a different risk profile configured by its Market Curator. ### Debt Ceiling Each Credit Manager has a **Debt Ceiling** — the maximum amount it can borrow from the Pool. This is how risk isolation works: - Credit Manager A (blue chips, LT 85-90%) gets \$80M ceiling - Credit Manager B (emerging tokens, LT 60-75%) gets \$10M ceiling - If B's strategy causes bad debt, the loss is capped at \$10M ## Credit Account The Credit Account is where borrowing happens. When a user opens a Credit Account under a specific Credit Manager: 1. User deposits collateral into the account 2. Credit Manager borrows from the Pool on the account's behalf 3. Borrowed funds are sent directly to the Credit Account 4. User deploys capital via [Multicalls](https://docs.gearbox.finance/developers/gm-concept-multicalls) (swap, farm, stake) 5. Credit Manager checks solvency after every operation The Credit Account is an isolated smart contract — its state (collateral, debt, enabled tokens) is separate from every other account. ## Quotas Quotas exist only in Gearbox Markets (not in P2P lending). They serve two purposes: ### Concentration Limits A hard cap on how much total debt across the Pool can be backed by a specific collateral token. Example: even if there's \$100M in the Pool, at most \$20M of debt can be backed by CRV tokens. This prevents a single volatile asset from dominating pool risk. ### Risk-Priced Interest Each collateral token carries a **Quota Rate** — an additional interest rate charged to borrowers who use that token. Riskier tokens cost more: | Collateral | Quota Rate | Meaning | | --- | --- | --- | | WETH | +0.5% | Low risk, low extra cost | | wstETH | +1% | Liquid staking, moderate | | CRV | +5% | Governance token, volatile | ``` Total Borrow APR = Base Rate + Quota Rate ``` The Base Rate is driven by Pool utilization (supply/demand). The Quota Rate is set by the Curator (or voted via Gauge/Tumbler) based on collateral risk. ## How It All Connects ```mermaid flowchart LR Lender -->|"deposit USDC"| Pool Pool -->|"issue dUSDC"| Lender Pool -->|"lend to CM"| CM[Credit Manager] CM -->|"borrow for account"| CA[Credit Account] Borrower -->|"multicall"| Facade[Credit Facade] Facade -->|"route"| CM CM -->|"check solvency"| Oracle[Price Oracle] Curator -->|"set params"| Config[Credit Configurator] Config -->|"update"| CM ``` - **Lenders** interact with the Pool only - **Borrowers** interact with the Credit Facade → Credit Manager → Credit Account - **Curators** interact with the Credit Configurator → Credit Manager - **Liquidators** interact with the Credit Facade to liquidate unhealthy accounts ## Learn More - [**Credit Accounts**](https://docs.gearbox.finance/developers/gm-concept-accounts) — The Credit Suite architecture in detail - [**Quotas & Interest Rates**](https://docs.gearbox.finance/developers/gm-markets-rates) — Querying rates programmatically - [**Pool Operations**](https://docs.gearbox.finance/developers/gm-markets-pools) — Deposit/withdraw via SDK - [**Gearbox Markets Overview**](https://docs.gearbox.finance/developers/gm-overview) — Including comparison with P2P lending ## Credit Accounts Source: https://docs.gearbox.finance/developers/gm-concept-accounts File: content/developers/gm-concept-accounts.mdx A Credit Account is the core primitive of Gearbox Protocol. It is an isolated smart contract that holds a borrower's collateral and borrowed funds, enabling leveraged interaction with DeFi protocols while maintaining strict solvency guarantees. ## The Credit Suite Each Credit Account belongs to a **Credit Suite** — a trio of tightly coupled contracts: ```mermaid flowchart TD User[User / Bot] Curator[Market Curator] subgraph "Credit Suite" Facade[Credit Facade] Manager[Credit Manager] Config[Credit Configurator] end subgraph "Infrastructure" Pool[Pool V3] Oracle[Price Oracle] Account[Credit Account] end User -->|"multicall"| Facade Curator -->|"configure"| Config Config -->|"updates"| Facade Config -->|"updates"| Manager Facade -->|"validates & routes"| Manager Manager -->|"borrows/repays"| Pool Manager -->|"deploys/manages"| Account Manager -->|"valuation"| Oracle ``` ### Credit Facade (Entry Point) The user-facing contract. Borrowers submit multicall transactions here. The Facade validates requests, routes operations to the Credit Manager, and enforces the final solvency check. ### Credit Manager (Accounting Engine) The core logic contract. It tracks debt, manages collateral, calculates health factors, and interacts with the Pool for borrowing/repayment. The Credit Manager maintains the registry of all Credit Accounts and their state. ### Credit Configurator (Admin Interface) The governance contract. Market Curators use it to manage risk parameters — collateral tokens, liquidation thresholds, adapters, fees, and debt limits. All impactful changes pass through timelocks. ## Account Lifecycle 1. **Open** — User deposits collateral and borrows from the Pool. A Credit Account contract is deployed (or reused from a pool of pre-deployed accounts). 2. **Operate** — User executes multicalls: swap collateral, farm yield, adjust leverage. All operations happen *inside* the Credit Account. The user never directly touches Pool liquidity. 3. **Monitor** — The account's Health Factor (collateral value vs. debt) is continuously assessable. If it drops below 1, the account becomes liquidatable. 4. **Close** — User repays all debt + interest + fees. Remaining collateral is returned to the user's wallet. The Credit Account is returned to the reuse pool. ## Key Properties **Isolation** — Each Credit Account is a separate contract. One account's operations or losses cannot affect another. **Check-on-Exit** — Any sequence of whitelisted operations is allowed within a multicall, but the account must remain solvent (HF >= 1) when the multicall ends. This enables complex strategies that may be temporarily insolvent mid-execution. **Non-Custodial** — Neither the protocol nor the Market Curator can access funds inside a Credit Account. Only the account owner (and authorized bots with explicit permissions) can operate on it. **Composable** — Through Adapters, Credit Accounts can interact with any whitelisted DeFi protocol (Uniswap, Curve, Lido, Aave, etc.) as if they were regular EOA wallets — but with solvency enforcement. ## Configuration Parameters Market Curators define the risk profile for all Credit Accounts in their suite: | Parameter | Description | | --- | --- | | **Liquidation Threshold (LT)** | Per-token discount factor for solvency calculation. LT of 85% means \$100 of ETH counts as \$85 toward collateralization. | | **Collateral Tokens** | Allowed tokens in Credit Accounts. Unlisted tokens are valued at zero. | | **Adapters** | Whitelisted DeFi protocol integrations (Uniswap, Curve, etc.). | | **Debt Limits** | Minimum and maximum debt per account, preventing dust or concentration risk. | | **Fees** | Liquidation fee (to protocol) and liquidation premium (to liquidator). | ## Learn More - **How operations are batched and executed** — [Multicall System](https://docs.gearbox.finance/developers/gm-concept-multicalls) - **How solvency is measured and enforced** — [Health Factor & Risk](https://docs.gearbox.finance/developers/gm-concept-risk) - **Working with Credit Accounts in code** — [Credit Accounts (SDK)](https://docs.gearbox.finance/developers/gm-accounts) ## Multicall System Source: https://docs.gearbox.finance/developers/gm-concept-multicalls File: content/developers/gm-concept-multicalls.mdx The multicall system is the transaction model for all Credit Account operations in Gearbox. It allows complex DeFi strategies — borrowing, swapping, farming, adjusting collateral — to execute atomically in a single transaction with a solvency check at the end. ## Why Multicalls? In traditional lending protocols, each operation (deposit collateral, borrow, swap) is a separate transaction. This creates problems for leveraged positions: - **Mid-execution insolvency** — after borrowing but before deploying capital, the account is technically undercollateralized - **Gas inefficiency** — multiple transactions mean multiple solvency checks - **Sandwich risk** — separate swap transactions are vulnerable to MEV Multicalls solve all three: batch everything into one transaction, check solvency once at the end. ## How It Works ```mermaid flowchart TD User[User / Bot] -->|"multicall(account, calls)"| Facade[Credit Facade] Facade -->|"1. Start"| Loop[Execution Loop] Loop --> Check{Target?} Check -->|"Facade"| Internal[Internal Operation] Check -->|"Adapter"| Adapter[Adapter Contract] Internal -->|"addCollateral, increaseDebt..."| Loop Adapter -->|"swap, deposit, stake..."| Protocol[DeFi Protocol] Protocol --> Loop Loop -->|"2. All calls done"| Security[Security Checks] Security --> HF{Health Factor >= 1?} HF -->|"Yes"| Success[Transaction Success] HF -->|"No"| Revert[Transaction Reverts] ``` 1. **Start** — The Facade begins the multicall and sets the Credit Account as "active" 2. **Execute** — Each call in the array runs sequentially. Calls target either the Facade (internal operations) or an Adapter (external DeFi protocol calls) 3. **Check** — After all calls complete, the Facade performs security checks: slippage verification, forbidden token checks, and a full collateral check (HF >= 1) 4. **Result** — If the account is solvent, the transaction succeeds. If not, the entire transaction reverts — nothing happened. ## Available Operations ### Protocol Operations | Operation | What It Does | | --- | --- | | `addCollateral` | Move tokens from wallet to Credit Account | | `increaseDebt` | Borrow underlying from Pool | | `decreaseDebt` | Repay debt to Pool | | `updateQuota` | Enable/adjust quota for a collateral token | | `withdrawCollateral` | Remove assets from account | ### Safety Controls | Operation | What It Does | | --- | --- | | `onDemandPriceUpdates` | Push fresh price data (must be first call) | | `storeExpectedBalances` | Record balances before swaps for slippage check | | `compareBalances` | Verify balances after swaps meet minimums | | `setFullCheckParams` | Optimize the collateral check with token hints | ### External Calls (via Adapters) Adapters are whitelisted wrappers around DeFi protocols. Each adapter restricts which functions a Credit Account can call and validates outcomes. Through adapters, a Credit Account can: - Swap tokens on Uniswap, Curve, or other DEXes - Deposit into yield vaults (Yearn, ERC-4626) - Stake tokens (Lido, Convex) - Provide liquidity (Curve, Balancer) ## The "Diff" Pattern Adapters implement "diff" functions (e.g., `swapDiff`, `depositDiff`) for handling unknown amounts. Instead of specifying exact input: - **Standard**: needs exact `amountIn` - **Diff**: calculates `amountIn = currentBalance - leftoverAmount` This is essential when the result of a previous operation is unknown — for example, swapping all received tokens from a prior withdrawal. ## Functions That Accept Multicalls All state-changing CreditFacade functions accept a multicall array: | Function | Purpose | | --- | --- | | `openCreditAccount` | Create a new account with initial operations | | `closeCreditAccount` | Close account, return remaining funds | | `multicall` | Execute operations on existing account | | `botMulticall` | Bot-initiated operations (requires permissions) | | `liquidateCreditAccount` | Liquidate unhealthy account | ## Best Practices 1. **Price updates first** — always put `onDemandPriceUpdates` at the start when using pull-based oracles (Pyth, Redstone) 2. **Slippage protection** — use `storeExpectedBalances` before swaps and `compareBalances` after 3. **Dust management** — use `type(uint256).max` in `withdrawCollateral` to empty balances completely 4. **Gas optimization** — use `setFullCheckParams` with token mask hints for accounts with many enabled tokens ## Learn More - **Building multicalls in TypeScript** — [Multicalls (SDK)](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - **Individual operation details** — [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral), [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt), [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) - **How solvency is enforced** — [Health Factor & Risk](https://docs.gearbox.finance/developers/gm-concept-risk) ## Verify & Simulate Source: https://docs.gearbox.finance/developers/verify-simulate File: content/developers/verify-simulate.mdx Before executing any transaction on the mainnet (which costs gas, time and operations), it's better to verify that configuration works as intended. Gearbox provides a **Simulation Service** (Fork Testing). The system spins up a temporary "Sandbox" copy of the blockchain, applies your changes, and runs a list of tests. ## Automated Safety Checks The simulation runs a suite of automated checks to ensure your parameters are safe and functional. You should review the output of these tests (typically provided in the GIP report or Interface). | Test Category | What it checks | Why it matters | |---|---|---| | Router Paths | Can the system find a path to swap your new collateral into the underlying asset? | If this fails, users won't be able to open leveraged positions in a single transactions. Atomic liquidations will also be harmed. | | Insolvency | Does Collateral Value * Liquidation Threshold cover the debt? | Ensures there are no errors that allow immediate bad debt. | | Optimistic Liquidation | Can a liquidator actually sell the collateral on a DEX to repay the debt profitably? | If liquidity is too low for the configured parameters, the system will flag it. | | State Diff | Does the simulated blockchain state match your intended configuration? | Verifies that the transaction data you generated actually does what is expected. | ## The "Staging" App (User Experience Test) Automated tests check the math, but they don't check the experience. The simulation service generates a temporary **Staging Frontend** connected to the Sandbox fork. **Action:** Open the Staging App link and act as a user. 1. **Connect Wallet:** Use a test wallet (the fork will impersonate your tokens). 2. **Open a Position:** Try to borrow funds using your new strategy. 3. **Execute a Trade:** Try to swap assets or deposit into a vault via the adapter. 4. **Close/Repay:** Ensure you can exit the position. **After the fork has been created and the tests have passed, you can open the App connected to the test blockchain state.** ![Figure](https://docs.gearbox.finance/assets/docs/curators/verify-simulate/01-the-staging-app-user-experience-test.png) #### Application test walkthrough []() ## Prepare the Main Interface Once the contracts are deployed, they exist on the blockchain, but the official Gearbox Interface (app.gearbox.finance) may not know the imporant data: token icon, collateral APY, the list of points earned by borrowers or suppliers. For the tokens and strategies to be supported by the app, ensure that frontend configuration has all the required data: [listing-a-new-asset-in-the-main-app](https://docs.gearbox.finance/curators/listing-a-new-asset-in-the-main-app "mention") ## Next Steps If the simulations pass and the UI looks correct, you are ready to execute the transactions onchain. ## Health Factor & Risk Source: https://docs.gearbox.finance/developers/gm-concept-risk File: content/developers/gm-concept-risk.mdx The Health Factor is the core solvency metric in Gearbox. It determines whether a Credit Account is healthy, at risk, or liquidatable. Understanding how it's calculated — and the mechanisms that manage risk — is essential for building on the protocol. ## Health Factor The Health Factor (HF) is the ratio of risk-adjusted collateral value to total debt: ``` Health Factor = Total Weighted Value (TWV) / Total Debt ``` - **HF >= 1** — account is solvent - **HF < 1** — account is liquidatable ### Total Weighted Value (TWV) TWV is not simply the market value of collateral. Each token's value is discounted by its **Liquidation Threshold (LT)** and capped by its **Quota**: ``` TWV = Sum( min(Quota_i, Balance_i * Price_i * LT_i) ) / Price_underlying ``` For example, if an account holds \$100 of ETH with an LT of 90%, the system values it at \$90 for solvency purposes. ### Total Debt ``` Total Debt = Principal + Base Interest + Quota Interest + Fees ``` - **Principal** — the amount borrowed from the Pool - **Base Interest** — accrued from the Pool's utilization-based interest rate - **Quota Interest** — additional rate for each collateral token based on its risk profile - **Fees** — quota increase fees and other protocol charges ## Liquidation Thresholds Each collateral token has a Liquidation Threshold (LT) — a percentage that determines how much of its value counts toward solvency: | Token | LT | Meaning | | --- | --- | --- | | WETH | 90% | \$100 of ETH counts as \$90 | | WBTC | 85% | \$100 of BTC counts as \$85 | | CRV | 72% | \$100 of CRV counts as \$72 | Higher LT = more leverage possible. Lower LT = more conservative, safer. LT changes use a **ramping mechanism** — they interpolate linearly over a minimum of 24 hours, preventing sudden liquidation cascades. ## Quota System Quotas are per-token exposure limits that cap how much of each collateral asset counts toward solvency across the system: 1. **Quota Limits** — hard caps on total debt backed by a specific collateral asset. Prevents over-concentration in a single token. 2. **Quota Rates** — additional interest charged based on collateral risk. A volatile governance token might carry +5% quota rate on top of the base rate. ``` Total APR = Base Rate (from Pool utilization) + Quota Rate (from collateral risk) ``` ## Dual Oracle System Every collateral asset has two independent price feeds: - **Main Feed** — used for solvency calculation and liquidation triggers. Often a fundamental/backing price (e.g., exchange rate from the token contract). - **Reserve Feed** — used for safety validation during user operations. Often a market price (e.g., Chainlink spot). The system uses the **safe price** — the minimum of both feeds — for operations like withdrawals and debt increases. This creates an automatic circuit breaker against price manipulation or oracle failures. ``` Safe Price = min(Main Price, Reserve Price) ``` ## Liquidations When HF drops below 1, anyone can liquidate the account: ### Partial Liquidation (Deleverage) - Triggered when HF is below target but account is still partially solvent - Sells only enough collateral to restore HF to a target level - Lower premium than full liquidation - Executed by the Deleverage Bot ### Full Liquidation - Triggered when HF < 1 significantly - Liquidator repays total debt and claims all collateral at a discount - Liquidator profit = collateral value × liquidation premium - gas cost ### Bad Debt If collateral value is less than total debt after liquidation: 1. Unclaimed protocol fees (Treasury LP shares) are burned to cover the shortfall 2. Any remaining loss is socialized across all Pool LPs via Diesel Token exchange rate reduction ## Loss Policy The Loss Policy prevents liquidation cascades during market crashes. Instead of immediately liquidating at distressed market prices: 1. Check if HF < 1 using market price 2. If bad debt would result, recheck using fundamental (aliased) price 3. Only liquidate if the account is fundamentally insolvent This protects against flash crash scenarios while still handling genuine insolvency. ## Learn More - **Monitoring health factor in code** — [Credit Accounts (SDK)](https://docs.gearbox.finance/developers/gm-accounts) - **Insurance mechanism** — [Insurance](https://docs.gearbox.finance/developers/gm-markets-insurance) - **Liquidation bot implementation** — [Liquidation Bots Guide](https://docs.gearbox.finance/developers/gm-guide-bots) ## Overview Source: https://docs.gearbox.finance/developers/sdk-guide File: content/developers/sdk-guide.mdx The Gearbox SDK provides typed access to protocol state and operations. It wraps contract calls, handles encoding, and provides cached market data through a clean TypeScript interface. ## Prerequisites - Node.js 18+ - Basic familiarity with [viem](https://viem.sh/) - Understanding of async/await patterns ## What the SDK Provides | Component | Purpose | | --- | --- | | `GearboxSDK` | Main entry point with `attach()` initialization | | `marketRegister` | Cached market data access | | `addressProvider` | Contract discovery wrapper | | Plugins | Extended functionality (AccountsPlugin, AdaptersPlugin) | | Services | Credit account operations and multicall helpers | ## Guide Structure 1. [**Setup**](https://docs.gearbox.finance/developers/sdk-setup) — Installation, SDK initialization, basic configuration 2. [**Reading Data**](https://docs.gearbox.finance/developers/reading-data) — Market queries, account state, pool data 3. [**Credit Accounts**](https://docs.gearbox.finance/developers/credit-accounts) — Account operations via services 4. [**Multicalls**](https://docs.gearbox.finance/developers/multicalls) — Building and executing multicalls ## When to Use the SDK | Use Case | Recommended | | --- | --- | | Frontend applications | Yes | | Analytics dashboards | Yes | | Liquidation bots | Yes (or direct compressor calls) | | Backend services | Yes | | On-chain contracts | No (use Solidity Guide) | The SDK handles ABI management, type conversions, and caching internally. For on-chain integrations or gas-optimized bot implementations, consider the [Solidity Guide](https://docs.gearbox.finance/developers/overview-2). ## Architecture Understanding For conceptual background on how Gearbox works: - [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite) — How Credit Managers, Facades, and Configurators work together - [Pool Architecture](https://docs.gearbox.finance/developers/pools) — Lending pools and ERC-4626 compliance - [Multicall System](https://docs.gearbox.finance/developers/multicall-system) — How multicalls execute and validate --- ## Detailed Guides ### Multicall Operations Complete reference for each multicall operation with TypeScript examples: | Operation | Description | | --- | --- | | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | Transfer tokens to credit account | | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | Borrow and repay | | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | Manage collateral quotas | | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | Remove tokens from account | | [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) | Protect against price movement | | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | Interact with DeFi protocols | | [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) | Manage active collateral | | [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) | On-demand oracle updates | | [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) | Optimize health checks | | [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) | Security cleanup | See [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) for building and executing multicalls. ### Use Case Guides Application-specific guides for common development scenarios: | Building | Guide | Focus | | --- | --- | --- | | Web UI / Dashboard | [Frontend Applications](https://docs.gearbox.finance/developers/frontend-applications) | Data display, real-time updates | | Analytics / Indexer | [Backend Services](https://docs.gearbox.finance/developers/backend-services) | Historical data, event indexing | | Liquidation Bot | [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) | Monitoring, Router execution | ## Price feeds' configuration Source: https://docs.gearbox.finance/developers/price-feeds-configuration File: content/developers/price-feeds-configuration.mdx The feeds configurations are reviewed at . It requires CID of txs file uploaded to IPFS. ![Figure](https://docs.gearbox.finance/assets/docs/curators/price-feeds-configuration/01-price-feeds-configuration.png) #### TL;DR (Actionable checklist) 1. Txs simulation must pass. Click on Simulate button next to each batch to check. 2. Check the displayed price in the multisig UI to adequatly match current market values 1. Review allowPriceFeed transactions. 2. Grab the token address and verify its price on a DEX aggregator () 3. If not tradable on aggregators, ask the proposer for the correct reference (e.g. Pendle UI for PTs, Curve UI for LP tokens, or the issuer’s app for derivatives/vaults) 4. Zero price feed (always returns $0) can be safely added to any token for compatibility. 3. Check staleness period of the feed 1. Pull feeds → 4 min staleness 2. Push feeds → Heartbeat + 15 min (Ethereum) / Heartbeat + 2 min (L2s & faster chains). 4. Check that the feed contract is verified 1. Confirm verification on the chain’s block explorer. 5. Check that feeds are adequately capped from above: 1. Stablecoin feeds are capped by $1.04 from above 2. PT feeds for dollar-pegged vaults are capped by $1 from above 6. If the feed is deployed from external factory, it should use no Pull feeds as underlying feeds of factory deployment. ⚠️ If any of these criteria aren’t met: don’t sign, ask in chat for clarification. ✅ For a setLimiter transactions it's enough to check that simulation passes. This action updates exchange rate bounds of LP price feeds, and its correctness is checked on a contract level. *** #### 1) Purpose & Scope These Terms & Conditions define the minimum due‑diligence and neutral‑gatekeeping standards for IO signers when **adding, configuring, or allowing** price feeds in the **Price Feed Store (PFS)** on any supported EVM chain. The sole goal is to ensure that, **at the moment of signing**, every configured feed **returns an adequate market price for the intended token, normalized to 8 decimals**, and satisfies staleness / quality constraints. > Scope explicitly excludes any market‑risk, business, or curation decisions. IO signers act only as neutral technical gatekeepers. *** #### 2) Authority, Membership & Neutrality (summary) * **Authority (PFS):** IO may add/remove feeds; set staleness period; attach/detach feeds to tokens; and run feed configuration calls required by integrated providers. * **Neutrality:** IO remains **business-neutral**. Decisions must be based **only on objective technical criteria** below. All valid, safe requests should be processed in a reasonable timeframe. * **Non‑interference with markets:** PFS changes **do not alter behavior of existing Markets by themselves** and **are not auto‑applied** to them. *** #### 3) Definitions & Expectations * **Price Feed Store (PFS):** Chain‑specific registry of tokens and feeds. A token can be used as collateral only after its token entry and at least one allowed feed are present. * **8‑decimal normalization:** All effective Gearbox price feeds **must return USD‑denominated prices with 8 decimals** (`1e8` scale). Signers should verify output scale when checking a feed. * **Staleness Period:** Maximum allowed time since last update before a feed is considered stale and reverts/invalidates. * **Adequate market price:** A price reasonably close to reputable sources at the time of signing. *** #### 4) Pre‑Signing Due‑Diligence (hard requirements) **Signers must complete all checks below before approving the transaction.** If any check fails, **do not sign.** **4.1 Contract & Deployment** 1. **Feed contract is verified** on a reputable explorer (Etherscan/chain explorer). 2. If the feed is not external, it must be deployed from Bytecode Repository. **4.2 Price Output & Decimals** 1. **Price sanity:** Read the feed (via explorer read panel, provider dashboard, or PFS UI). The value must be **within a reasonable range** of one or more of: 1. **CoinGecko** (or equivalent public index), 2. **Trusted DEX/aggregators** (Uniswap/Curve/Balancer; 1inch/Cow/Odos), 3. **Designated platforms** when public indexes are unavailable (e.g., **Pendle markets** for PTs; **Pyth Insights**; **Redstone App**; protocol UIs for ERC4626 vault exchange rate). 2. **Decimals:** Confirm that the effective price value is **normalized to 8 decimals**. **4.3 Staleness** 1. **Staleness period** must be reasonable for the source and chain: 1. **Pull‑type feeds (e.g., Pyth/Redstone pull):** *recommended* `240s` (4 min) unless documented otherwise. 2. **External Aggregator feeds (push/heartbeat):** heartbeat **+ 15 min** (slower chains, e.g. Ethereum) or **+ 2 min** (faster chains, e.g. L2s). **4.4 Asset‑Specific Parameters (when applicable)** 1. **Stablecoin‑to‑USD feeds:** The observed price should be **bounded from above at 1.04**. 2. **Pegged assets feeds (LST-to-ETH, LRT-to-BTC, cbBTC-to-BTC etc.):** The observed ratio should be **bounded from above at 1.04**. *** #### 5) Refusal Policy If **any** requirement in fails or is inconclusive, **do not sign any transactions**. Examples: * Contract not verified; * Output not 8‑decimals normalized; * Price materially diverges from reputable venues; * Staleness period unreasonable for the source/chain; * Asset‑specific parameters missing/incorrect. *** ### Asset classes Asset class is a pair of (token-specific features, price feed methodology) which defines the behavior of collateral in different market scenarios. The same token can have different risks for LPs/Borrowers if priced differently. All of the tokens can be borrow-only or used as collaterals. \ Consider cases of * Correlated debt/collateral pairs * Volatile debt/collateral pairs Describe the policy of setting reserve feeds and aliased loss policy. Take into account that pull feeds providers (pyth and especially redstone) can be less reliable than push Some of the used feeds can be provided by token issuer itself (for example Resolv PoR, midas feeds etc.) and have no strict update frequency (we've seen Resolv update PoR feeds 2 hours later than was initially stated) 1. Stablecoins/ synthetic dollars\ USDT, USDe, USDai, DAI, USDf etc. 2. ETH or BTC equivalents 1. stETH, tBTC etc 3. Yield-bearing vaults\ Stream.finance xUSD, Midas vaults, tETH, LRTs etc. 1. Priced using ERC4626 feeds 2. Priced using composite feeds (prices can be market-based, exchange rate or PoR) 4. Pendle PT tokens 1. TWAP-based pricing or deterministic feeds 5. Curve, Balancer LP tokens 1. For the tokens having rate oracle or erc4626 vault attached in Curve or Balancer pools, oracle price appreciation is automatically displayed in virtual\_price 6. Pendle LP tokens 1. TWAP-based pricing or deterministic feeds 7. Delayed withdrawal phantom tokens 1. Since delayed withdrawal tokens are not liquidatable, the most favorable setting is when the position's HF is high enough not to fall below 1 due to accrued debt while redemption is being processed.\ \ One of the ways to achieve it is to set reserve feed of withdrawal token to be lower than its Main feed by some percentage. This percentage will effectively enforce the minimal health factor for user to have to initiate delayed withdrawal.\ \ Reserve to main price discount of 2% will mean that user has to maintain HF above \~1.02 to initiate full withdrawal of his collateral. ## Getting Started Source: https://docs.gearbox.finance/developers/gm-start File: content/developers/gm-start.mdx Choose how you want to integrate with Gearbox Markets. ## Integration Paths | Path | Best For | Status | | --- | --- | --- | | [**TypeScript (SDK)**](https://docs.gearbox.finance/developers/gm-start-ts) | Frontend apps, bots, analytics dashboards, backend services | Available | | [**GraphQL**](https://docs.gearbox.finance/developers/gm-start-graphql) | Querying indexed data, dashboards, lightweight integrations | Coming soon | ## TypeScript SDK The Gearbox SDK provides typed access to protocol state and operations. It wraps contract calls, handles encoding, and provides cached market data through a clean TypeScript interface. | Component | Purpose | | --- | --- | | `GearboxSDK` | Main entry point with `attach()` initialization | | `marketRegister` | Cached market data access | | `addressProvider` | Contract discovery wrapper | | Plugins | Extended functionality (AccountsPlugin, AdaptersPlugin) | | Services | Credit account operations and multicall helpers | **Prerequisites:** Node.js 18+, basic familiarity with [viem](https://viem.sh/). [Get started with TypeScript](https://docs.gearbox.finance/developers/gm-start-ts) ## SDK Namespaces Source: https://docs.gearbox.finance/developers/gm-start-namespaces File: content/developers/gm-start-namespaces.mdx The SDK vNext organizes its API into purpose-built namespaces. Each namespace groups related methods so you can discover, analyze, and execute without navigating raw contract state. ## Namespace Reference | Namespace | Purpose | | --- | --- | | `sdk.chains` | Multi-chain configuration — list supported chains, resolve RPC endpoints, check chain health | | `sdk.opportunities` | Unified discovery surface — search across all opportunity types (pools, strategies, markets) in one call | | `sdk.pools` | Pool-specific queries — list pools, get detail, check utilization, interest rates, and available liquidity | | `sdk.strategies` | Strategy-specific queries — list strategies, get detail, inspect adapters and leverage ranges | | `sdk.markets` | X-interface style markets — unified market view combining supply and borrow sides, useful for P2P and intent flows | | `sdk.tokens` | Token profiles and market data — prices, metadata, risk parameters, and enrichment info | | `sdk.curators` | Curator profiles and reputation — who manages which Credit Managers, track record, parameters | | `sdk.history` | Metric history time series — APY over time, utilization, TVL, and other historical data points | | `sdk.events` | Parameter changes and governance — feed of configuration updates, rate changes, and governance actions | | `sdk.positions` | Position lifecycle — deposit, open, adjust, close, and monitor Credit Account positions | | `sdk.trades` | Quote and routing — get swap quotes, compare routes, preview trade outcomes | | `sdk.transactions` | Final execution — submit prepared transactions, track confirmation, handle MEV protection | ## Runtime Modes The SDK operates in one of two runtime modes depending on backend availability: **Core-Only** — works without a backend connection. Supports chain reads, transaction building, simulation, execution, and current position status. Limited for history, profiles, APY enrichment, and snapshots. **Enriched** — available when the Gearbox backend is configured and healthy. Adds opportunity surfaces, historical data, curator/token profiles, points and APY enrichment, and fast bootstrap via `startFromCache`. ```ts import { GearboxSDK } from "@gearbox-protocol/sdk/official"; // Enriched mode (default) — uses backend at api.gearbox.fi const sdk = new GearboxSDK(); // Core-only mode — no backend dependency const sdk = new GearboxSDK({ backend: false }); ``` ## Loading Modes Two loading strategies control how aggressively the SDK fetches state: **Lazy (default)** — no heavy state load on startup. Each query fetches only what it needs. Best for frontend screens, agent queries, and partial usage. **Full-State** — loads broad chain state upfront. Best for dense workflows, advanced analysis, and fast repeated navigation after the initial bootstrap. ```ts // Lazy loading (default) await sdk.loadState({ mode: "lazy" }); // Full-state loading await sdk.loadState({ mode: "full-state" }); ``` Both modes coexist — you can start lazy and switch to full-state later if your workflow demands it. ## Learn More - [Getting Started — TypeScript SDK](https://docs.gearbox.finance/developers/gm-start-ts) — installation and bootstrap - [Opportunities](https://docs.gearbox.finance/developers/gm-opportunities) — discover yield across all types - [Lender (Pools)](https://docs.gearbox.finance/developers/gm-lender) — pool queries and deposits - [Positions (Strategies)](https://docs.gearbox.finance/developers/gm-positions) — open and manage leveraged positions ## TypeScript Source: https://docs.gearbox.finance/developers/gm-start-ts File: content/developers/gm-start-ts.mdx Install and initialize the Gearbox SDK, then read your first market data. ## Installation ```bash npm install @gearbox-protocol/sdk viem ``` ## Initialize the SDK ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], // Empty = auto-discover all markets }); ``` | Parameter | Type | Description | | --- | --- | --- | | `client` | `PublicClient` | viem client instance | | `marketConfigurators` | `Address[]` | Filter to specific configurators, or `[]` for all | ## Read Market Data The SDK exposes markets through `marketRegister`: ```typescript // All markets const markets = sdk.marketRegister.markets; // Find by pool address const market = sdk.marketRegister.findByPool(poolAddress); // Find by credit manager const market = sdk.marketRegister.findByCreditManager(cmAddress); // Access market data const pool = market.pool; console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available liquidity: ${pool.availableLiquidity}`); ``` ## Read Pool State ```typescript const pool = market.pool; // Interest rates (RAY = 27 decimals) const RAY = 10n ** 27n; const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY}%`); console.log(`Borrow APR: ${borrowAPR}%`); // Share price console.log(`Diesel rate: ${pool.dieselRate}`); ``` ## Read Credit Manager Config ```typescript for (const cm of market.creditManagers) { console.log(`Credit Manager: ${cm.address}`); console.log(`Min debt: ${cm.minDebt}`); console.log(`Max debt: ${cm.maxDebt}`); // Allowed collateral tokens for (const token of cm.collateralTokens) { console.log(` ${token.symbol}: LT ${token.liquidationThreshold}`); } } ``` ## Use Plugins Plugins extend SDK functionality: ```typescript import { AccountsPlugin, AdaptersPlugin } from '@gearbox-protocol/sdk'; sdk.use(new AccountsPlugin()); sdk.use(new AdaptersPlugin()); // Query accounts const accounts = sdk.accounts.byCreditManager(cmAddress); // Query adapters const adapters = sdk.adapters.byProtocol('uniswap-v3'); ``` | Plugin | Purpose | | --- | --- | | `AccountsPlugin` | Credit account indexing and filtering | | `AdaptersPlugin` | Protocol adapter discovery and metadata | ## Address Provider The SDK wraps AddressProvider for contract discovery: ```typescript import { AP_MARKET_COMPRESSOR, VERSION_RANGE_310 } from '@gearbox-protocol/sdk'; const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` ## Real-Time Data via Compressors The SDK caches data on initialization. For live data, call compressors directly: ```typescript import { marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310 } from '@gearbox-protocol/sdk'; const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); const freshData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function main() { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const RAY = 10n ** 27n; for (const market of sdk.marketRegister.markets) { const pool = market.pool; const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; console.log(`\n=== ${pool.underlying.symbol} Market ===`); console.log(`Pool: ${pool.address}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Credit Managers: ${market.creditManagers.length}`); } // Create service for account operations const service = createCreditAccountService(sdk, 310); const usdcMarket = sdk.marketRegister.markets.find( m => m.pool.underlying.symbol === 'USDC' ); if (usdcMarket) { const accounts = await service.getCreditAccounts( { creditManager: usdcMarket.creditManagers[0].address }, sdk.currentBlock ); console.log(`\nFound ${accounts.length} USDC credit accounts`); } } main().catch(console.error); ``` ## Next Steps - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) — Query pools, rates, and collateral info - [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) — Open and manage leveraged positions - [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) — Build and execute operations ## GraphQL Source: https://docs.gearbox.finance/developers/gm-start-graphql File: content/developers/gm-start-graphql.mdx GraphQL integration for querying indexed Gearbox protocol data. > This guide is coming soon. The GraphQL API is currently under development. ## What to Expect The GraphQL API will provide: - Indexed market and pool data with historical snapshots - Credit account state queries with filtering and pagination - Event-based activity feeds (deposits, borrows, liquidations) - Real-time subscriptions for position monitoring ## In the Meantime Use the [TypeScript SDK](https://docs.gearbox.finance/developers/gm-start-ts) for all integrations. It provides: - Cached market data via `marketRegister` - Real-time queries via [Compressors](https://docs.gearbox.finance/developers/gm-ref-compressors) (on-chain data aggregation contracts) - Credit account indexing via `AccountsPlugin` ## Opportunities Source: https://docs.gearbox.finance/developers/gm-opportunities File: content/developers/gm-opportunities.mdx An **Opportunity** is any way to earn yield in Gearbox, presented at the highest level of abstraction. It is the entry point for discovery — before you decide whether to lend or leverage, you search opportunities. ## Unified Discovery `sdk.opportunities.search()` returns a unified list of all opportunity types across all chains and pools. This single call replaces the need to separately query pools, strategies, and markets. ```ts // Search all opportunities const all = await sdk.opportunities.search(); // Filter by criteria const highYield = await sdk.opportunities.search({ minApy: 5, chainId: 1, assetClass: "stablecoin", }); ``` ## Opportunity Types There are two opportunity types available today, with two more planned: ### Available Now | Type | Description | User Role | | --- | --- | --- | | **Lender Opportunity** (`pool`) | Provide liquidity to a Pool, receive Diesel Tokens, earn passive yield | Lender / LP | | **Strategy Opportunity** (`strategy`) | Borrow from a Pool, deploy capital via a Credit Account with leverage | Borrower / Strategist | ### Future Types (Not Yet Implemented) | Type | Description | User Role | | --- | --- | --- | | **Intent Opportunity** (`intent`) | Limit-order style LP with custom terms (P2P supply side) | Lender | | **Intent Strategy** (`intent_strategy`) | P2P leveraged position with bespoke terms | Borrower | ## Opportunity Fields Every opportunity shares a common shape regardless of type: ```ts interface Opportunity { id: string; // Unique identifier chainId: number; // Chain where this opportunity exists type: OpportunityKind; // "pool" | "strategy" | "market" headlineApy: number; // Primary APY figure (supply APY or net strategy APY) depositToken: string; // Token you deposit to enter capacity: bigint; // Available liquidity / remaining capacity access: string; // Access control level curator?: string; // Curator managing this opportunity riskScore?: number; // Backend-enriched risk assessment } ``` Strategy opportunities add fields like `targetToken`, `leverage`, and `adapters`. Pool opportunities add fields like `utilizationRate` and `dieselToken`. ## Learn More - [Lender Opportunities](https://docs.gearbox.finance/developers/gm-opps-lender) — passive yield via Pool deposits - [Strategy Opportunities](https://docs.gearbox.finance/developers/gm-opps-strategy) — leveraged yield via Credit Accounts - [SDK Namespaces](https://docs.gearbox.finance/developers/gm-start-namespaces) — full namespace reference ## Lender Opportunities Source: https://docs.gearbox.finance/developers/gm-opps-lender File: content/developers/gm-opps-lender.mdx Lender opportunities represent the supply side of Gearbox — you deposit tokens into a Pool, receive yield-bearing Diesel Tokens in return, and earn passive yield as borrowers pay interest. ## What They Represent When you enter a lender opportunity, you are depositing into an ERC-4626 Pool. The Pool issues Diesel Tokens (e.g., dUSDC, dWETH) whose exchange rate appreciates over time as interest accrues from borrowers. No active management is required. ## Sub-Types | Sub-Type | Description | Status | | --- | --- | --- | | `pool` | Standard LP in an ERC-4626 Pool — deposit, earn yield, withdraw anytime | Available | | `intent` | Limit-order style LP with custom terms (rate, duration, collateral preferences) | Future | ## Discovering Lender Opportunities Use either the opportunities namespace for unified search or the pools namespace for direct queries: ```ts // Via unified search const lenderOpps = await sdk.opportunities.search({ type: "pool" }); // Via pools namespace directly const pools = await sdk.pools.list(); ``` ## Key Fields | Field | Description | | --- | --- | | `depositToken` | The token you deposit (USDC, WETH, etc.) | | `headlineApy` | Current supply APY — what lenders earn | | `capacity` | Available liquidity remaining in the Pool | | `access` | Access control — permissionless or gated | | `utilizationRate` | Current Pool utilization (higher = more yield, less exit liquidity) | | `dieselToken` | The yield-bearing receipt token you receive | ## Filtering You can filter lender opportunities by asset class, chain, and minimum APY: ```ts // All USDC lender opportunities with APY > 3% const usdcOpps = await sdk.opportunities.search({ type: "pool", depositToken: "USDC", minApy: 3, }); // Stablecoin opportunities on Arbitrum const arbStables = await sdk.opportunities.search({ type: "pool", chainId: 42161, assetClass: "stablecoin", }); ``` ## Example: List High-Yield USDC Pools ```ts import { GearboxSDK } from "@gearbox-protocol/sdk/official"; const sdk = new GearboxSDK(); await sdk.loadState({ mode: "lazy" }); const opps = await sdk.opportunities.search({ type: "pool", depositToken: "USDC", minApy: 3, }); for (const opp of opps) { console.log( `${opp.id} | APY: ${opp.headlineApy.toFixed(2)}% | Capacity: ${opp.capacity}` ); } ``` ## Next Steps - [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) — how to actually deposit and withdraw - [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) — how supply APY is calculated - [Strategy Opportunities](https://docs.gearbox.finance/developers/gm-opps-strategy) — the borrower side ## Overview Source: https://docs.gearbox.finance/developers/overview File: content/developers/overview.mdx Build on Gearbox Protocol. Choose your path based on how you'll integrate. ## Choose Your Path ### [SDK Guide (TypeScript)](https://docs.gearbox.finance/developers/sdk-guide) **Best for:** Frontend developers, bot builders, analytics dashboards The SDK provides typed access to Gearbox protocol state and operations. It wraps contract calls, handles encoding, and provides cached market data. * Read market state via `marketRegister` * Query credit accounts via services * Build multicalls with helpers * No Solidity knowledge required ### [Solidity Guide](https://docs.gearbox.finance/developers/overview-2) **Best for:** Smart contract developers, on-chain integrators, adapter builders Integrate directly with Gearbox contracts from your Solidity code. * Discover contracts via AddressProvider * Call CreditFacade operations * Build multicalls in Solidity * Build custom adapters ## Reference * [Concepts](https://github.com/de-snake/docs-knowledge/blob/new-docs-dev-1/concepts/README.md) - Architecture explanations (no code) * [Compressors](https://docs.gearbox.finance/developers/compressors) - Data aggregation contracts * [Automated Insurance](https://docs.gearbox.finance/developers/automated-insurance) - Protocol safety mechanisms * [Interest Rate Model](https://docs.gearbox.finance/core/interest-rate-model) - Utilization curves and rates * [Quota Keeper](https://docs.gearbox.finance/developers/quota-keeper) - Collateral exposure limits ## Quick Links | Need | Go to | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Install SDK | [SDK Setup](https://docs.gearbox.finance/developers/sdk-setup) | | Find contract addresses | [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) | | Understand Credit Suite | [Credit Suite Concepts](https://docs.gearbox.finance/developers/credit-suite) | | Build multicalls | [SDK Multicalls](https://docs.gearbox.finance/developers/multicalls) or [Solidity Multicalls](https://docs.gearbox.finance/developers/multicalls) | | Query credit accounts | [SDK Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) | | Pool deposit/withdraw | [Pool Operations](https://docs.gearbox.finance/developers/pool-operations) | ## Strategy Opportunities Source: https://docs.gearbox.finance/developers/gm-opps-strategy File: content/developers/gm-opps-strategy.mdx Strategy opportunities represent the borrower side of Gearbox — you borrow from a Pool, deploy capital into DeFi protocols via a Credit Account, and earn leveraged yield minus borrow costs. ## What They Represent When you enter a strategy opportunity, you open a Credit Account, deposit collateral, borrow pool liquidity, and deploy the combined capital through whitelisted DeFi adapters (Uniswap, Curve, Lido, Aave, etc.). The Credit Manager enforces solvency after every operation. ## Sub-Types | Sub-Type | Description | Status | | --- | --- | --- | | `strategy` | Leveraged position via Credit Account + adapters | Available | | `intent_strategy` | P2P leveraged position with custom borrow terms | Future | ## Discovering Strategy Opportunities Use either the opportunities namespace for unified search or the strategies namespace for direct queries: ```ts // Via unified search const strategyOpps = await sdk.opportunities.search({ type: "strategy" }); // Via strategies namespace directly const strategies = await sdk.strategies.list(); ``` ## Key Fields | Field | Description | | --- | --- | | `depositToken` | The collateral token you deposit (ETH, USDC, etc.) | | `targetToken` | The token the strategy targets for yield (stETH, yvUSDC, etc.) | | `headlineApy` | Net yield after borrow costs — what the borrower actually earns | | `leverageRange` | Min and max leverage available (e.g., 2x–10x) | | `adapters` | DeFi protocols available within this strategy | | `borrowRate` | Current cost of borrowing from the underlying Pool | | `capacity` | Remaining borrowable liquidity | ## Filtering You can filter strategy opportunities by asset class, chain, leverage, and minimum APY: ```ts // All ETH strategies with APY > 10% const ethStrats = await sdk.opportunities.search({ type: "strategy", depositToken: "ETH", minApy: 10, }); // High-leverage strategies on mainnet const leveraged = await sdk.opportunities.search({ type: "strategy", chainId: 1, minLeverage: 5, }); ``` ## Example: List High-Yield ETH Strategies ```ts import { GearboxSDK } from "@gearbox-protocol/sdk/official"; const sdk = new GearboxSDK(); await sdk.loadState({ mode: "lazy" }); const opps = await sdk.opportunities.search({ type: "strategy", depositToken: "ETH", minApy: 10, }); for (const opp of opps) { console.log( `${opp.id} | APY: ${opp.headlineApy.toFixed(2)}% | Leverage: ${opp.leverageRange.max}x` ); } ``` ## Next Steps - [Positions (Strategies)](https://docs.gearbox.finance/developers/gm-positions) — how to open, adjust, and close positions - [Multicall System](https://docs.gearbox.finance/developers/gm-accounts-multicalls) — composable operations within Credit Accounts - [Lender Opportunities](https://docs.gearbox.finance/developers/gm-opps-lender) — the supply side ## SDK Setup Source: https://docs.gearbox.finance/developers/sdk-setup File: content/developers/sdk-setup.mdx Install and initialize the Gearbox SDK for TypeScript development. > For Solidity contract discovery, see [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts). ## Installation ```typescript npm install @gearbox-protocol/sdk viem ``` ## SDK Initialization ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], // Empty array = auto-discover all markets }); ``` **Parameters:** | Parameter | Type | Description | | --------------------- | -------------- | ------------------------------------------------- | | `client` | `PublicClient` | viem client instance | | `marketConfigurators` | `Address[]` | Filter to specific configurators, or `[]` for all | ## Accessing Markets The SDK exposes markets through `marketRegister`: ```typescript // All markets const markets = sdk.marketRegister.markets; // Find by pool address const market = sdk.marketRegister.findByPool(poolAddress); // Find by credit manager const market = sdk.marketRegister.findByCreditManager(cmAddress); // Market provides typed access const pool = market.pool; // Pool state const creditManagers = market.creditManagers; // Array of CMs const priceOracle = market.priceOracle; ``` ## Using Plugins Plugins extend SDK functionality for specific use cases: ```typescript import { AccountsPlugin, AdaptersPlugin } from '@gearbox-protocol/sdk'; // Attach plugins sdk.use(new AccountsPlugin()); sdk.use(new AdaptersPlugin()); // After loading, access plugin data const accounts = sdk.accounts.byCreditManager(cmAddress); const adapters = sdk.adapters.byProtocol('uniswap-v3'); ``` **Available Plugins:** | Plugin | Purpose | | ---------------- | --------------------------------------- | | `AccountsPlugin` | Credit account indexing and filtering | | `AdaptersPlugin` | Protocol adapter discovery and metadata | ## Address Provider via SDK The SDK wraps AddressProvider for contract discovery: ```typescript import { AP_MARKET_COMPRESSOR, VERSION_RANGE_310 } from '@gearbox-protocol/sdk'; // Get latest compressor address const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // List all registered contracts const contracts = sdk.addressProvider.list(); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function main() { const client = createPublicClient({ chain: mainnet, transport: http(), }); // Initialize SDK const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); // Find USDC market const usdcMarket = sdk.marketRegister.markets.find( m => m.pool.underlying.symbol === 'USDC' ); if (!usdcMarket) throw new Error('USDC market not found'); // Get credit managers for this market const creditManagers = usdcMarket.creditManagers; console.log(`Found ${creditManagers.length} credit managers`); // Create service for account operations const service = createCreditAccountService(sdk, 310); // Query accounts const accounts = await service.getCreditAccounts( { creditManager: creditManagers[0].address }, sdk.currentBlock ); console.log(`Found ${accounts.length} credit accounts`); } main().catch(console.error); ``` ## Next Steps * [Reading Data](https://docs.gearbox.finance/developers/reading-data) - Query market state and pool data * [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) - Account operations via services For architectural background, see [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite). ## Reading Data Source: https://docs.gearbox.finance/developers/reading-data File: content/developers/reading-data.mdx Query market state, pool data, and credit account information using the SDK. > For Solidity pool operations, see [Pool Operations](https://docs.gearbox.finance/developers/pool-operations). ## Market Data via marketRegister The `marketRegister` provides cached access to all Gearbox markets: ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // All markets const markets = sdk.marketRegister.markets; // Find specific market const market = sdk.marketRegister.findByPool(poolAddress); // or const market = sdk.marketRegister.findByCreditManager(cmAddress); // Access market components console.log(`Pool: ${market.pool.address}`); console.log(`Available liquidity: ${market.pool.availableLiquidity}`); console.log(`Diesel rate: ${market.pool.dieselRate}`); console.log(`Supply rate: ${market.pool.supplyRate}`); console.log(`Credit managers: ${market.creditManagers.length}`); ``` ## Pool State Access pool data through the market object: ```typescript const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; // Core metrics console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available liquidity: ${pool.availableLiquidity}`); // Interest rates (RAY = 27 decimals) const RAY = 10n ** 27n; const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY}%`); console.log(`Borrow APR: ${borrowAPR}%`); // Share price const dieselRate = pool.dieselRate; console.log(`Diesel rate: ${dieselRate}`); ``` ## Credit Manager Data Access credit manager configuration: ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); for (const cm of market.creditManagers) { console.log(`Credit Manager: ${cm.address}`); console.log(`Credit Facade: ${cm.creditFacade}`); // Debt limits console.log(`Min debt: ${cm.minDebt}`); console.log(`Max debt: ${cm.maxDebt}`); // Allowed tokens for (const token of cm.collateralTokens) { console.log(` ${token.symbol}: LT ${token.liquidationThreshold}`); } } ``` ## Real-Time Data via Compressors The SDK caches data on initialization. For real-time data, use compressors directly: ```typescript import { GearboxSDK, marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Get compressor address const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Fetch fresh market data const freshData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); console.log(`Fresh available liquidity: ${freshData.pool.availableLiquidity}`); ``` ## Filtering Markets Query markets by criteria: ```typescript // All USDC markets const usdcMarkets = sdk.marketRegister.markets.filter( m => m.pool.underlying.symbol === 'USDC' ); // Markets with high liquidity const liquidMarkets = sdk.marketRegister.markets.filter( m => m.pool.availableLiquidity > 1_000_000n * 10n ** 6n // > 1M ); // Markets by configurator const curatedMarkets = sdk.marketRegister.markets.filter( m => m.configurator === curatorAddress ); ``` ## Price Oracle Data Access price information through the market: ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); // Price oracle address const priceOracle = market.priceOracle; // Token prices are available through market data for (const token of market.tokens) { console.log(`${token.symbol}: ${token.price} USD`); } ``` ## Complete Example ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function getMarketOverview() { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const RAY = 10n ** 27n; for (const market of sdk.marketRegister.markets) { const pool = market.pool; console.log(`\n=== ${pool.underlying.symbol} Market ===`); console.log(`Pool: ${pool.address}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available: ${pool.availableLiquidity}`); const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Credit Managers: ${market.creditManagers.length}`); } } getMarketOverview().catch(console.error); ``` ## Next Steps * [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) - Query and manage credit accounts * [Multicalls](https://docs.gearbox.finance/developers/multicalls) - Build and execute multicalls For architectural background, see [Pool Architecture](https://docs.gearbox.finance/developers/pools). ## Credit Accounts Source: https://docs.gearbox.finance/developers/credit-accounts File: content/developers/credit-accounts.mdx Query and manage credit accounts using SDK services. > For Solidity credit operations, see [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts). ## Creating a Service Use `createCreditAccountService` for credit account operations: ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Create service (310 = V3.1) const service = createCreditAccountService(sdk, 310); ``` ## Querying Credit Accounts ### Get All Accounts for a Credit Manager ```typescript const accounts = await service.getCreditAccounts( { creditManager: cmAddress }, sdk.currentBlock ); for (const account of accounts) { console.log(`Account: ${account.addr}`); console.log(` Owner: ${account.owner}`); console.log(` Debt: ${account.debt}`); console.log(` Health Factor: ${account.healthFactor}`); } ``` ### Filter by Owner ```typescript const myAccounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: myAddress, }, sdk.currentBlock ); ``` ### Account Data Structure Each credit account includes: | Field | Type | Description | | ---------------- | ------------- | --------------------------------- | | `addr` | `address` | Credit Account contract address | | `owner` | `address` | Account owner | | `creditManager` | `address` | Parent Credit Manager | | `debt` | `uint256` | Total debt (principal + interest) | | `healthFactor` | `uint256` | Current HF (10000 = 1.0) | | `tokens` | `TokenInfo[]` | Token balances and values | | `isLiquidatable` | `boolean` | Whether account can be liquidated | ## Reading Account State ### Health Factor ```typescript const account = accounts[0]; // Health factor is scaled by 10000 (10000 = 1.0) const hf = Number(account.healthFactor) / 10000; console.log(`Health Factor: ${hf.toFixed(4)}`); if (account.isLiquidatable) { console.log('Account is liquidatable!'); } ``` ### Token Balances ```typescript for (const token of account.tokens) { console.log(`${token.symbol}:`); console.log(` Balance: ${token.balance}`); console.log(` Value (underlying): ${token.balanceInUnderlying}`); console.log(` LT: ${token.lt / 100}%`); } ``` ### Debt Breakdown ```typescript console.log(`Total Debt: ${account.debt}`); console.log(`Principal: ${account.borrowedAmount}`); console.log(`Accrued Interest: ${account.cumulativeQuotaInterest}`); console.log(`Quota Fees: ${account.quotaFees}`); ``` ## Market Discovery Find the market for a credit manager: ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); const creditFacade = market.creditFacade; console.log(`Credit Facade: ${creditFacade.address}`); console.log(`Pool: ${market.pool.address}`); ``` ## Opening a Credit Account ```typescript // Build multicall with SDK helpers const calls = [ service.prepareAddCollateral(usdcAddress, 10000n * 10n ** 6n), service.prepareIncreaseDebt(40000n * 10n ** 6n), ]; // Get credit facade const market = sdk.marketRegister.findByCreditManager(cmAddress); // Open account const hash = await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ## Closing a Credit Account ```typescript // Build close multicall - typically repay and withdraw const closeCalls = [ service.prepareDecreaseDebt(account.debt), // Repay all debt // Withdraw remaining collateral handled automatically ]; const hash = await market.creditFacade.write.closeCreditAccount([ account.addr, closeCalls, ]); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function queryAccounts(cmAddress: `0x${string}`) { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const service = createCreditAccountService(sdk, 310); // Get all accounts const accounts = await service.getCreditAccounts( { creditManager: cmAddress }, sdk.currentBlock ); console.log(`Found ${accounts.length} credit accounts\n`); for (const account of accounts) { const hf = Number(account.healthFactor) / 10000; console.log(`Account: ${account.addr}`); console.log(` Owner: ${account.owner}`); console.log(` Debt: ${account.debt}`); console.log(` Health Factor: ${hf.toFixed(4)}`); console.log(` Liquidatable: ${account.isLiquidatable}`); // Token breakdown console.log(` Tokens:`); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}: ${token.balance}`); } } console.log(''); } } queryAccounts('0x...').catch(console.error); ``` ## Next Steps * [Multicalls](https://docs.gearbox.finance/developers/multicalls) - Build complex operations For architectural background, see [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite). ## Lender (Pools) Source: https://docs.gearbox.finance/developers/gm-lender File: content/developers/gm-lender.mdx This section covers everything from the lender's perspective — how Gearbox pools work, how to deposit and withdraw, how interest rates are determined, and how the insurance mechanism protects depositors. ## Overview Lenders provide liquidity to ERC-4626 Pools and receive Diesel Tokens as yield-bearing receipts. Interest paid by borrowers flows back to the Pool, causing the Diesel Token exchange rate to appreciate over time. Lenders earn passive yield without managing positions or choosing strategies. ## What's in This Section | Page | Covers | | --- | --- | | [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) | Query pool state, TVL, utilization, and enriched metadata | | [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) | Deposit, withdraw, and preview pool transactions | | [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) | How supply/borrow APY is calculated, quota rates, and the interest rate curve | | [History & Events](https://docs.gearbox.finance/developers/gm-markets-insurance) | Historical metrics, parameter changes, and governance event feeds | | [Insurance](https://docs.gearbox.finance/developers/gm-markets-insurance) | Automated insurance fund, bad-debt coverage, and loss distribution | ## SDK Entry Points The primary namespaces for lender operations: ```ts // Discover lender opportunities const pools = await sdk.pools.list(); const detail = await sdk.pools.getDetail(poolId); // Deposit into a pool const tx = await sdk.positions.prepareDeposit({ pool, amount }); const preview = await sdk.positions.previewDeposit(tx); await sdk.transactions.execute(tx); // Check pool status const status = await sdk.pools.getStatus(poolId); // Historical data const apyHistory = await sdk.history.getMetric({ pool: poolId, metric: "supplyApy" }); ``` ## Learn More - [Lender Opportunities](https://docs.gearbox.finance/developers/gm-opps-lender) — discover pools via the unified opportunity search - [Positions (Strategies)](https://docs.gearbox.finance/developers/gm-positions) — the borrower side of the protocol - [SDK Namespaces](https://docs.gearbox.finance/developers/gm-start-namespaces) — full namespace reference ## Markets Source: https://docs.gearbox.finance/developers/gm-markets File: content/developers/gm-markets.mdx A Gearbox market is the fundamental unit of the protocol: a lending pool paired with one or more credit managers, a quota system, and an insurance buffer. This section covers how to query and interact with markets using the TypeScript SDK. ## What is a Market? Each market revolves around a single underlying asset (e.g. USDC, WETH). It contains: | Component | Purpose | |---|---| | **Pool** | ERC-4626 vault that holds liquidity and issues diesel (share) tokens | | **Credit Managers** | Define borrowing parameters, allowed collateral, and adapters | | **Quota Keeper** | Tracks per-token borrowing limits and quota interest rates | | **Interest Rate Model** | Computes base borrow rates from pool utilization | | **Treasury Splitter** | Retains fee revenue as an insurance buffer before distributing surplus | ## Accessing Markets ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk"; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Iterate all markets for (const market of sdk.marketRegister.markets) { console.log(`${market.pool.underlying.symbol} — ${market.creditManagers.length} CM(s)`); } ``` ## Section Overview | Page | Description | |---|---| | [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) | Query pools, credit managers, collateral tokens, and rates | | [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) | Deposit, withdraw, and manage LP positions via ERC-4626 | | [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) | Interest rate model parameters, quota rates, and quota limits | | [Insurance Mechanism](https://docs.gearbox.finance/developers/gm-markets-insurance) | TreasurySplitter, revenue retention, and bad debt coverage | ## Next Steps Start with [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) to learn how to read pool state and credit manager configuration, or jump to [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) if you want to deposit liquidity right away. ## Opportunities Source: https://docs.gearbox.finance/developers/gm-markets-opportunities File: content/developers/gm-markets-opportunities.mdx Discover yield opportunities across Gearbox Markets using the SDK vNext. The unified opportunity surface lets you search pools, leveraged strategies, and markets from a single entry point, across all supported chains. ## Unified Discovery The `sdk.opportunities.search()` method returns a combined list of pools and strategies, ranked and filterable. This is the recommended starting point for any discovery flow. ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk/official"; const sdk = await GearboxSDK.init({ chains: [1, 42161, 10], startFromCache: true, }); // Discover all opportunities across chains const opportunities = await sdk.opportunities.search(); for (const opp of opportunities) { console.log(`${opp.title} — ${opp.type} on chain ${opp.chainId}`); console.log(` APY: ${opp.headlineApy}% Capacity: ${opp.capacity}`); } ``` ## Opportunity Fields Every opportunity — whether pool, strategy, or market — shares a common shape: | Field | Type | Description | |---|---|---| | `id` | `string` | Unique identifier (address or composite key) | | `chainId` | `number` | Chain where the opportunity lives | | `type` | `"pool" \| "strategy" \| "market"` | Kind of opportunity | | `title` | `string` | Human-readable name (e.g. "dUSDC-V3-Tier1") | | `depositToken` | `TokenRef` | Token used to enter the position | | `headlineApy` | `number` | Composite APY (organic + incentives) | | `yieldType` | `string` | Yield source classification (e.g. "lending", "staking_spread") | | `capacity` | `string` | Remaining capacity in underlying token | | `access` | `"permissionless" \| "kyc"` | Whether entry requires KYC verification | | `risk` | `object` | Risk metadata (curator, oracle types, utilization) | | `points` | `object \| null` | Active points programs, if any | ## Pool Discovery Use `sdk.pools.list()` when you specifically want LP deposit opportunities: ```typescript const pools = await sdk.pools.list({ chainId: 1, asset: "STABLE", minApy: 5, minTvl: 1_000_000, }); for (const pool of pools) { console.log(`${pool.title} — APY ${pool.headlineApy}%`); console.log(` TVL: $${pool.tvlUsd} Utilization: ${pool.utilizationRate}%`); } ``` ## Strategy Discovery Use `sdk.strategies.list()` for leveraged strategy opportunities: ```typescript const strategies = await sdk.strategies.list({ chainId: 42161, asset: "ETH", minNetApy: 3, }); for (const s of strategies) { console.log(`${s.title} — Net APY ${s.headlineApy}%`); console.log(` Borrow: ${s.borrowableAmount} Leverage: up to ${s.maxLeverage}x`); } ``` ## Filtering All discovery methods accept a common filter object. Filters compose — pass multiple to narrow results. ### Filter by Chain ```typescript // Single chain const mainnetOpps = await sdk.opportunities.search({ chainId: 1 }); // Multiple chains const multiChain = await sdk.opportunities.search({ chainId: [1, 42161] }); ``` ### Filter by Asset Class The SDK classifies underlying tokens into asset classes for coarse filtering: | Asset Class | Constant | Tokens | |---|---|---| | Stablecoins | `Asset.STABLE` | USDC, USDT, DAI, GHO | | ETH-correlated | `Asset.ETH` | WETH, wstETH, rETH, cbETH | | BTC-correlated | `Asset.BTC` | WBTC, tBTC, sBTC | | Monad native | `Asset.MON` | MON, wMON | ```typescript import { Asset } from "@gearbox-protocol/sdk/official"; // Only stablecoin opportunities const stables = await sdk.opportunities.search({ asset: Asset.STABLE }); // ETH-correlated strategies on Arbitrum const ethStrategies = await sdk.strategies.list({ asset: Asset.ETH, chainId: 42161, }); ``` ### Filter by APY Floor ```typescript // Only opportunities with 5%+ headline APY const highYield = await sdk.opportunities.search({ minApy: 5 }); ``` ### Filter by TVL Minimum ```typescript // Only pools with at least $1M TVL const largePools = await sdk.pools.list({ minTvl: 1_000_000 }); ``` ### Filter by Access ```typescript // Only permissionless opportunities (no KYC) const open = await sdk.opportunities.search({ access: "permissionless" }); // Only KYC-gated opportunities const gated = await sdk.opportunities.search({ access: "kyc" }); ``` ### Combined Filters ```typescript const candidates = await sdk.opportunities.search({ chainId: [1, 42161], asset: Asset.STABLE, minApy: 4, minTvl: 500_000, access: "permissionless", }); ``` ## Multi-Chain Support Every entity in the SDK vNext includes a `chainId` field. The SDK can be initialized with multiple chains and will aggregate results across all of them: ```typescript const sdk = await GearboxSDK.init({ chains: [1, 42161, 10, 8453], startFromCache: true, }); // Results span all configured chains const allOpps = await sdk.opportunities.search(); // Group by chain const byChain = Object.groupBy(allOpps, opp => opp.chainId); ``` ## Freshness Metadata Discovery responses include metadata about data freshness and sources, so you can make trust decisions: ```typescript const result = await sdk.opportunities.search(); console.log(`Data as of: ${result.meta.asOf}`); console.log(`Sources: ${result.meta.sources.join(", ")}`); console.log(`Backend available: ${result.meta.backendAvailable}`); ``` | Meta Field | Type | Description | |---|---|---| | `asOf` | `string` | ISO timestamp of the data snapshot | | `sources` | `string[]` | Data sources used (e.g. `["backend", "onchain"]`) | | `backendAvailable` | `boolean` | Whether the backend enrichment service was reachable | When the backend is unavailable, the SDK falls back to on-chain data. APY enrichment and points data may be missing in this mode. ## Method Signatures | Method | Returns | Description | |---|---|---| | `sdk.opportunities.search(filters?)` | `Opportunity[]` | Unified discovery across pools, strategies, and markets | | `sdk.pools.list(filters?)` | `PoolOpportunity[]` | LP pool discovery | | `sdk.strategies.list(filters?)` | `StrategyOpportunity[]` | Leveraged strategy discovery | | `sdk.pools.getDetail(id)` | `PoolDetail` | Full pool due-diligence data | | `sdk.strategies.getDetail(id)` | `StrategyDetail` | Full strategy due-diligence data | ## Learn More - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) — reading pool metrics and credit manager configuration - [History & Events](https://docs.gearbox.finance/developers/gm-markets-history) — historical APY, utilization, and governance changes - [Positions](https://docs.gearbox.finance/developers/gm-accounts-positions) — opening, adjusting, and closing positions - [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) — low-level ERC-4626 pool interactions ## Markets Data Source: https://docs.gearbox.finance/developers/gm-markets-data File: content/developers/gm-markets-data.mdx Read pool metrics, credit manager configuration, collateral tokens, and interest rates using the Gearbox SDK. All data is cached on SDK initialization and can be refreshed via compressors. ## Initialize the SDK ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk"; import { createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); ``` ## Finding Markets The `marketRegister` provides cached access to all Gearbox markets: ```typescript // All markets const markets = sdk.marketRegister.markets; // Find by pool address const market = sdk.marketRegister.findByPool(poolAddress); // Find by credit manager address const market = sdk.marketRegister.findByCreditManager(cmAddress); ``` ## Filtering Markets Query markets by underlying token, liquidity, or configurator: ```typescript // All USDC markets const usdcMarkets = sdk.marketRegister.markets.filter( m => m.pool.underlying.symbol === "USDC" ); // Markets with > 1M available liquidity const liquidMarkets = sdk.marketRegister.markets.filter( m => m.pool.availableLiquidity > 1_000_000n * 10n ** 6n ); // Markets by configurator const curatedMarkets = sdk.marketRegister.markets.filter( m => m.configurator === curatorAddress ); ``` ## Pool State Access core pool metrics through the market object: ```typescript const pool = market.pool; console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available liquidity: ${pool.availableLiquidity}`); console.log(`Diesel rate (share price): ${pool.dieselRate}`); ``` ### Interest Rates Rates are stored in RAY precision (27 decimals): ```typescript const RAY = 10n ** 27n; const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Borrow APR: ${borrowAPR.toFixed(2)}%`); ``` | Field | Type | Description | |---|---|---| | `supplyRate` | `bigint` | Annual supply rate for LPs (RAY) | | `baseInterestRate` | `bigint` | Annual base borrow rate (RAY) | | `dieselRate` | `bigint` | Current share price (RAY) | | `totalAssets` | `bigint` | Total underlying in pool | | `availableLiquidity` | `bigint` | Underlying available for borrowing | ## Credit Manager Data Each market can have multiple credit managers with different risk profiles: ```typescript for (const cm of market.creditManagers) { console.log(`Credit Manager: ${cm.address}`); console.log(`Credit Facade: ${cm.creditFacade}`); console.log(`Min debt: ${cm.minDebt}`); console.log(`Max debt: ${cm.maxDebt}`); // Allowed collateral tokens and their liquidation thresholds for (const token of cm.collateralTokens) { console.log(` ${token.symbol}: LT ${token.liquidationThreshold}`); } } ``` | Field | Type | Description | |---|---|---| | `minDebt` | `bigint` | Minimum borrowable amount | | `maxDebt` | `bigint` | Maximum borrowable amount | | `collateralTokens` | `array` | Allowed tokens with liquidation thresholds | | `creditFacade` | `Address` | Entry point for user interactions | ## Price Oracle Data Access token prices through the market: ```typescript const priceOracle = market.priceOracle; for (const token of market.tokens) { console.log(`${token.symbol}: ${token.price} USD`); } ``` ## Real-Time Data via Compressors The SDK caches data on initialization. For fresh on-chain data, query compressors directly: ```typescript import { GearboxSDK, marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from "@gearbox-protocol/sdk"; // Get compressor address const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Fetch fresh market data const freshData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: "getMarketData", args: [poolAddress], }); console.log(`Fresh available liquidity: ${freshData.pool.availableLiquidity}`); ``` ## Complete Example ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk"; import { createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; async function getMarketOverview() { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const RAY = 10n ** 27n; for (const market of sdk.marketRegister.markets) { const pool = market.pool; console.log(`\n=== ${pool.underlying.symbol} Market ===`); console.log(`Pool: ${pool.address}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available: ${pool.availableLiquidity}`); const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Credit Managers: ${market.creditManagers.length}`); } } getMarketOverview().catch(console.error); ``` ## Next Steps - [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) -- Deposit and withdraw liquidity - [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) -- Understand rate models and quota limits - [Insurance Mechanism](https://docs.gearbox.finance/developers/gm-markets-insurance) -- How protocol revenue protects lenders ## Multicalls Source: https://docs.gearbox.finance/developers/multicalls File: content/developers/multicalls.mdx Build and execute multicalls using SDK service helpers. > For Solidity multicall encoding, see [Multicalls](https://docs.gearbox.finance/developers/multicalls). ## Service Multicall Helpers The SDK provides structured multicall builders via `createCreditAccountService`: ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); ``` ## Available Service Methods | Method | Operation | | ---------------------------------------------- | -------------------------- | | `prepareAddCollateral(token, amount)` | Add collateral from wallet | | `prepareIncreaseDebt(amount)` | Borrow from pool | | `prepareDecreaseDebt(amount)` | Repay debt | | `prepareUpdateQuota(token, change, minQuota)` | Adjust token quota | | `prepareWithdrawCollateral(token, amount, to)` | Remove collateral | ## Detailed Operation Guides For comprehensive documentation of each operation: **SDK Helper Operations:** * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Transfer tokens with approval patterns * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Borrowing, repayment, and constraints * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Quota mechanics and limits * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - Safe pricing and health impact **Manual Encoding Operations:** * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Balance delta protection * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Adapter interaction patterns * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Token mask management * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - On-demand oracle data * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Health check optimization * [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) - Security cleanup ## Building a Multicall ```typescript // Build multicall with SDK helpers const calls = [ // Add 10,000 USDC as collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Borrow 40,000 USDC (5x leverage) service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for destination token service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; ``` ## Executing Multicalls ### On Existing Account ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); await market.creditFacade.write.multicall([ creditAccountAddress, calls, ]); ``` ### Opening with Multicall ```typescript const hash = await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ## Combining SDK Helpers with Raw Encoding For adapter calls or custom operations, combine SDK helpers with manual encoding: ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; const calls = [ // SDK helpers for standard operations service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Manual encoding for adapter calls { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [{ tokenIn: usdcAddress, tokenOut: wethAddress, fee: 500, recipient: '0x0000000000000000000000000000000000000000', // Adapter overrides deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), amountIn: 50_000n * 10n ** 6n, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n, }], }), }, // SDK helper for quota service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; ``` ## Slippage Protection Add slippage checks around external calls: ```typescript const calls = [ // Store expected balances before swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: wethAddress, amount: minExpectedWeth }]], }), }, // Swap operation { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // Compare balances after swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, ]; ``` ## On-Demand Price Updates If using pull-based oracles, add price updates first: ```typescript const calls = [ // Price updates must be first { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdates', args: [[{ token: wethAddress, reserve: false, data: priceData }]], }), }, // Then standard operations service.prepareAddCollateral(usdcAddress, amount), service.prepareIncreaseDebt(debtAmount), ]; ``` ## Getting Adapter Addresses Retrieve adapter addresses from the Credit Manager: ```typescript import { getContract } from 'viem'; import { creditManagerAbi } from '@gearbox-protocol/sdk'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get adapter for a protocol (e.g., Uniswap V3 Router) const uniswapV3Adapter = await creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService, iCreditFacadeV300MulticallAbi, } from '@gearbox-protocol/sdk'; import { encodeFunctionData, createPublicClient, createWalletClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function leveragePosition() { const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client: publicClient, marketConfigurators: [], }); const service = createCreditAccountService(sdk, 310); // Find market const market = sdk.marketRegister.findByCreditManager(cmAddress); // Build multicall const calls = [ // Add collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Borrow service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for final token service.prepareUpdateQuota(targetToken, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; // Execute const walletClient = createWalletClient({ chain: mainnet, transport: http(), account: myAccount, }); const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'openCreditAccount', args: [myAccount.address, calls, 0n], }); console.log(`Transaction: ${hash}`); } ``` ## Best Practices 1. **Always use slippage protection** when performing swaps 2. **Price updates first** if using pull-based oracles 3. **Set quotas** for tokens you'll hold as collateral 4. **Approve collateral** to Credit Manager (not Facade) before adding For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Operations Reference Source: https://docs.gearbox.finance/developers/operations-reference File: content/developers/operations-reference.mdx Detailed guides for each multicall operation in TypeScript. > For Solidity multicall encoding, see [Multicalls](https://docs.gearbox.finance/developers/multicalls). ## Why This Section? The main [multicalls.md](https://docs.gearbox.finance/developers/multicalls) covers the basics: SDK service helpers, combining with raw encoding, and a complete example. This section goes deeper on each operation - when you need it, complete examples, and what can go wrong. ## Quick Reference | Operation | SDK Helper | When to Use | Guide | | -------------------- | ----------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | Add Collateral | `prepareAddCollateral()` | Deposit tokens to increase health factor | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | | Increase Debt | `prepareIncreaseDebt()` | Borrow from pool | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Decrease Debt | `prepareDecreaseDebt()` | Repay borrowed funds | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Update Quota | `prepareUpdateQuota()` | Enable/adjust quota token exposure | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | | Withdraw Collateral | `prepareWithdrawCollateral()` | Remove tokens from account | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | | Slippage Control | Manual encoding | Protect swaps from sandwich attacks | [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) | | External Calls | Manual encoding | Interact with Uniswap, Curve, etc. | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | | Enable/Disable Token | Manual encoding | Explicit collateral management | [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) | | Price Updates | Manual encoding | Update Pyth/Redstone feeds | [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) | | Check Params | Manual encoding | Optimize gas, set min health factor | [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) | | Revoke Allowances | Manual encoding | Security measure after suspicious activity | [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) | ## Page Structure Each operation guide follows the same structure: 1. **Why** - When you need this operation 2. **What** - What it does and how it fits the system 3. **How** - Working TypeScript code 4. **Gotchas** - Common mistakes and edge cases 5. **See Also** - Related operations and Solidity reference ## SDK Helpers vs Manual Encoding **Five operations have SDK helpers** via `createCreditAccountService`: * `prepareAddCollateral(token, amount)` * `prepareIncreaseDebt(amount)` * `prepareDecreaseDebt(amount)` * `prepareUpdateQuota(token, change, minQuota)` * `prepareWithdrawCollateral(token, amount, to)` **Six operations require manual encoding** with viem's `encodeFunctionData`: * `storeExpectedBalances` / `compareBalances` * `enableToken` / `disableToken` * `onDemandPriceUpdate` * `setFullCheckParams` * `revokeAdapterAllowances` All manual encoding uses `iCreditFacadeV300MulticallAbi` from `@gearbox-protocol/sdk`. ## Related * [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) - Basic patterns and complete example * [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) - Account data and services * [Solidity Multicalls](https://docs.gearbox.finance/developers/multicalls) - On-chain implementation details ## Pool Operations Source: https://docs.gearbox.finance/developers/gm-markets-pools File: content/developers/gm-markets-pools.mdx Gearbox pools are ERC-4626 compliant vaults. Liquidity providers deposit underlying assets and receive diesel tokens (shares) that appreciate as borrowers pay interest. This page covers how to interact with pools using the TypeScript SDK and viem. ## Pool Overview | Concept | Description | |---|---| | **Underlying** | The base asset (e.g. USDC, WETH) | | **Diesel token** | The pool's share token (e.g. dUSDC, dWETH) | | **Diesel rate** | Share price in RAY (27 decimals): 1 diesel = `dieselRate / 10^27` underlying | | **ERC-4626** | Standard vault interface for deposit, withdraw, mint, redeem | ## Reading Pool State ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk"; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total assets: ${pool.totalAssets}`); console.log(`Available liquidity: ${pool.availableLiquidity}`); console.log(`Diesel rate: ${pool.dieselRate}`); ``` ## Deposit Deposit underlying assets and receive diesel tokens: ```typescript import { erc20Abi } from "viem"; import { iPoolV3Abi } from "@gearbox-protocol/sdk"; // 1. Approve the pool to spend underlying const approveHash = await walletClient.writeContract({ address: underlyingAddress, abi: erc20Abi, functionName: "approve", args: [poolAddress, amount], }); await publicClient.waitForTransactionReceipt({ hash: approveHash }); // 2. Deposit and receive shares const depositHash = await walletClient.writeContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "deposit", args: [amount, receiverAddress], }); const receipt = await publicClient.waitForTransactionReceipt({ hash: depositHash }); ``` ### Deposit with Referral Track referrals on-chain using the Gearbox-specific extension: ```typescript const hash = await walletClient.writeContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "depositWithReferral", args: [amount, receiverAddress, 123n], // referralCode }); ``` ## Preview Operations Check expected shares or assets before executing: ```typescript // How many shares for a given deposit? const expectedShares = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "previewDeposit", args: [amount], }); // How many assets needed to mint exact shares? const requiredAssets = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "previewMint", args: [shares], }); // How many shares burned on withdraw? const sharesBurned = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "previewWithdraw", args: [amount], }); // How many assets received on redeem? const assetsReceived = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "previewRedeem", args: [shares], }); ``` ## Withdraw Withdraw exact underlying assets by burning the required shares: ```typescript const hash = await walletClient.writeContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "withdraw", args: [amount, receiverAddress, ownerAddress], }); ``` ## Redeem Burn exact shares and receive underlying assets: ```typescript const hash = await walletClient.writeContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "redeem", args: [shares, receiverAddress, ownerAddress], }); ``` ## Mint Mint exact shares by depositing the required underlying: ```typescript // Preview required assets first const requiredAssets = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "previewMint", args: [shares], }); // Approve and mint await walletClient.writeContract({ address: underlyingAddress, abi: erc20Abi, functionName: "approve", args: [poolAddress, requiredAssets], }); await walletClient.writeContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "mint", args: [shares, receiverAddress], }); ``` ## Maximum Operations Query the maximum deposit, withdraw, mint, or redeem for a given address: ```typescript const maxDeposit = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "maxDeposit", args: [receiverAddress], }); const maxWithdraw = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "maxWithdraw", args: [ownerAddress], }); const maxRedeem = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "maxRedeem", args: [ownerAddress], }); const maxMint = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "maxMint", args: [receiverAddress], }); ``` ## ERC-4626 Method Summary | Method | Input | Output | Description | |---|---|---|---| | `deposit(assets, receiver)` | Underlying amount | Shares minted | Deposit exact assets | | `mint(shares, receiver)` | Share amount | Assets deposited | Mint exact shares | | `withdraw(assets, receiver, owner)` | Underlying amount | Shares burned | Withdraw exact assets | | `redeem(shares, receiver, owner)` | Share amount | Assets received | Burn exact shares | | `previewDeposit(assets)` | Underlying amount | Expected shares | Preview deposit | | `previewMint(shares)` | Share amount | Required assets | Preview mint | | `previewWithdraw(assets)` | Underlying amount | Shares to burn | Preview withdraw | | `previewRedeem(shares)` | Share amount | Assets to receive | Preview redeem | ## Utilization Calculation Compute pool utilization from available data: ```typescript const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; const totalAssets = pool.totalAssets; const available = pool.availableLiquidity; const borrowed = totalAssets - available; // Utilization in basis points (0-10000) const utilizationBps = totalAssets > 0n ? Number(borrowed * 10000n / totalAssets) : 0; console.log(`Utilization: ${(utilizationBps / 100).toFixed(2)}%`); ``` ## Next Steps - [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) -- How borrow rates and quota fees are determined - [Insurance Mechanism](https://docs.gearbox.finance/developers/gm-markets-insurance) -- How protocol revenue protects lenders - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) -- Query market state and credit manager config ## Adding Collateral Source: https://docs.gearbox.finance/developers/adding-collateral File: content/developers/adding-collateral.mdx Deposit tokens from your wallet to a Credit Account. > For Solidity implementation, see [Adding Collateral](https://docs.gearbox.finance/developers/multicalls). ## Why You need to add collateral when: * **Opening an account** - Initial deposit to enable borrowing * **Improving health factor** - Account approaching liquidation threshold * **Enabling more borrowing** - Current collateral limits how much you can borrow Adding collateral increases your account's total weighted value (TWV), which improves the health factor and allows larger debt positions. ## What `addCollateral` transfers tokens from your wallet to the Credit Account. On execution: 1. The Credit Manager calls `transferFrom` to move tokens from caller to Credit Account 2. The token is enabled as collateral (if not already enabled) 3. Quoted tokens are NOT auto-enabled - you must set a quota separately **Important:** Approve tokens to the **Credit Manager**, not the Credit Facade. The Credit Manager is the contract that actually executes the transfer. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); // Build the multicall const calls = [ service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), ]; // First, approve to Credit Manager (not Facade!) const market = sdk.marketRegister.findByCreditManager(cmAddress); await usdcContract.write.approve([ market.creditManager.address, 10_000n * 10n ** 6n, ]); // Execute on existing account await market.creditFacade.write.multicall([creditAccountAddress, calls]); // Or open new account with collateral await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ### Using Permit (No Separate Approval) For EIP-2612 compatible tokens, you can avoid the separate approval transaction: ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Sign permit message (details depend on your wallet setup) const { v, r, s, deadline } = await signPermit(/* ... */); const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'addCollateralWithPermit', args: [tokenAddress, amount, deadline, v, r, s], }), }, ]; ``` ## Gotchas ### Approve to Credit Manager, Not Facade The most common mistake. The Credit Manager executes the `transferFrom`, so it needs the approval: ```typescript // CORRECT await token.write.approve([creditManager.address, amount]); // WRONG - will fail await token.write.approve([creditFacade.address, amount]); ``` ### Quoted Tokens Need Quota Adding a quoted token as collateral does NOT automatically enable it. You must also call `updateQuota`: ```typescript const calls = [ service.prepareAddCollateral(quotedTokenAddress, amount), service.prepareUpdateQuota(quotedTokenAddress, quotaAmount, minQuota), ]; ``` ### Direct Transfers Don't Enable Sending tokens directly to a Credit Account (via `transfer`) does NOT enable them as collateral. You still need a multicall with `enableToken` to count them in the health factor. ### Invalid Collateral Tokens Only tokens recognized by the Credit Manager can be used as collateral. Transferring unrecognized tokens to a Credit Account may result in them being stuck (only governance can recover). Check if a token is valid: ```typescript // This reverts if token is not valid collateral const mask = await creditManager.read.getTokenMaskOrRevert([tokenAddress]); ``` ## See Also * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Often combined with adding collateral * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Required for quoted tokens ## Interest Rates & Quotas Source: https://docs.gearbox.finance/developers/gm-markets-rates File: content/developers/gm-markets-rates.mdx Gearbox uses a two-part interest model: a **base interest rate** driven by pool utilization, and **quota rates** that add per-token fees for collateral exposure. Together they determine the total borrowing cost for credit accounts. ## Base Interest Rate Model The base rate follows a linear kinked model with three slopes, similar to Aave/Compound. Parameters are set per pool. | Parameter | Description | |---|---| | `U1` | First utilization kink (basis points) | | `U2` | Second utilization kink (basis points) | | `Rbase` | Base rate at 0% utilization | | `Rslope1` | Rate slope between 0 and U1 | | `Rslope2` | Rate slope between U1 and U2 | | `Rslope3` | Rate slope above U2 (steep penalty zone) | ### Reading Model Parameters ```typescript import { iLinearInterestRateModelV3Abi } from "@gearbox-protocol/sdk"; // Get the IRM address from the pool const irmAddress = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "interestRateModel", }); // Query model parameters const params = await publicClient.readContract({ address: irmAddress, abi: iLinearInterestRateModelV3Abi, functionName: "getModelParameters", }); const [U1, U2, Rbase, Rslope1, Rslope2, Rslope3] = params; console.log(`Kink 1: ${U1} bps, Kink 2: ${U2} bps`); console.log(`Slopes: ${Rbase} / ${Rslope1} / ${Rslope2} / ${Rslope3}`); ``` ### Current Rates via SDK ```typescript const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; const RAY = 10n ** 27n; const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Base Borrow APR: ${borrowAPR.toFixed(2)}%`); ``` ## Quota System Quotas regulate how much pool capital can be exposed to specific collateral tokens. Each quoted token carries its own interest rate on top of the base borrow rate. ### How Quotas Work - A credit account's "quota" for a token is the amount of debt backed by that collateral. - Quota interest is **additive** (linear), not compounding. The PoolQuotaKeeper tracks a `cumulativeIndex` per token. - If `totalQuoted` reaches the `limit` for a token, any transaction that increases that quota reverts with `QuotaIsOutOfBoundsException`. ### Reading Quota Parameters ```typescript import { getContract } from "viem"; import { poolQuotaKeeperV3Abi } from "@gearbox-protocol/sdk"; // Get the QuotaKeeper address const quotaKeeperAddress = await publicClient.readContract({ address: poolAddress, abi: iPoolV3Abi, functionName: "poolQuotaKeeper", }); const quotaKeeper = getContract({ address: quotaKeeperAddress, abi: poolQuotaKeeperV3Abi, client: publicClient, }); // Get global quota parameters for a token const tokenParams = await quotaKeeper.read.getTokenQuotaParams([tokenAddress]); ``` The returned object contains: | Field | Type | Description | |---|---|---| | `rate` | `uint16` | Annual interest rate in basis points | | `cumulativeIndex` | `uint192` | Accumulated interest index | | `quotaIncreaseFee` | `uint16` | One-time fee on quota increases (basis points) | | `totalQuoted` | `uint96` | Total quota across all credit accounts | | `limit` | `uint96` | Maximum allowed `totalQuoted` | | `isActive` | `bool` | Whether the token is actively quoted | ### Checking Available Quota ```typescript const tokenParams = await quotaKeeper.read.getTokenQuotaParams([tokenAddress]); const availableQuota = tokenParams.limit - tokenParams.totalQuoted; console.log(`Total quoted: ${tokenParams.totalQuoted}`); console.log(`Limit: ${tokenParams.limit}`); console.log(`Available: ${availableQuota}`); console.log(`Rate: ${tokenParams.rate} bps`); ``` ### Per-Account Quota and Interest Query how much quota an individual credit account is consuming: ```typescript const [quoted, outstandingInterest] = await quotaKeeper.read.getQuotaAndOutstandingInterest([ creditAccountAddress, tokenAddress, ]); console.log(`Account quota: ${quoted}`); console.log(`Outstanding interest: ${outstandingInterest}`); ``` ## Rate Setting: Gauge vs. Tumbler Quota rates are set by an external contract implementing `IRateKeeper`. There are two models: ### GaugeV3 (Voting Model) Used in the core protocol. GEAR token stakers vote to move rates between a `minRate` and `maxRate` for each token. ### TumblerV3 (Curator Model) Used in the permissionless ecosystem. Curators set exact rates directly. | Function | Description | |---|---| | `setRate(address token, uint16 rate)` | Set rate in basis points | | `updateRates()` | Apply pending rate changes | | `epochLength` | Minimum time between rate updates | Rates set via Tumbler only take effect after `updateRates()` is called, and the Tumbler enforces an `epochLength` to prevent frequent manipulation. ## Total Borrowing Cost The total interest rate for a credit account is: ``` Total Rate = Base Borrow Rate + Sum(Quota Rate_i * Quota_i / Debt) ``` Where each quoted token contributes its quota rate proportional to the quota amount relative to total debt. ## Next Steps - [Insurance Mechanism](https://docs.gearbox.finance/developers/gm-markets-insurance) -- How fees fund the insurance buffer - [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) -- Deposit and withdraw liquidity - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) -- Query pool and credit manager state ## Debt Management Source: https://docs.gearbox.finance/developers/debt-management File: content/developers/debt-management.mdx Borrow from or repay to the pool. > For Solidity implementation, see [Debt Management](https://docs.gearbox.finance/developers/multicalls). ## Why You manage debt when: * **Increasing leverage** - Borrow more to amplify exposure * **Taking profit** - Repay debt while keeping collateral positions * **Reducing risk** - Lower debt to improve health factor * **Closing account** - Repay all debt before withdrawal Debt operations affect your health factor: borrowing decreases it, repaying increases it. ## What ### Increase Debt `increaseDebt` borrows the underlying asset from the pool to your Credit Account: 1. Pool transfers underlying to Credit Account 2. Debt parameters (principal + interest) are recalculated 3. Health factor decreases ### Decrease Debt `decreaseDebt` repays debt from Credit Account's underlying balance: 1. Underlying is transferred from Credit Account to pool 2. Debt parameters are recalculated 3. Health factor increases **Repayment order** (when not paying full debt): 1. Quota-related fees (quota increase fees) 2. Accrued quota interest 3. Interest + interest fee (split pro-rata if partial) 4. Principal This means partial payments may not reduce your principal at all. ## How ### Borrow More ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); const calls = [ service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Borrow 40,000 USDC ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Repay Debt ```typescript const calls = [ service.prepareDecreaseDebt(10_000n * 10n ** 6n), // Repay 10,000 USDC ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Repay All Debt Pass `type(uint256).max` equivalent to repay everything: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ // Zero all quotas FIRST (required before full repayment) service.prepareUpdateQuota(quotedToken1, BigInt.asIntN(96, -1n * 2n ** 95n), 0n), service.prepareUpdateQuota(quotedToken2, BigInt.asIntN(96, -1n * 2n ** 95n), 0n), // Then repay all debt service.prepareDecreaseDebt(MAX_UINT256), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Common Pattern: Add Collateral + Borrow ```typescript const calls = [ // First add collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Then borrow (5x leverage) service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for destination token service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; // Don't forget approval to Credit Manager! await usdcContract.write.approve([market.creditManager.address, 10_000n * 10n ** 6n]); await market.creditFacade.write.openCreditAccount([ownerAddress, calls, 0n]); ``` ## Gotchas ### One Debt Update Per Block You cannot increase AND decrease debt in the same block. This constraint prevents manipulation: ```typescript // WRONG - will revert on second operation const calls = [ service.prepareIncreaseDebt(amount1), service.prepareDecreaseDebt(amount2), // Reverts! ]; // CORRECT - one multicall, one debt operation const calls = [ service.prepareIncreaseDebt(netAmount), ]; ``` ### Zero All Quotas Before Full Repayment Non-zero quotas with zero debt is an invalid state. Zero your quotas BEFORE the final debt repayment: ```typescript const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quotas first service.prepareUpdateQuota(token1, INT96_MIN, 0n), service.prepareUpdateQuota(token2, INT96_MIN, 0n), // Then full repayment service.prepareDecreaseDebt(MAX_UINT256), ]; ``` ### Debt Must Stay in Range After any debt change, the principal must be either: * Zero (fully repaid), OR * Within `[minDebt, maxDebt]` range You cannot have debt between 0 and `minDebt`. ### Forbidden Tokens Block Borrowing If your account has forbidden tokens enabled as collateral, you cannot increase debt. Disable them first. ### Interest Accrues Continuously When repaying "the full amount", the debt may have grown since you read it. Add a buffer: ```typescript // Read current total debt const accountData = await service.getCreditAccountData(creditAccountAddress); const totalDebt = accountData.debt; // Add 0.1% buffer for interest accrual const repayAmount = totalDebt + (totalDebt / 1000n); // Or just use MAX_UINT256 to repay whatever the current amount is const calls = [service.prepareDecreaseDebt(MAX_UINT256)]; ``` ### Cannot Decrease on Open / Increase on Close * `decreaseDebt` is prohibited when opening an account * `increaseDebt` is prohibited when closing an account This prevents gaming the system by borrowing during liquidation or repaying during account creation. ## See Also * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Quota interest affects total debt * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Often combined with borrowing * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - Often combined with repaying ## History & Events Source: https://docs.gearbox.finance/developers/gm-markets-history File: content/developers/gm-markets-history.mdx Query historical time-series data and track parameter changes across Gearbox Markets using the SDK vNext. History and event feeds require the backend enrichment service but degrade gracefully when it is unavailable. ## Historical Metrics Use `sdk.history.getMetric()` to retrieve time-series data for any pool or strategy metric. ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk/official"; const sdk = await GearboxSDK.init({ chains: [1, 42161], startFromCache: true, }); // APY history for a pool over the last 90 days const apyHistory = await sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "apy", period: "90d", }); for (const point of apyHistory.data) { console.log(`${point.date}: ${point.value}%`); } ``` ### Available Metrics | Metric | Entity Type | Description | |---|---|---| | `"apy"` | Pool | Composite supply APY (organic + incentives) | | `"utilization"` | Pool | Pool utilization rate (0-1) | | `"tvl"` | Pool | Total value locked in underlying token | | `"borrow_rate"` | Pool | Current borrow rate for the pool | | `"share_price"` | Pool | Diesel token price in underlying — drops indicate bad debt | | `"oracle_price"` | Token | Oracle price history for a collateral token | ### Parameters | Parameter | Type | Required | Description | |---|---|---|---| | `entity` | `string` | Yes | Pool address, strategy ID, or token address | | `chainId` | `number` | Yes | Chain ID for the entity | | `metric` | `string` | Yes | One of the metrics listed above | | `period` | `string` | No | Time window: `"7d"`, `"30d"`, `"90d"`, `"1y"`. Default `"90d"` | | `resolution` | `string` | No | Data point interval: `"1h"`, `"1d"`, `"1w"`. Default `"1d"` | ### Multi-Metric Query Fetch several metrics at once for a single entity: ```typescript const [apy, utilization, tvl] = await Promise.all([ sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "apy", period: "90d" }), sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "utilization", period: "90d" }), sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "tvl", period: "90d" }), ]); ``` ### Share Price Monitoring Share price is the primary indicator for bad debt socialization. A drop in share price means losses were distributed to LPs: ```typescript const sharePrice = await sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "share_price", period: "90d", resolution: "1d", }); // Detect any drops for (let i = 1; i < sharePrice.data.length; i++) { const prev = sharePrice.data[i - 1].value; const curr = sharePrice.data[i].value; if (curr < prev) { console.warn(`Share price dropped on ${sharePrice.data[i].date}: ${prev} → ${curr}`); } } ``` ## Event Feed Use `sdk.events.getFeed()` to retrieve a log of parameter changes — both past governance actions and pending scheduled changes. ```typescript const feed = await sdk.events.getFeed({ entity: poolAddress, chainId: 1, since: "2025-01-01T00:00:00Z", }); for (const event of feed.events) { console.log(`[${event.timestamp}] ${event.type}: ${event.description}`); } ``` ### Event Types | Event Type | Description | |---|---| | `"collateral_added"` | New collateral token added to a credit manager | | `"collateral_removed"` | Token forbidden or removed | | `"lt_changed"` | Liquidation threshold updated | | `"debt_limit_changed"` | Credit manager debt limit adjusted | | `"irm_updated"` | Interest rate model parameters changed | | `"cm_added"` | New credit manager connected to pool | | `"quota_rate_changed"` | Token quota rate adjusted | | `"curator_changed"` | Pool curator address updated | | `"oracle_changed"` | Oracle feed updated for a collateral token | ### Feed Parameters | Parameter | Type | Required | Description | |---|---|---|---| | `entity` | `string` | Yes | Pool address or credit manager address | | `chainId` | `number` | Yes | Chain ID | | `since` | `string` | No | ISO timestamp — only events after this time | | `includeProjected` | `boolean` | No | Include pending/scheduled changes. Default `true` | ## State Projections Instead of abstract event labels, the SDK provides concrete before/after snapshots through State Projections. This lets you see exactly how a pending governance change will affect parameters. ### PoolStateProjection ```typescript const projection = await sdk.events.getProjection({ entity: poolAddress, chainId: 1, }); // Current state console.log("Current parameters:", projection.current); // Projected state after pending changes if (projection.projected) { console.log("Projected parameters:", projection.projected); // See individual parameter diffs for (const diff of projection.changes) { console.log(`${diff.parameter}: ${diff.from} → ${diff.to} (ETA: ${diff.eta})`); } } ``` ### Projection Fields | Field | Type | Description | |---|---|---| | `current` | `ParameterSnapshot` | Current on-chain parameter values | | `projected` | `ParameterSnapshot \| null` | Parameters after all pending changes apply. `null` when backend unavailable | | `changes` | `ParameterDiff[]` | List of individual parameter diffs | ### ParameterDiff Each diff in `changes` is a concrete before/after comparison: | Field | Type | Description | |---|---|---| | `parameter` | `string` | Parameter name (e.g. `"quotaRate"`, `"debtLimit"`, `"liquidationThreshold"`) | | `token` | `string \| null` | Affected token address, if parameter is token-scoped | | `from` | `string \| number` | Current value | | `to` | `string \| number` | Value after change applies | | `eta` | `string` | ISO timestamp when the change takes effect | | `status` | `"pending" \| "queued" \| "executed"` | Current status of the change | ### Example: Quota Rate Change ```typescript const projection = await sdk.events.getProjection({ entity: poolAddress, chainId: 1, }); // Find quota rate changes const quotaChanges = projection.changes.filter(d => d.parameter === "quotaRate"); for (const change of quotaChanges) { console.log( `${change.token} quota rate: ${change.from}% → ${change.to}% (ETA: ${change.eta})` ); // Output: "USDC quota rate: 3% → 4% (ETA: 2025-04-20T12:00:00Z)" } ``` ## Graceful Degradation History and event data come from the Gearbox backend (`GET /v2/history/:metric`, `GET /v2/events`). When the backend is unavailable, the SDK degrades gracefully: | Feature | Backend Available | Backend Unavailable | |---|---|---| | `sdk.history.getMetric()` | Full time-series data | Throws or returns empty `data[]` | | `sdk.events.getFeed()` | Past + pending events | On-chain events only, no pending | | `projection.projected` | Concrete snapshot | `null` | | `projection.changes` | Full diff list | `[]` (empty array) | Check backend availability before relying on enriched data: ```typescript const result = await sdk.history.getMetric({ entity: poolAddress, chainId: 1, metric: "apy", }); if (!result.meta.backendAvailable) { console.warn("Backend unavailable — historical data may be incomplete"); } ``` ## Backend Endpoints The SDK vNext communicates with these backend endpoints for history and events: | Endpoint | SDK Method | Description | |---|---|---| | `GET /v2/history/:metric` | `sdk.history.getMetric()` | Time-series metric data | | `GET /v2/events` | `sdk.events.getFeed()` | Parameter change feed | ## Learn More - [Opportunities](https://docs.gearbox.finance/developers/gm-markets-opportunities) — discovering yield opportunities - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) — reading current pool metrics and credit manager state - [Interest Rates](https://docs.gearbox.finance/developers/gm-markets-rates) — understanding the interest rate model - [Positions](https://docs.gearbox.finance/developers/gm-accounts-positions) — managing positions with prepare/preview/execute ## Insurance Mechanism Source: https://docs.gearbox.finance/developers/gm-markets-insurance File: content/developers/gm-markets-insurance.mdx The Gearbox insurance mechanism is a liquidity buffer held in the `TreasurySplitter` contract. It acts as first-loss protection for lenders by retaining protocol revenue before distributing surplus to curators and the DAO. ## How It Works The `TreasurySplitter` collects fees from interest payments and liquidations. Before any fees can be claimed, the contract checks whether its balance exceeds the configured `tokenInsuranceAmount`: - **Balance < Insurance Amount:** Distribution is blocked. All funds remain in the contract. - **Balance >= Insurance Amount:** Only the surplus (`balance - insuranceAmount`) is distributed. The insurance floor stays locked. This guarantees a pool of liquid assets is always available to cover unexpected losses. ## Fee Flow ``` Protocol Fees (interest + liquidations) | v TreasurySplitter | +--- Balance < Insurance Target? ---> Retain all (buffer building) | +--- Balance >= Insurance Target? ---> Distribute surplus to Curator / DAO ``` ## Covering Bad Debt When a liquidation results in bad debt (collateral value < debt), the protocol covers the shortfall: 1. A liquidator calls `liquidateCreditAccount`. Collateral proceeds are insufficient to repay the full debt. 2. The Credit Manager calls `pool.repayCreditAccount(...)` with the loss amount. 3. The Pool burns LP shares held by the Treasury address to cover the loss. 4. The TreasurySplitter's accumulated LP tokens serve as the recapitalization source. ## Reading Insurance State ### Check Insurance Target The minimum retained amount before any distribution occurs: ```typescript import { getContract } from "viem"; import { iTreasurySplitterAbi } from "@gearbox-protocol/sdk"; const treasurySplitter = getContract({ address: treasurySplitterAddress, abi: iTreasurySplitterAbi, client: publicClient, }); const insuranceTarget = await treasurySplitter.read.tokenInsuranceAmount([tokenAddress]); console.log(`Insurance target: ${insuranceTarget}`); ``` ### Check Current Buffer Status Compare the actual token balance to the target: ```typescript import { erc20Abi } from "viem"; const target = await treasurySplitter.read.tokenInsuranceAmount([tokenAddress]); const balance = await publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [treasurySplitterAddress], }); const fullyInsured = balance >= target; const surplus = balance > target ? balance - target : 0n; const deficit = balance < target ? target - balance : 0n; console.log(`Target: ${target}`); console.log(`Balance: ${balance}`); console.log(`Fully insured: ${fullyInsured}`); console.log(`Surplus: ${surplus}`); console.log(`Deficit: ${deficit}`); ``` ### Query Fee Distribution Config Understand how surplus fees are split between receivers: ```typescript // Default split configuration const defaultSplit = await treasurySplitter.read.defaultSplit(); // Token-specific split (overrides default if set) const tokenSplit = await treasurySplitter.read.tokenSplits([tokenAddress]); // Returns: { receivers: address[], proportions: uint256[] } ``` ### Monitor Governance Changes Insurance parameter changes require dual signatures (Curator + DAO): ```typescript const proposals = await treasurySplitter.read.activeProposals(); for (const proposal of proposals) { console.log("Pending proposal:", proposal); } ``` ## Insurance Health Summary Build a quick health check across all pools: ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk"; import { erc20Abi, getContract } from "viem"; async function checkInsuranceHealth(sdk: GearboxSDK) { const splitter = getContract({ address: treasurySplitterAddress, abi: iTreasurySplitterAbi, client: publicClient, }); for (const market of sdk.marketRegister.markets) { const token = market.pool.underlying; const target = await splitter.read.tokenInsuranceAmount([token.address]); const balance = await publicClient.readContract({ address: token.address, abi: erc20Abi, functionName: "balanceOf", args: [treasurySplitterAddress], }); const ratio = target > 0n ? Number(balance * 10000n / target) / 100 : 0; console.log(`${token.symbol}: ${ratio.toFixed(1)}% funded (${balance} / ${target})`); } } ``` ## Key Points | Aspect | Detail | |---|---| | **Buffer type** | Liquid underlying tokens held in TreasurySplitter | | **Distribution rule** | Surplus above insurance target is distributable | | **Bad debt coverage** | Pool burns Treasury LP shares to absorb losses | | **Governance** | Insurance amount changes require dual Curator + DAO approval | ## Next Steps - [Markets Data](https://docs.gearbox.finance/developers/gm-markets-data) -- Query pool and credit manager state - [Pool Operations](https://docs.gearbox.finance/developers/gm-markets-pools) -- Deposit and withdraw liquidity - [Interest Rates & Quotas](https://docs.gearbox.finance/developers/gm-markets-rates) -- Rate models and quota limits ## Updating Quotas Source: https://docs.gearbox.finance/developers/updating-quotas File: content/developers/updating-quotas.mdx Enable or adjust exposure to quota-based collateral tokens. > For Solidity implementation, see [Updating Quotas](https://docs.gearbox.finance/developers/multicalls). ## Why You update quotas when: * **Enabling a quota token** - Required before that token counts as collateral * **Increasing exposure** - Need more of a token to count toward health factor * **Reducing exposure** - Lower quota to reduce quota interest costs * **Closing positions** - Zero quotas before full debt repayment Quotas control how much of a token's value counts as collateral. Without a quota, even holding a quota token contributes zero to your health factor. ## What `updateQuota` changes your quota for a specific token: 1. If increasing from zero, the token is enabled as collateral 2. If decreasing to zero, the token is disabled as collateral 3. Quota increase may be limited by global capacity (per-pool limits) 4. Quota interest accrues based on your quota amount **Key parameters:** * `token` - The quota token address * `quotaChange` - Delta to apply (positive = increase, negative = decrease) * `minQuota` - Minimum acceptable resulting quota (prevents partial fills) The `minQuota` parameter protects you: if the pool can only give you 80% of your requested quota, and you set `minQuota` to 100% of your request, the transaction reverts instead of accepting partial quota. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Request 50,000 USDC worth of quota const quotaAmount = 50_000n * 10n ** 6n; const calls = [ service.prepareUpdateQuota(wethAddress, quotaAmount, quotaAmount), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Decrease Quota ```typescript // Decrease quota by 20,000 (negative change) const decrease = -20_000n * 10n ** 6n; const calls = [ service.prepareUpdateQuota(wethAddress, decrease, 0n), ]; ``` ### Zero Quota Entirely Pass `type(int96).min` to disable quota completely: ```typescript // int96 minimum value const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), ]; ``` ### Common Pattern: Enable Quota After Swap After swapping into a quota token, you need to enable quota for it to count: ```typescript const calls = [ // Swap USDC to WETH via adapter { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // Enable quota for the received WETH service.prepareUpdateQuota(wethAddress, quotaAmount, quotaAmount), ]; ``` ## Gotchas ### Check Quota Limits Before Requesting Each quota token has a pool-wide limit. If the limit is reached, your request fails (or gets partial fill): ```typescript // Check available quota capacity const quotaKeeper = market.quotaKeeper; const quotaInfo = await quotaKeeper.read.getQuotaInfo([wethAddress]); const available = quotaInfo.limit - quotaInfo.totalQuoted; if (requested > available) { console.log(`Only ${available} quota available, requested ${requested}`); } ``` ### minQuota Prevents Partial Fills If you need exactly 100 units of quota: ```typescript // SAFE - reverts if less than 100 available service.prepareUpdateQuota(token, 100n, 100n); // RISKY - accepts partial fill service.prepareUpdateQuota(token, 100n, 0n); ``` ### Per-Account Quota Maximum Each account has an implicit max quota of `8 * maxDebt` per asset. You cannot exceed this even if pool capacity exists. ### Zero Quotas Before Zero Debt You cannot have active quotas with zero debt. When closing an account: ```typescript const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ // Zero ALL quotas first service.prepareUpdateQuota(token1, INT96_MIN, 0n), service.prepareUpdateQuota(token2, INT96_MIN, 0n), // Then repay debt service.prepareDecreaseDebt(MAX_UINT256), ]; ``` ### Cannot Update Quotas on Zero-Debt Account If your account has zero debt, quota updates fail. You must have active debt to hold quotas. ### Quota Tokens vs Non-Quota Tokens Not all tokens are quota tokens. Non-quota tokens: * Are enabled/disabled via `enableToken`/`disableToken` * Don't require quota to count as collateral * Have different risk parameters Check if a token is quota-based by examining the Credit Manager configuration. ### Forbidden Tokens Block Quota Increases If your account has forbidden tokens enabled, you cannot increase any quotas. Disable forbidden tokens first. ## See Also * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Quotas require active debt * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Often combined with quota updates * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - For non-quota tokens ## Withdrawing Collateral Source: https://docs.gearbox.finance/developers/withdrawing-collateral File: content/developers/withdrawing-collateral.mdx Remove tokens from your Credit Account. > For Solidity implementation, see [Withdrawing Collateral](https://docs.gearbox.finance/developers/multicalls). ## Why You withdraw collateral when: * **Taking profit** - Extract gains while keeping the position open * **Rebalancing** - Move assets between Credit Account and wallet * **Closing positions** - Extract remaining value after repaying debt * **Emergency exit** - Quickly reduce exposure Withdrawals decrease your health factor since you're removing value from the account. ## What `withdrawCollateral` transfers tokens from Credit Account to a specified address: 1. Token is transferred from Credit Account to `to` address 2. If balance goes to zero, token is auto-disabled 3. **Safe pricing** is triggered for the final collateral check **Safe pricing** is critical to understand: when any withdrawal occurs in a multicall, the final health check uses `min(mainPrice, reservePrice)` for ALL collateral. This can cause withdrawals to fail even when the account looks healthy based on main prices alone. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Withdraw 5,000 USDC to your wallet const calls = [ service.prepareWithdrawCollateral( usdcAddress, 5_000n * 10n ** 6n, myWalletAddress, ), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Withdraw Entire Balance Pass max uint256 to withdraw all of a token: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ service.prepareWithdrawCollateral(usdcAddress, MAX_UINT256, myWalletAddress), ]; ``` ### Withdraw to Different Address The `to` parameter can be any address: ```typescript const calls = [ service.prepareWithdrawCollateral( usdcAddress, amount, recipientAddress, // Can be different from caller ), ]; ``` ### Common Pattern: Repay + Withdraw After repaying debt, withdraw remaining funds: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quotas service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), // Repay all debt service.prepareDecreaseDebt(MAX_UINT256), // Withdraw remaining collateral service.prepareWithdrawCollateral(usdcAddress, MAX_UINT256, myWalletAddress), service.prepareWithdrawCollateral(wethAddress, MAX_UINT256, myWalletAddress), ]; ``` ## Gotchas ### Safe Pricing Can Block Withdrawals This is the biggest surprise for developers. When you withdraw, ALL collateral is valued at `min(mainPrice, reservePrice)`: ``` Regular check: collateral valued at main price Withdrawal: collateral valued at min(main, reserve) ``` An account that looks healthy at main prices may fail the withdrawal check at safe prices. **Example:** * Main price: $100 * Reserve price: $80 * Regular health check uses $100 * Withdrawal health check uses $80 Your account might have HF 1.2 normally but only HF 0.96 under safe pricing. **Workaround:** Add extra collateral buffer or reduce debt before withdrawing if you're close to the threshold. ### Forbidden Tokens Block Withdrawals If your account has forbidden tokens enabled, withdrawals are prohibited. You must disable forbidden tokens first (usually by swapping them away). ### Token Auto-Disables at Zero Balance When you withdraw the entire balance of a token: * Non-quota tokens are auto-disabled * Quota tokens remain enabled until quota is zeroed This is usually what you want, but be aware if you're tracking enabled tokens. ### Reserve Price May Be Zero Some tokens have a reserve price of zero (untrusted tokens). Any withdrawal will fail if such tokens are enabled, because their value becomes zero under safe pricing. Check the price feed configuration before withdrawing: ```typescript // If reserve price is 0, safe pricing makes this collateral worthless const priceFeed = await priceOracle.read.priceFeedsRaw([tokenAddress, true]); ``` ### Withdrawal Doesn't Auto-Disable Quota Tokens Unlike non-quota tokens, quota tokens remain enabled even at zero balance. You must explicitly zero the quota: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quota first service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), // Then withdraw service.prepareWithdrawCollateral(wethAddress, MAX_UINT256, myWalletAddress), ]; ``` ### Can't Withdraw Below Minimum Debt After withdrawal, your account must still satisfy debt constraints. If withdrawal would leave you with debt between 0 and `minDebt`, it fails. ## See Also * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Often combined with withdrawals * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Zero quotas before withdrawing quota tokens ## Controlling Slippage Source: https://docs.gearbox.finance/developers/controlling-slippage File: content/developers/controlling-slippage.mdx Protect swaps from sandwich attacks and price movement. > For Solidity implementation, see [Controlling Slippage](https://docs.gearbox.finance/developers/multicalls). ## Why You need slippage protection when: * **Swapping tokens** - DEX prices can move between quote and execution * **Multi-step operations** - Swap + deposit combos need end-to-end protection * **Large trades** - Bigger trades have higher slippage impact * **Protecting against MEV** - Sandwich bots exploit unprotected swaps Without slippage protection, you might receive significantly fewer tokens than expected. Gearbox provides native slippage controls that work across any sequence of operations. ## What Gearbox uses a two-step pattern: 1. **`storeExpectedBalances`** - Record expected minimum balances BEFORE operations 2. **`compareBalances`** - Verify actual balances meet expectations AFTER operations If the final balance is less than expected, the entire multicall reverts. **Why not use DEX slippage parameters?** * Multi-step operations (swap → deposit) can't use single slippage value * Some protocols (ERC4626 vaults) have no built-in slippage protection * Gearbox slippage works at the account level, across any operation sequence ## How ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Calculate minimum expected output with 0.5% slippage tolerance const expectedOutput = 25n * 10n ** 18n; // 25 WETH const slippageBps = 50n; // 0.5% const minExpected = expectedOutput - (expectedOutput * slippageBps / 10000n); const calls = [ // 1. Store expected balance BEFORE swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: wethAddress, amount: minExpected }]], }), }, // 2. Perform the swap { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // 3. Verify slippage AFTER swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, ]; ``` ### Multiple Tokens Check slippage on multiple output tokens: ```typescript const calls = [ // Store expectations for both tokens { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[ { token: wethAddress, amount: minWethExpected }, { token: usdcAddress, amount: minUsdcExpected }, ]], }), }, // Multiple swaps... { /* swap 1 */ }, { /* swap 2 */ }, // Single compare covers all stored expectations { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, ]; ``` ### Negative Deltas (Expected to Spend) You can also specify tokens you expect to decrease: ```typescript // Expect to spend at most 50,000 USDC const maxSpend = 50_000n * 10n ** 6n; const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[ { token: usdcAddress, amount: -maxSpend }, // Negative = expect decrease { token: wethAddress, amount: minWethExpected }, ]], }), }, // ... ]; ``` ## Gotchas ### Keep Slippage Checks Close to Operations Place `storeExpectedBalances` immediately before the first external call, and `compareBalances` immediately after the last: ```typescript // CORRECT - slippage checks wrap the swap tightly const calls = [ service.prepareAddCollateral(usdcAddress, amount), // Internal op storeExpectedBalances, // Right before swap swap, // External call compareBalances, // Right after swap service.prepareUpdateQuota(wethAddress, quota, quota), // Internal op ]; // WRONG - addCollateral between store and compare affects balances const calls = [ storeExpectedBalances, swap, service.prepareAddCollateral(usdcAddress, amount), // Changes balances! compareBalances, // May fail incorrectly ]; ``` ### compareBalances Fails Without storeExpectedBalances Calling `compareBalances` without a prior `storeExpectedBalances` in the same multicall reverts. ### Auto-Compare at Multicall End If you call `storeExpectedBalances` but forget `compareBalances`, the check happens automatically at the end of the multicall. However, it's better to be explicit. ### BalanceDelta Type The `amount` in `BalanceDelta` is the **change** you expect, not the final balance: ```typescript // Current WETH balance: 10 // Swap adds 25 WETH // Final balance: 35 // CORRECT - amount is the delta (change) { token: wethAddress, amount: 25n * 10n ** 18n } // WRONG - this would require 35 WETH increase { token: wethAddress, amount: 35n * 10n ** 18n } ``` ### Slippage Check Works on balanceOf The check reads actual token balances via `balanceOf`. This means: * Internal transfers (like `addCollateral`) affect the balance * Token rebases affect the balance * Any balance change, regardless of source, is included ### Cannot Reuse Stored Balances After `compareBalances` runs, the stored expectations are cleared. For multiple swap sequences, call `storeExpectedBalances` again: ```typescript const calls = [ // First swap storeExpectedBalances1, swap1, compareBalances, // Clears stored values // Second swap needs new store storeExpectedBalances2, // Must call again swap2, compareBalances, ]; ``` ## See Also * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Adapter calls that need slippage protection * [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) - Basic slippage example * [Price Updates](https://docs.gearbox.finance/developers/updating-price-feeds) - Stale prices can cause unexpected slippage ## Making External Calls Source: https://docs.gearbox.finance/developers/making-external-calls File: content/developers/making-external-calls.mdx Interact with external protocols (Uniswap, Curve, Yearn, etc.) from your Credit Account. > For Solidity implementation, see [Making External Calls](https://docs.gearbox.finance/developers/multicalls). ## Why You make external calls when: * **Swapping tokens** - Trade via Uniswap, Curve, or other DEXs * **Depositing to vaults** - Stake in Yearn, Lido, or yield strategies * **Managing LP positions** - Add/remove liquidity on various protocols * **Executing complex strategies** - Chain multiple protocol interactions Credit Accounts interact with external protocols through **adapters** - whitelisted contracts that translate your calls into safe operations. ## What External calls flow through adapters: 1. You encode a call targeting an adapter address 2. Credit Facade routes the call to the adapter 3. Adapter builds the actual calldata for the external protocol 4. Adapter requests token approvals if needed 5. Credit Manager executes the call from the Credit Account 6. Credit Account acts as the "user" from the external protocol's perspective 7. Adapter returns which tokens to enable/disable based on the operation **Key insight:** The Credit Account makes the actual call, so it receives the output tokens directly. You never touch the funds - they stay in the Credit Account. ## How ### Step 1: Get Adapter Address ```typescript import { getContract } from 'viem'; import { creditManagerAbi } from '@gearbox-protocol/sdk'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get adapter for a protocol (e.g., Uniswap V3 Router) const uniswapV3Adapter = await creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); // Returns 0x0 if no adapter exists for this protocol if (uniswapV3Adapter === '0x0000000000000000000000000000000000000000') { throw new Error('No adapter for this protocol'); } ``` ### Step 2: Encode the Adapter Call ```typescript import { encodeFunctionData } from 'viem'; import { uniswapV3AdapterAbi } from '@gearbox-protocol/integrations-v3'; const swapParams = { tokenIn: usdcAddress, tokenOut: wethAddress, fee: 500, recipient: '0x0000000000000000000000000000000000000000', // Adapter overrides this deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), amountIn: 50_000n * 10n ** 6n, amountOutMinimum: 24n * 10n ** 18n, // Slippage protection sqrtPriceLimitX96: 0n, }; const calls = [ { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, ]; ``` ### Complete Example: Swap with Slippage Protection ```typescript import { encodeFunctionData } from 'viem'; import { GearboxSDK, createCreditAccountService, iCreditFacadeV300MulticallAbi, } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Get adapter const uniswapV3Adapter = await market.creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); const calls = [ // Add collateral (SDK helper) service.prepareAddCollateral(usdcAddress, 50_000n * 10n ** 6n), // Borrow (SDK helper) service.prepareIncreaseDebt(200_000n * 10n ** 6n), // Slippage protection start { target: market.creditFacade.address, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: wethAddress, amount: 99n * 10n ** 18n }]], // Expect ~100 WETH }), }, // Swap via adapter (manual encoding) { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // Slippage protection end { target: market.creditFacade.address, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, // Set quota for received token (SDK helper) service.prepareUpdateQuota(wethAddress, 250_000n * 10n ** 6n, 250_000n * 10n ** 6n), ]; // Approve collateral to Credit Manager await usdcContract.write.approve([market.creditManager.address, 50_000n * 10n ** 6n]); // Execute await market.creditFacade.write.openCreditAccount([ownerAddress, calls, 0n]); ``` ### Diff Functions Many adapters have `_diff` variants that operate on "entire balance minus 1": ```typescript // Instead of specifying exact amount... { functionName: 'deposit', args: [exactAmount] } // Use diff to deposit all USDC (minus 1 wei) { functionName: 'depositDiff', args: [1n] } ``` This is useful when you don't know the exact balance after previous operations. ## Gotchas ### Adapter ABIs Need Separate Import SDK exports core ABIs, but adapter ABIs often need separate import: ```typescript // Core ABIs from SDK import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Adapter ABIs from integrations package import { uniswapV3AdapterAbi } from '@gearbox-protocol/integrations-v3'; ``` Check what's available in `@gearbox-protocol/integrations-v3`. ### Not All Protocols Have Adapters An adapter must exist for each protocol you want to interact with. Check with `contractToAdapter`: ```typescript const adapter = await creditManager.read.contractToAdapter([protocolAddress]); if (adapter === '0x0000000000000000000000000000000000000000') { // No adapter - this protocol isn't integrated throw new Error('Protocol not supported'); } ``` ### Recipient Parameter is Overridden Many DEX functions have a `recipient` parameter. Adapters override this to ensure tokens go to the Credit Account, not an arbitrary address: ```typescript // You can pass any address here - adapter ignores it const swapParams = { recipient: '0x0000000000000000000000000000000000000000', // Will be overridden // ... }; ``` ### Always Use Slippage Protection External calls are vulnerable to sandwich attacks. Always wrap swaps with slippage checks: ```typescript const calls = [ storeExpectedBalances, adapterSwapCall, compareBalances, ]; ``` ### Adapter Function Signatures May Differ Adapter functions may have slightly different signatures than the underlying protocol: ```typescript // Uniswap Router: exactInputSingle(params) returns (uint256) // Adapter: exactInputSingle(params) -> also enables output token // Yearn Vault: deposit(amount) returns (shares) // Adapter: depositDiff(leftoverAmount) -> deposits all balance minus leftoverAmount ``` Read the adapter interface documentation for exact signatures. ### Token Enable/Disable is Automatic After adapter calls, tokens are automatically enabled/disabled based on balance changes: * Balance goes from 0 to non-zero: Token enabled * Balance goes from non-zero to 0: Token disabled You usually don't need manual `enableToken`/`disableToken` after adapter calls. ## See Also * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protect your swaps * [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) - Combining SDK helpers with manual encoding * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Manual token management ## Enabling/Disabling Tokens Source: https://docs.gearbox.finance/developers/enabling-disabling-tokens File: content/developers/enabling-disabling-tokens.mdx Explicitly manage which tokens count as collateral. > For Solidity implementation, see [Enabling and Disabling Tokens](https://docs.gearbox.finance/developers/multicalls). ## Why You manually enable/disable tokens when: * **Direct transfers** - Tokens sent directly to Credit Account aren't auto-enabled * **Gas optimization** - Disable unused tokens to reduce collateral check cost * **Risk management** - Prevent certain tokens from counting in health factor * **Edge cases** - Override automatic enable/disable behavior Most of the time you don't need this - tokens auto-enable/disable based on balance changes. But sometimes manual control is necessary. ## What Non-quoted tokens have automatic enable/disable behavior: | Balance Change | Action | | -------------- | ------------ | | 0/1 to > 1 | Auto-enable | | > 1 to 0/1 | Auto-disable | `enableToken` and `disableToken` let you override this when needed. **Important:** These functions only work on **non-quoted tokens**. Quota tokens can only be enabled/disabled via `updateQuota`. ## How ### Enable a Token ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'enableToken', args: [tokenAddress], }), }, ]; ``` ### Disable a Token ```typescript const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'disableToken', args: [tokenAddress], }), }, ]; ``` ### Enable Token After Direct Transfer If someone sends tokens directly to your Credit Account: ```typescript // Token was transferred directly to Credit Account // It won't count as collateral until enabled const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'enableToken', args: [directlyTransferredToken], }), }, ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Disable Unused Tokens to Save Gas Each enabled token requires a price oracle call during collateral check. Disable tokens with zero balance: ```typescript // Check which tokens have zero balance const accountData = await service.getCreditAccountData(creditAccountAddress); const tokensToDisable = accountData.tokens .filter(t => t.balance <= 1n && t.isEnabled) .map(t => t.token); const calls = tokensToDisable.map(token => ({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'disableToken', args: [token], }), })); ``` ## Gotchas ### No-Op for Quota Tokens Calling `enableToken` or `disableToken` on a quota token does nothing: ```typescript // This does nothing - quota tokens use updateQuota { functionName: 'enableToken', args: [quotaTokenAddress], // No effect } // Use this instead for quota tokens service.prepareUpdateQuota(quotaTokenAddress, quotaAmount, minQuota); ``` ### Cannot Enable Forbidden Tokens Some tokens are marked as "forbidden" and cannot be enabled: ```typescript // This will revert { functionName: 'enableToken', args: [forbiddenTokenAddress], // Reverts! } ``` Forbidden tokens must be swapped away, not disabled. ### Auto-Enable Usually Works Adapter calls and standard operations auto-enable tokens when balance increases: ```typescript // This swap auto-enables WETH { target: uniswapAdapter, callData: encodeSwap(/* USDC → WETH */), } // No need to call enableToken(wethAddress) after ``` You only need manual enable when: * Tokens are transferred directly to Credit Account (not via adapter) * You want to enable a zero-balance token preemptively ### Max Enabled Tokens Limit Each Credit Manager has a maximum number of enabled tokens per account. Exceeding this reverts the multicall: ```typescript // Check the limit const maxTokens = await creditManager.read.maxEnabledTokens(); // Count currently enabled tokens const enabledCount = accountData.tokens.filter(t => t.isEnabled).length; if (enabledCount >= maxTokens) { // Must disable some tokens before enabling new ones } ``` ### Disabled Tokens Still on Account Disabling a token doesn't remove it from the account - it just excludes it from health factor calculation. The balance remains: ```typescript // Token is disabled but balance stays disableToken(wethAddress); // Balance is still there, just not counted as collateral // Liquidators can claim disabled token balances as bonus! ``` **Warning:** Don't keep significant value in disabled tokens. During liquidation, liquidators can withdraw disabled tokens on top of their normal premium. ### Balance of 1 is "Zero" Gearbox treats balance of 0 and 1 the same (due to ERC20 rounding issues). Auto-disable triggers at balance <= 1: ```typescript // These are equivalent from Gearbox perspective balance = 0n // Disabled balance = 1n // Also disabled // This is enabled balance = 2n ``` ## See Also * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - How to enable/disable quota tokens * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Auto-enable behavior with adapters * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Optimize checks for enabled tokens ## Credit Accounts Source: https://docs.gearbox.finance/developers/gm-accounts File: content/developers/gm-accounts.mdx Query and manage Gearbox credit accounts using the SDK. Credit accounts are isolated smart contracts that hold collateral and debt positions on behalf of users. ## Creating a Service Use `createCreditAccountService` for credit account operations: ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Create service (310 = V3.1) const service = createCreditAccountService(sdk, 310); ``` ## Querying Credit Accounts ### Get All Accounts for a Credit Manager ```typescript const accounts = await service.getCreditAccounts( { creditManager: cmAddress }, sdk.currentBlock ); for (const account of accounts) { console.log(`Account: ${account.addr}`); console.log(` Owner: ${account.owner}`); console.log(` Debt: ${account.debt}`); console.log(` Health Factor: ${account.healthFactor}`); } ``` ### Filter by Owner ```typescript const myAccounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: myAddress, }, sdk.currentBlock ); ``` ### Account Data Structure Each credit account includes: | Field | Type | Description | | ---------------- | ------------- | --------------------------------- | | `addr` | `address` | Credit Account contract address | | `owner` | `address` | Account owner | | `creditManager` | `address` | Parent Credit Manager | | `debt` | `uint256` | Total debt (principal + interest) | | `healthFactor` | `uint256` | Current HF (10000 = 1.0) | | `tokens` | `TokenInfo[]` | Token balances and values | | `isLiquidatable` | `boolean` | Whether account can be liquidated | ## Reading Account State ### Health Factor ```typescript const account = accounts[0]; // Health factor is scaled by 10000 (10000 = 1.0) const hf = Number(account.healthFactor) / 10000; console.log(`Health Factor: ${hf.toFixed(4)}`); if (account.isLiquidatable) { console.log('Account is liquidatable!'); } ``` ### Token Balances ```typescript for (const token of account.tokens) { console.log(`${token.symbol}:`); console.log(` Balance: ${token.balance}`); console.log(` Value (underlying): ${token.balanceInUnderlying}`); console.log(` LT: ${token.lt / 100}%`); } ``` ### Debt Breakdown ```typescript console.log(`Total Debt: ${account.debt}`); console.log(`Principal: ${account.borrowedAmount}`); console.log(`Accrued Interest: ${account.cumulativeQuotaInterest}`); console.log(`Quota Fees: ${account.quotaFees}`); ``` ## Market Discovery Find the market for a credit manager: ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); const creditFacade = market.creditFacade; console.log(`Credit Facade: ${creditFacade.address}`); console.log(`Pool: ${market.pool.address}`); ``` ## Opening a Credit Account ```typescript // Build multicall with SDK helpers const calls = [ service.prepareAddCollateral(usdcAddress, 10000n * 10n ** 6n), service.prepareIncreaseDebt(40000n * 10n ** 6n), ]; // Get credit facade const market = sdk.marketRegister.findByCreditManager(cmAddress); // Open account const hash = await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ## Closing a Credit Account ```typescript // Build close multicall - typically repay and withdraw const closeCalls = [ service.prepareDecreaseDebt(account.debt), // Repay all debt // Withdraw remaining collateral handled automatically ]; const hash = await market.creditFacade.write.closeCreditAccount([ account.addr, closeCalls, ]); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function queryAccounts(cmAddress: `0x${string}`) { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const service = createCreditAccountService(sdk, 310); // Get all accounts const accounts = await service.getCreditAccounts( { creditManager: cmAddress }, sdk.currentBlock ); console.log(`Found ${accounts.length} credit accounts\n`); for (const account of accounts) { const hf = Number(account.healthFactor) / 10000; console.log(`Account: ${account.addr}`); console.log(` Owner: ${account.owner}`); console.log(` Debt: ${account.debt}`); console.log(` Health Factor: ${hf.toFixed(4)}`); console.log(` Liquidatable: ${account.isLiquidatable}`); // Token breakdown console.log(` Tokens:`); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}: ${token.balance}`); } } console.log(''); } } queryAccounts('0x...').catch(console.error); ``` ## Learn More * [Account Operations](https://docs.gearbox.finance/developers/gm-accounts-ops) - Reference table of all operations * [KYC'd Accounts](https://docs.gearbox.finance/developers/gm-accounts-kyc) - Permissioned credit accounts * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Build complex operations * [Liquidations](https://docs.gearbox.finance/developers/gm-liquidations) - Liquidation mechanics ## Positions (Strategies) Source: https://docs.gearbox.finance/developers/gm-positions File: content/developers/gm-positions.mdx This section covers opening and managing leveraged positions via Credit Accounts. A "position" in Gearbox is a Credit Account with active debt and collateral — it is the fundamental unit of leveraged activity in the protocol. ## Overview To enter a strategy, you open a Credit Account, deposit collateral, borrow from a Pool, and deploy capital through whitelisted DeFi adapters. The Credit Manager checks solvency after every operation. When you are done, you close the position by repaying debt and withdrawing remaining collateral. The position lifecycle is: **open, adjust, monitor, close**. ## What's in This Section | Page | Covers | | --- | --- | | [Account Operations](https://docs.gearbox.finance/developers/gm-accounts-ops) | Open, adjust, and close Credit Account positions | | [Positions](https://docs.gearbox.finance/developers/gm-accounts) | Query active positions, health factor, collateral breakdown | | [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) | Composable batched operations within a single transaction | | [Add Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) | Multicall operation: add collateral tokens | | [Manage Debt](https://docs.gearbox.finance/developers/gm-mc-debt) | Multicall operation: increase or decrease debt | | [Withdraw](https://docs.gearbox.finance/developers/gm-mc-withdraw) | Multicall operation: withdraw collateral | | [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) | Multicall operation: interact with external DeFi protocols | | [Manage Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) | Multicall operation: update quota allocations | | [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) | Multicall operation: enable/disable collateral tokens | | [Safety & Slippage](https://docs.gearbox.finance/developers/gm-mc-safety) | Multicall operation: slippage protection and health checks | | [Liquidations](https://docs.gearbox.finance/developers/gm-liquidations) | How liquidations work, thresholds, and partial liquidations | | [Bot System](https://docs.gearbox.finance/developers/gm-bots) | Automated position management, keeper bots, and monitoring | ## SDK Entry Points The primary namespaces for position management: ```ts // Open a position const tx = await sdk.positions.prepareOpen({ strategy, amount, leverage }); const preview = await sdk.positions.previewOpen(tx); await sdk.transactions.execute(tx); // Check position status const status = await sdk.positions.getStatus(positionId); // Adjust an existing position const adjustTx = await sdk.positions.prepareAdjust({ position, changes }); const adjustPreview = await sdk.positions.previewAdjust(adjustTx); await sdk.transactions.execute(adjustTx); // Close a position const closeTx = await sdk.positions.prepareClose({ position }); const closePreview = await sdk.positions.previewClose(closeTx); await sdk.transactions.execute(closeTx); ``` ## Learn More - [Strategy Opportunities](https://docs.gearbox.finance/developers/gm-opps-strategy) — discover strategies via the unified opportunity search - [Lender (Pools)](https://docs.gearbox.finance/developers/gm-lender) — the lender side of the protocol - [SDK Namespaces](https://docs.gearbox.finance/developers/gm-start-namespaces) — full namespace reference ## Updating Price Feeds Source: https://docs.gearbox.finance/developers/updating-price-feeds File: content/developers/updating-price-feeds.mdx Push fresh price data for on-demand oracles (Pyth, Redstone). > For Solidity implementation, see [Updating On-Demand Price Feeds](https://docs.gearbox.finance/developers/multicalls). ## Why You update price feeds when: * **Using on-demand oracles** - Pyth and Redstone require fresh data with each transaction * **Multicalls fail** - "Stale price" errors indicate missing price updates * **Withdrawals** - Reserve price feeds may also need updates under safe pricing Some tokens use "pull-based" oracles that don't update automatically. You must push fresh price data before operations that need it. ## What `onDemandPriceUpdate` pushes oracle data to the price feed: 1. You obtain signed price data from the oracle provider (off-chain) 2. You include the price update as the FIRST call in your multicall 3. Credit Facade forwards the data to the price feed contract 4. The price feed validates the signature and updates **Critical rule:** All price updates must be at the **beginning** of the calls array. Any `onDemandPriceUpdate` after another call type will revert. ## How ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Get price data from oracle provider (e.g., Pyth) const priceData = await pythClient.getPriceUpdateData([feedId]); const calls = [ // Price update MUST be first { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [ tokenAddress, // Token to update price for false, // reserve: false = main feed, true = reserve feed priceData, // Signed price data from oracle ], }), }, // Now other operations service.prepareAddCollateral(usdcAddress, amount), service.prepareIncreaseDebt(debtAmount), ]; ``` ### Multiple Price Updates Update several tokens at once (all must be at the start): ```typescript const calls = [ // All price updates first { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [token1, false, priceData1], }), }, { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [token2, false, priceData2], }), }, // Then other operations // ... ]; ``` ### Updating Reserve Feed (For Withdrawals) Withdrawals trigger safe pricing, which uses both main and reserve feeds: ```typescript const calls = [ // Main feed update { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [tokenAddress, false, mainPriceData], // reserve = false }), }, // Reserve feed update { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [tokenAddress, true, reservePriceData], // reserve = true }), }, // Now the withdrawal will work with safe pricing service.prepareWithdrawCollateral(otherToken, amount, recipient), ]; ``` ### Detecting Which Feeds Need Updates ```typescript import { priceFeedCompressorAbi } from '@gearbox-protocol/sdk'; const feedInfo = await client.readContract({ address: priceFeedCompressorAddress, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [creditManagerAddress], }); // feedInfo contains tokens that need on-demand updates const tokensNeedingUpdate = feedInfo.filter(f => f.needsUpdate); ``` ## Gotchas ### Price Updates MUST Be First This is the most common mistake. Price updates after any other call type revert: ```typescript // WRONG - price update after addCollateral const calls = [ service.prepareAddCollateral(token, amount), onDemandPriceUpdate, // Reverts! ]; // CORRECT - price update first const calls = [ onDemandPriceUpdate, service.prepareAddCollateral(token, amount), ]; ``` ### Fresh Data Required Price data has a short validity window (usually a few minutes). Generate fresh data right before the transaction: ```typescript // Get price data immediately before building transaction const priceData = await pythClient.getPriceUpdateData([feedId]); // Use it right away const calls = [ onDemandPriceUpdate(token, false, priceData), // ... ]; // DON'T cache price data for later ``` ### Not All Tokens Need Updates Only tokens with on-demand price feeds need updates. Tokens using Chainlink or other push-based oracles don't need `onDemandPriceUpdate`: ```typescript // Check if token uses on-demand feed const priceFeed = await priceOracle.read.priceFeedsRaw([tokenAddress, false]); const feedType = priceFeed.feedType; // Only PYTH and REDSTONE feeds need updates if (feedType === 'PYTH' || feedType === 'REDSTONE') { // Include price update } ``` ### Disabled Tokens Don't Need Updates If a token will be disabled by the end of the multicall, you don't need to update its price: ```typescript // weth is getting swapped entirely (will be disabled) const calls = [ // No need for WETH price update since it's being disabled service.prepareAddCollateral(usdcAddress, amount), adapterSwap(weth, usdc, entireBalance), // WETH will auto-disable after swap ]; ``` ### Off-Chain Data Retrieval You need to fetch price data from the oracle's API before building your transaction. This is protocol-specific: ```typescript // Pyth example const pythConnection = new PriceServiceConnection("https://hermes.pyth.network"); const priceUpdateData = await pythConnection.getPriceFeedsUpdateData([feedId]); // Redstone example (simplified) const redstonePayload = await getRedstonePayload([tokenSymbol]); ``` Your contract or frontend must handle this off-chain step. ### Contracts Need Price Data Input If you're building a contract that interacts with Gearbox, it must accept price data as an input parameter: ```typescript // Your contract function signature function executeWithGearbox( bytes[] calldata priceUpdates, // Must be passed from frontend // other params ) external { // Build multicall with price updates first } ``` Contracts cannot fetch price data themselves - it must come from off-chain. ## See Also * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Stale prices can cause slippage issues * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - May need reserve feed updates * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Related to price feed behavior ## Account Operations Source: https://docs.gearbox.finance/developers/gm-accounts-ops File: content/developers/gm-accounts-ops.mdx A reference of all operations available on credit accounts. Each operation can be included as a call within a multicall. ## Quick Reference | Operation | SDK Helper | When to Use | Guide | | -------------------- | ----------------------------- | ------------------------------------------ | ---------------------------------------------------- | | Add Collateral | `prepareAddCollateral()` | Deposit tokens to increase health factor | [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) | | Increase Debt | `prepareIncreaseDebt()` | Borrow from pool | [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) | | Decrease Debt | `prepareDecreaseDebt()` | Repay borrowed funds | [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) | | Update Quota | `prepareUpdateQuota()` | Enable/adjust quota token exposure | [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) | | Withdraw Collateral | `prepareWithdrawCollateral()` | Remove tokens from account | [Withdrawing Collateral](https://docs.gearbox.finance/developers/gm-mc-withdraw) | | Slippage Control | Manual encoding | Protect swaps from sandwich attacks | [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) | | External Calls | Manual encoding | Interact with Uniswap, Curve, etc. | [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) | | Enable/Disable Token | Manual encoding | Explicit collateral management | [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) | | Price Updates | Manual encoding | Update Pyth/Redstone feeds | [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) | | Check Params | Manual encoding | Optimize gas, set min health factor | [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) | | Revoke Allowances | Manual encoding | Security measure after suspicious activity | [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) | ## SDK Helpers vs Manual Encoding **Five operations have SDK helpers** via `createCreditAccountService`: * `prepareAddCollateral(token, amount)` * `prepareIncreaseDebt(amount)` * `prepareDecreaseDebt(amount)` * `prepareUpdateQuota(token, change, minQuota)` * `prepareWithdrawCollateral(token, amount, to)` **Six operations require manual encoding** with viem's `encodeFunctionData`: * `storeExpectedBalances` / `compareBalances` * `enableToken` / `disableToken` * `onDemandPriceUpdate` * `setFullCheckParams` * `revokeAdapterAllowances` All manual encoding uses `iCreditFacadeV300MulticallAbi` from `@gearbox-protocol/sdk`. ## Page Structure Each operation guide covers: 1. **Why** - When you need this operation 2. **What** - What it does and how it fits the system 3. **How** - Working TypeScript code 4. **Gotchas** - Common mistakes and edge cases ## Learn More * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - How to build and execute multicalls * [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) - Account data and services ## Collateral Check Params Source: https://docs.gearbox.finance/developers/collateral-check-params File: content/developers/collateral-check-params.mdx Optimize gas and set minimum health factor for collateral checks. > For Solidity implementation, see [Setting Collateral Check Params](https://docs.gearbox.finance/developers/multicalls). ## Why You set collateral check params when: * **Optimizing gas** - Hint which tokens cover the debt to skip unnecessary oracle calls * **Risk management** - Enforce a minimum health factor above 1.0 * **Large accounts** - Many enabled tokens make default checks expensive * **Automated systems** - Bots can benefit from consistent gas costs The collateral check iterates through enabled tokens, summing value until it exceeds debt. Hints tell it which tokens to check first, potentially skipping expensive oracle calls. ## What `setFullCheckParams` configures two things: 1. **Collateral hints** - Token masks to prioritize during the check 2. **Min health factor** - Minimum acceptable HF (in basis points, 10000 = 1.0) If you know your USDC and WETH cover the debt, pass their masks as hints. The check evaluates them first and may stop early without checking other tokens. ## How ### Basic Usage with Hints ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi, creditManagerAbi } from '@gearbox-protocol/sdk'; import { getContract } from 'viem'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get token masks (each token has a unique bitmask) const usdcMask = await creditManager.read.getTokenMaskOrRevert([usdcAddress]); const wethMask = await creditManager.read.getTokenMaskOrRevert([wethAddress]); const calls = [ // Set hints at the start of multicall { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'setFullCheckParams', args: [ [usdcMask, wethMask], // Check these tokens first 10000, // minHealthFactor: 1.0 (10000 bps) ], }), }, // Rest of your multicall service.prepareAddCollateral(usdcAddress, amount), // ... ]; ``` ### Setting Higher Min Health Factor Require account to maintain at least 1.2 HF: ```typescript const MIN_HF_120 = 12000; // 1.2 in basis points const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'setFullCheckParams', args: [ [], // No hints MIN_HF_120, ], }), }, // Operations... ]; ``` ### Complete Example: Gas-Optimized Multicall ```typescript import { encodeFunctionData, getContract } from 'viem'; import { GearboxSDK, createCreditAccountService, iCreditFacadeV300MulticallAbi, creditManagerAbi, } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); const creditManager = getContract({ address: market.creditManager.address, abi: creditManagerAbi, client: publicClient, }); // Get masks for tokens that will cover the debt const usdcMask = await creditManager.read.getTokenMaskOrRevert([usdcAddress]); const calls = [ // Hints first - USDC will cover most of debt { target: market.creditFacade.address, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'setFullCheckParams', args: [ [usdcMask], // USDC covers debt, check it first 10500, // Require 1.05 HF minimum ], }), }, service.prepareAddCollateral(usdcAddress, 100_000n * 10n ** 6n), service.prepareIncreaseDebt(200_000n * 10n ** 6n), // Swap some USDC to WETH { target: uniswapAdapter, callData: encodeSwap(/* ... */), }, ]; ``` ## Gotchas ### Masks, Not Addresses The hints array takes token **masks**, not addresses: ```typescript // WRONG - passing addresses args: [[usdcAddress, wethAddress], 10000] // CORRECT - passing masks const usdcMask = await creditManager.read.getTokenMaskOrRevert([usdcAddress]); args: [[usdcMask], 10000] ``` ### Hints Are Optimization, Not Guarantee The check still validates ALL enabled tokens - hints just change the order. If hints don't cover the debt, it continues with remaining tokens. ```typescript // If USDC hint doesn't cover debt, WETH and other tokens are still checked // Hints just potentially skip some oracle calls ``` ### Min Health Factor Must Be >= 10000 You cannot set a health factor below 1.0: ```typescript // WRONG - less than 10000 reverts args: [[], 9500] // Reverts! // CORRECT - must be >= 10000 args: [[], 10000] // Exactly 1.0 args: [[], 11000] // 1.1 ``` ### Hints Don't Help Small Accounts For accounts with few enabled tokens (< 5), hints add gas overhead without saving much. Only use for accounts with many tokens. ### Order Matters in Hints Array Tokens are checked in the order you provide: ```typescript // Check WETH first, then USDC args: [[wethMask, usdcMask], 10000] // Check USDC first, then WETH args: [[usdcMask, wethMask], 10000] ``` Put your highest-value collateral first for best gas savings. ### Each Check Calls Oracle Once Without hints, the check iterates through all enabled tokens by mask order until TWV >= debt. With hints: 1. Check hinted tokens first 2. If TWV >= debt, stop early 3. If not, continue with remaining tokens Best case: hints cover debt, skip other oracle calls. Worst case: hints don't help, all tokens checked anyway. ### Params Reset After Multicall `setFullCheckParams` only affects the current multicall's final check. Next multicall uses defaults again. ### Computing Token Masks Token masks are powers of 2, assigned sequentially when tokens are added to the Credit Manager: ```typescript // First token: mask = 1 (2^0) // Second token: mask = 2 (2^1) // Third token: mask = 4 (2^2) // etc. // Always use getTokenMaskOrRevert to get the correct mask const mask = await creditManager.read.getTokenMaskOrRevert([tokenAddress]); ``` ### Can Combine with Other Params Use hints for gas optimization AND min HF for risk management: ```typescript args: [ [primaryCollateralMask, secondaryCollateralMask], // Gas optimization 11000, // Risk management: require 1.1 HF ] ``` ## See Also * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Affects which tokens are checked * [Price Updates](https://docs.gearbox.finance/developers/updating-price-feeds) - Oracle calls that hints can skip * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Debt determines what TWV must cover ## KYC'd Accounts Source: https://docs.gearbox.finance/developers/gm-accounts-kyc File: content/developers/gm-accounts-kyc.mdx Some Gearbox credit markets require identity verification before users can open or manage credit accounts. These permissioned markets use on-chain access control to restrict operations to whitelisted addresses. ## How It Works Permissioned credit managers use a **whitelist contract** that gates access to core operations. When KYC is enabled on a market: * Only whitelisted addresses can call `openCreditAccount` * Existing account owners retain access to manage their positions * Liquidations remain permissionless (anyone can liquidate unhealthy accounts) ## Checking Access Before attempting to open an account on a permissioned market, verify that your address is whitelisted: ```typescript import { getContract } from 'viem'; import { creditFacadeV3Abi } from '@gearbox-protocol/sdk'; const creditFacade = getContract({ address: facadeAddress, abi: creditFacadeV3Abi, client: publicClient, }); // Check if the facade has access restrictions const isDegenMode = await creditFacade.read.degenNFT(); const hasExpiration = await creditFacade.read.expirationDate(); // If degenNFT is set, the market requires a special NFT for access if (isDegenMode !== '0x0000000000000000000000000000000000000000') { console.log('This market requires a DegenNFT for access'); } ``` ## DegenNFT Gating The most common permissioning mechanism is the **DegenNFT**. Markets configured with a DegenNFT address require callers to hold that NFT to open accounts: * Each NFT grants a limited number of account opens * NFTs are distributed through governance or KYC providers * The NFT balance is decremented on each `openCreditAccount` call ```typescript import { erc721Abi } from 'viem'; const degenNFT = getContract({ address: degenNFTAddress, abi: erc721Abi, client: publicClient, }); const balance = await degenNFT.read.balanceOf([myAddress]); console.log(`Available account opens: ${balance}`); ``` ## Implications for Developers When building integrations against permissioned markets: * **Check access first** - Query the whitelist or NFT balance before constructing transactions * **Handle rejections gracefully** - Provide clear error messages when access is denied * **Liquidation bots work normally** - No KYC is needed for liquidation operations * **Multicalls are unaffected** - Once an account is open, all multicall operations work identically to permissionless markets ## Learn More * [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) - General account management * [Account Operations](https://docs.gearbox.finance/developers/gm-accounts-ops) - Full operation reference * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Building multicalls ## Positions Source: https://docs.gearbox.finance/developers/gm-accounts-positions File: content/developers/gm-accounts-positions.mdx Manage positions — open, adjust, close, and monitor — using the SDK vNext. The SDK builds transactions but never signs them. Your agent or wallet client handles signing and submission. ## Transaction Lifecycle Every position action follows the same five-step flow: | Step | Method | What Happens | |---|---|---| | **Prepare** | `prepareDeposit()` / `prepareOpen()` | Build a `RawTx` with calldata | | **Preview** | `previewDeposit()` / `previewOpen()` | Simulate the transaction against current state | | **Validate** | Check preview result | Verify HF, slippage, warnings | | **Execute** | `walletClient.sendTransaction(rawTx)` | Sign and submit on-chain | | **Monitor** | `sdk.accounts.getStatus()` | Track position health post-execution | ## The RawTx Type All prepare methods return a `RawTx` — a ready-to-sign transaction object: ```typescript interface RawTx { to: `0x${string}`; // Target contract address calldata: `0x${string}`; // Encoded function call value: bigint; // ETH value (usually 0n) } ``` The SDK builds, never signs. You submit the transaction using your wallet client: ```typescript const txHash = await walletClient.sendTransaction({ to: rawTx.to, data: rawTx.calldata, value: rawTx.value, }); ``` ## Depositing into a Pool ### Prepare a Deposit ```typescript import { GearboxSDK } from "@gearbox-protocol/sdk/official"; const sdk = await GearboxSDK.init({ chains: [1], startFromCache: true, }); const rawTx = await sdk.positions.prepareDeposit({ pool: poolAddress, chainId: 1, amount: 10_000n * 10n ** 6n, // 10,000 USDC receiver: walletAddress, }); ``` ### Preview a Deposit Before submitting, simulate to verify expected outcomes: ```typescript const preview = await sdk.positions.previewDeposit({ pool: poolAddress, chainId: 1, amount: 10_000n * 10n ** 6n, receiver: walletAddress, }); console.log(`Shares received: ${preview.sharesReceived}`); console.log(`Share price: ${preview.sharePrice}`); console.log(`Breakeven: ${preview.breakevenDays} days`); console.log(`Warnings: ${preview.warnings.join(", ") || "none"}`); ``` ### Full Deposit Flow ```typescript // 1. Prepare const rawTx = await sdk.positions.prepareDeposit({ pool: poolAddress, chainId: 1, amount: depositAmount, receiver: walletAddress, }); // 2. Preview const preview = await sdk.positions.previewDeposit({ pool: poolAddress, chainId: 1, amount: depositAmount, receiver: walletAddress, }); // 3. Validate if (!preview.success || preview.warnings.length > 0) { console.error("Deposit preview failed:", preview.warnings); return; } // 4. Execute (SDK builds, you sign) const txHash = await walletClient.sendTransaction({ to: rawTx.to, data: rawTx.calldata, value: rawTx.value, }); await publicClient.waitForTransactionReceipt({ hash: txHash }); ``` ## Opening a Leveraged Position ### Prepare an Open ```typescript const rawTx = await sdk.positions.prepareOpen({ creditManager: cmAddress, chainId: 1, collateralToken: wstETHAddress, collateralAmount: 10n * 10n ** 18n, // 10 wstETH targetLeverage: 3.0, }); ``` ### Preview an Open ```typescript const preview = await sdk.positions.previewOpen({ creditManager: cmAddress, chainId: 1, collateralToken: wstETHAddress, collateralAmount: 10n * 10n ** 18n, targetLeverage: 3.0, }); console.log(`Health Factor: ${preview.healthFactor}`); console.log(`Net APY: ${preview.netApy}%`); console.log(`Actual leverage: ${preview.actualLeverage}x`); console.log(`Debt: ${preview.debtAmount}`); console.log(`Swap impact: ${preview.swapImpactBps} bps`); ``` ### Full Open Flow ```typescript // 1. Prepare the transaction const rawTx = await sdk.positions.prepareOpen({ creditManager: cmAddress, chainId: 1, collateralToken: wstETHAddress, collateralAmount: collateralAmount, targetLeverage: 3.0, }); // 2. Preview and validate const preview = await sdk.positions.previewOpen({ creditManager: cmAddress, chainId: 1, collateralToken: wstETHAddress, collateralAmount: collateralAmount, targetLeverage: 3.0, }); if (!preview.success) { console.error("Position open failed simulation"); return; } if (preview.healthFactor < 1.5) { console.warn(`Health factor ${preview.healthFactor} is below safety threshold`); return; } if (preview.warnings.length > 0) { console.warn("Warnings:", preview.warnings); } // 3. Execute const txHash = await walletClient.sendTransaction({ to: rawTx.to, data: rawTx.calldata, value: rawTx.value, }); const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); console.log(`Position opened: ${receipt.transactionHash}`); ``` ## Universal Transaction Preview Use `sdk.previewTransaction()` to simulate any raw transaction against current chain state. This works for transactions built outside the SDK as well. ```typescript const result = await sdk.previewTransaction({ to: targetAddress, calldata: encodedCalldata, value: 0n, }, { chainId: 1, from: walletAddress, }); console.log(`Success: ${result.success}`); console.log(`Health factor: ${result.healthFactor}`); console.log(`Actions: ${result.actions.map(a => a.type).join(", ")}`); console.log(`Balance changes:`, result.balanceChanges); console.log(`Warnings: ${result.warnings.join(", ") || "none"}`); ``` ### Preview Result Fields | Field | Type | Description | |---|---|---| | `success` | `boolean` | Whether the simulation succeeded | | `healthFactor` | `number \| null` | Projected health factor after execution | | `actions` | `Action[]` | Decoded actions the transaction performs | | `balanceChanges` | `BalanceChange[]` | Token balance deltas | | `warnings` | `string[]` | Human-readable risk warnings | ## Listing Credit Accounts Retrieve all credit accounts owned by a wallet: ```typescript const accounts = await sdk.accounts.list({ owner: walletAddress, chainId: 1, }); for (const account of accounts) { console.log(`Account: ${account.address}`); console.log(` CM: ${account.creditManager}`); console.log(` Health Factor: ${account.healthFactor}`); console.log(` Total Value: $${account.totalValueUsd}`); } ``` ## Position Health and Status Get detailed status for a specific credit account: ```typescript const status = await sdk.accounts.getStatus({ account: creditAccountAddress, chainId: 1, }); console.log(`Health Factor: ${status.healthFactor}`); console.log(`Leverage: ${status.leverage}x`); console.log(`Total Value: $${status.totalValueUsd}`); console.log(`Total Debt: $${status.totalDebtUsd}`); // Token composition for (const token of status.tokens) { console.log(` ${token.symbol}: ${token.balance} ($${token.valueUsd})`); } // Active bots for (const bot of status.bots) { console.log(` Bot: ${bot.address} — ${bot.permissions.join(", ")}`); } ``` ### Status Fields | Field | Type | Description | |---|---|---| | `healthFactor` | `number` | Current HF. Below 1 means liquidatable | | `totalValueUsd` | `number` | Position total value in USD | | `totalDebtUsd` | `number` | Outstanding debt in USD | | `leverage` | `number` | Current leverage ratio | | `tokens` | `TokenPosition[]` | Per-token balances, values, and LT info | | `bots` | `BotInfo[]` | Attached bots and their permissions | | `isPaused` | `boolean` | Whether the credit manager is paused | | `expirationDate` | `string \| null` | Account expiration, if applicable | ## Monitoring Alerts Use `sdk.monitor.getAlerts()` to check for active warnings on your positions: ```typescript const alerts = await sdk.monitor.getAlerts({ owner: walletAddress, chainId: 1, }); for (const alert of alerts) { console.log(`[${alert.severity}] ${alert.account}: ${alert.message}`); } ``` ### Alert Types | Severity | Condition | Suggested Action | |---|---|---| | `"warning"` | Health factor below 1.3 | Add collateral or reduce debt | | `"critical"` | Health factor below 1.1 | Immediate deleveraging or exit | | `"info"` | LT ramp active | Plan scheduled adjustment | | `"info"` | Account nearing expiration | Close or migrate before deadline | ## Position Lifecycle Example A complete flow from discovery through monitoring: ```typescript import { GearboxSDK, Asset } from "@gearbox-protocol/sdk/official"; // 1. Initialize const sdk = await GearboxSDK.init({ chains: [1], startFromCache: true }); // 2. Discover const strategies = await sdk.strategies.list({ chainId: 1, asset: Asset.ETH, minNetApy: 3, }); const strategy = strategies[0]; // 3. Prepare const rawTx = await sdk.positions.prepareOpen({ creditManager: strategy.id, chainId: 1, collateralToken: wstETHAddress, collateralAmount: 10n * 10n ** 18n, targetLeverage: 3.0, }); // 4. Preview const preview = await sdk.positions.previewOpen({ creditManager: strategy.id, chainId: 1, collateralToken: wstETHAddress, collateralAmount: 10n * 10n ** 18n, targetLeverage: 3.0, }); if (!preview.success || preview.healthFactor < 1.5) { throw new Error("Preview failed safety checks"); } // 5. Execute (you sign) const txHash = await walletClient.sendTransaction({ to: rawTx.to, data: rawTx.calldata, value: rawTx.value, }); await publicClient.waitForTransactionReceipt({ hash: txHash }); // 6. Monitor const accounts = await sdk.accounts.list({ owner: walletAddress, chainId: 1 }); const status = await sdk.accounts.getStatus({ account: accounts[0].address, chainId: 1, }); console.log(`Position HF: ${status.healthFactor}`); ``` ## Method Reference | Method | Returns | Description | |---|---|---| | `sdk.positions.prepareDeposit(params)` | `RawTx` | Build a pool deposit transaction | | `sdk.positions.previewDeposit(params)` | `DepositPreview` | Simulate a pool deposit | | `sdk.positions.prepareOpen(params)` | `RawTx` | Build a leveraged position open | | `sdk.positions.previewOpen(params)` | `OpenPreview` | Simulate a position open | | `sdk.previewTransaction(rawTx, opts)` | `TransactionPreview` | Universal simulation for any raw tx | | `sdk.accounts.list(params)` | `CreditAccount[]` | List credit accounts for a wallet | | `sdk.accounts.getStatus(params)` | `AccountStatus` | Full position health and composition | | `sdk.monitor.getAlerts(params)` | `Alert[]` | Active warnings across positions | ## Learn More - [Opportunities](https://docs.gearbox.finance/developers/gm-markets-opportunities) — discovering pools and strategies - [History & Events](https://docs.gearbox.finance/developers/gm-markets-history) — historical data and parameter change tracking - [Account Operations](https://docs.gearbox.finance/developers/gm-accounts-ops) — low-level multicall operations reference - [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) — building complex multi-step transactions ## Revoke Allowances Source: https://docs.gearbox.finance/developers/revoke-allowances File: content/developers/revoke-allowances.mdx Revoke Credit Account's token approvals to external contracts. > For Solidity implementation, see [Revoke Allowances](https://docs.gearbox.finance/developers/multicalls). ## Why You revoke allowances when: * **Security incident** - Third-party contract may be compromised * **Legacy cleanup** - Old accounts may have stale approvals from previous Gearbox versions * **Defense in depth** - Proactively remove unnecessary approvals Current Gearbox V3 automatically resets allowances to 1 after each interaction. However, older accounts from V2.1 may still have active allowances to external protocols. ## What `revokeAdapterAllowances` resets token approvals from your Credit Account to specified contracts: 1. You specify which (spender, token) pairs to revoke 2. Credit Account sets allowance to 1 for each pair 3. External contracts can no longer spend those tokens **Note:** Allowance is set to 1, not 0, due to gas optimization (writing non-zero to non-zero is cheaper than writing zero). ## How ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Define which approvals to revoke const revocations = [ { spender: uniswapRouterAddress, token: usdcAddress, }, { spender: curvePoolAddress, token: daiAddress, }, ]; const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'revokeAdapterAllowances', args: [revocations], }), }, ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Check Existing Allowances Before revoking, you might want to see what allowances exist: ```typescript import { erc20Abi, getContract } from 'viem'; const token = getContract({ address: tokenAddress, abi: erc20Abi, client: publicClient, }); // Check allowance from Credit Account to a spender const allowance = await token.read.allowance([ creditAccountAddress, spenderAddress, ]); if (allowance > 1n) { console.log(`Credit Account has ${allowance} allowance to ${spenderAddress}`); } ``` ### Revoke All Known Adapters If you want to revoke all adapter allowances for a token: ```typescript // Get all adapters from Credit Manager const adapters = await creditManager.read.adapters(); // Build revocations for all adapters const revocations = adapters.map(adapter => ({ spender: adapter, token: tokenAddress, })); const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'revokeAdapterAllowances', args: [revocations], }), }, ]; ``` ## Gotchas ### Usually Not Needed in V3 Gearbox V3 automatically resets allowances after each adapter interaction. This function exists mainly for: 1. Legacy accounts migrated from V2.1 2. Paranoid security posture 3. Specific incident response If you're using a fresh V3 account, allowances are already minimal. ### Revocation Struct Format The `RevocationPair` struct has two fields: ```typescript interface RevocationPair { spender: Address; // Contract that has the allowance token: Address; // Token that was approved } ``` Both must be valid addresses. Invalid addresses may cause the call to revert or have no effect. ### Sets to 1, Not 0 For gas efficiency, allowances are set to 1 instead of 0: ```typescript // Before revocation: allowance = 1000000000000000000 (1 token) // After revocation: allowance = 1 (essentially zero for practical purposes) ``` An allowance of 1 wei is functionally zero for any realistic token amount. ### Can't Revoke Non-Existent Allowances Revoking an allowance that doesn't exist (already 0 or 1) is a no-op - it won't revert, but wastes gas. ### Adapter vs External Contract Revocations target the **spender** (usually an adapter), not the underlying protocol: ```typescript // The adapter has the allowance, not Uniswap directly const revocations = [ { spender: uniswapAdapter, // Adapter address, not Uniswap Router token: usdcAddress, }, ]; ``` Adapters are what actually interact with your Credit Account's tokens. ### Batch Multiple Revocations You can revoke multiple (spender, token) pairs in one call: ```typescript // Efficient - single call const revocations = [ { spender: adapter1, token: usdc }, { spender: adapter1, token: dai }, { spender: adapter2, token: usdc }, ]; args: [revocations] // Less efficient - multiple calls [ { args: [[{ spender: adapter1, token: usdc }]] }, { args: [[{ spender: adapter1, token: dai }]] }, { args: [[{ spender: adapter2, token: usdc }]] }, ] ``` ### When to Actually Use This Real scenarios where revocation makes sense: 1. **Third-party exploit:** A protocol Gearbox integrates with gets hacked. Revoke allowances to that protocol's adapter as a precaution. 2. **Account migration:** Moving from V2.1 account with old allowances to ensure clean state. 3. **Compliance requirement:** Some regulatory frameworks require revoking unused approvals. 4. **Personal security policy:** You want explicit control over all approvals. For normal operations, V3's automatic reset is sufficient. ## See Also * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - How adapters use allowances * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Related account management * [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) - Account overview and management ## Multicalls Source: https://docs.gearbox.finance/developers/gm-accounts-multicalls File: content/developers/gm-accounts-multicalls.mdx Build and execute multicalls to perform multiple operations on a credit account atomically. Multicalls are the primary way to interact with credit accounts. ## Service Multicall Helpers The SDK provides structured multicall builders via `createCreditAccountService`: ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); ``` ## Available Service Methods | Method | Operation | | ---------------------------------------------- | -------------------------- | | `prepareAddCollateral(token, amount)` | Add collateral from wallet | | `prepareIncreaseDebt(amount)` | Borrow from pool | | `prepareDecreaseDebt(amount)` | Repay debt | | `prepareUpdateQuota(token, change, minQuota)` | Adjust token quota | | `prepareWithdrawCollateral(token, amount, to)` | Remove collateral | ## Building a Multicall ```typescript // Build multicall with SDK helpers const calls = [ // Add 10,000 USDC as collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Borrow 40,000 USDC (5x leverage) service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for destination token service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; ``` ## Executing Multicalls ### On Existing Account ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); await market.creditFacade.write.multicall([ creditAccountAddress, calls, ]); ``` ### Opening with Multicall ```typescript const hash = await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ## Combining SDK Helpers with Raw Encoding For adapter calls or custom operations, combine SDK helpers with manual encoding: ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; const calls = [ // SDK helpers for standard operations service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Manual encoding for adapter calls { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [{ tokenIn: usdcAddress, tokenOut: wethAddress, fee: 500, recipient: '0x0000000000000000000000000000000000000000', // Adapter overrides deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), amountIn: 50_000n * 10n ** 6n, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n, }], }), }, // SDK helper for quota service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; ``` ## Getting Adapter Addresses Retrieve adapter addresses from the Credit Manager: ```typescript import { getContract } from 'viem'; import { creditManagerAbi } from '@gearbox-protocol/sdk'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get adapter for a protocol (e.g., Uniswap V3 Router) const uniswapV3Adapter = await creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); ``` ## Complete Example ```typescript import { GearboxSDK, createCreditAccountService, iCreditFacadeV300MulticallAbi, } from '@gearbox-protocol/sdk'; import { encodeFunctionData, createPublicClient, createWalletClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function leveragePosition() { const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client: publicClient, marketConfigurators: [], }); const service = createCreditAccountService(sdk, 310); // Find market const market = sdk.marketRegister.findByCreditManager(cmAddress); // Build multicall const calls = [ // Add collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Borrow service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for final token service.prepareUpdateQuota(targetToken, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; // Execute const walletClient = createWalletClient({ chain: mainnet, transport: http(), account: myAccount, }); const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'openCreditAccount', args: [myAccount.address, calls, 0n], }); console.log(`Transaction: ${hash}`); } ``` ## Best Practices 1. **Always use slippage protection** when performing swaps 2. **Price updates first** if using pull-based oracles 3. **Set quotas** for tokens you'll hold as collateral 4. **Approve collateral** to Credit Manager (not Facade) before adding ## Learn More * [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) - Transfer tokens with approval patterns * [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) - Borrowing, repayment, and constraints * [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) - Quota mechanics and limits * [Withdrawing Collateral](https://docs.gearbox.finance/developers/gm-mc-withdraw) - Safe pricing and health impact * [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) - Adapter interaction patterns * [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) - Balance protection, price feeds, and check params * [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) - Token enable/disable and allowance revocation ## Use Cases Source: https://docs.gearbox.finance/developers/use-cases File: content/developers/use-cases.mdx The SDK guide shows you how to use individual methods. This section shows you how to combine them to build real applications. ## Choose Your Path | If you're building... | Start here | You'll learn | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | Web dashboard, portfolio UI | [Frontend Applications](https://docs.gearbox.finance/developers/frontend-applications) | Data fetching patterns, display mapping, real-time updates | | Indexer, analytics, data pipeline | [Backend Services](https://docs.gearbox.finance/developers/backend-services) | Historical snapshots, event indexing, state tracking | | Liquidation bot | [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) | Account monitoring, health factor filtering, execution patterns | | Health monitoring | [Health Factor Monitoring](https://docs.gearbox.finance/developers/health-factor-monitoring) | Track HF over time, alerts, risk analysis | ## Quick Decision Guide **Need to display data to users in real-time?** Start with [Frontend Applications](https://docs.gearbox.finance/developers/frontend-applications). You'll use `marketRegister` for cached data and compressors for fresh queries. **Need to collect and analyze historical data?** Start with [Backend Services](https://docs.gearbox.finance/developers/backend-services). You'll index events and snapshot state at specific blocks. **Need to monitor accounts and execute on-chain actions?** Start with [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots). You'll filter accounts by health factor and use the Router for execution. **Need to track account health and alert on risk?** Start with [Health Factor Monitoring](https://docs.gearbox.finance/developers/health-factor-monitoring). You'll poll compressors, classify risk levels, and build alerting. ## Prerequisites Before diving into use-case guides, complete: 1. [**Setup**](https://docs.gearbox.finance/developers/sdk-setup) - Install the SDK and initialize `GearboxSDK` 2. [**Reading Data**](https://docs.gearbox.finance/developers/reading-data) - Understand `marketRegister` and basic queries ## Common Foundation: Compressors All use cases rely on compressors for efficient data fetching. Compressors aggregate on-chain data into single calls, reducing RPC overhead. | Compressor | Use Case | | ------------------------- | --------------------------------------------- | | `MarketCompressor` | Pool state, credit manager config, token data | | `CreditAccountCompressor` | Account queries with filtering and pagination | | `PriceFeedCompressor` | Oracle status and update requirements | See [Compressors Reference](https://docs.gearbox.finance/developers/compressors) for the complete API. ## Relationship to Other Guides ``` SDK Guide ├── setup.md # Installation and initialization ├── reading-data.md # Basic queries ├── credit-accounts.md # Account operations ├── multicalls.md # Operation overview ├── multicalls/ # Individual operation docs │ └── [10 operation pages] └── use-cases/ # <-- You are here ├── frontend-applications.md ├── backend-services.md ├── liquidation-bots.md └── health-factor-monitoring.md ``` The **multicalls/** directory documents individual operations (add collateral, manage debt, etc.). The **use-cases/** directory shows how to combine those operations with data fetching to build complete applications. ## Adding Collateral Source: https://docs.gearbox.finance/developers/gm-mc-collateral File: content/developers/gm-mc-collateral.mdx Deposit tokens from your wallet to a credit account to increase its health factor and enable borrowing. ## Why You need to add collateral when: * **Opening an account** - Initial deposit to enable borrowing * **Improving health factor** - Account approaching liquidation threshold * **Enabling more borrowing** - Current collateral limits how much you can borrow Adding collateral increases your account's total weighted value (TWV), which improves the health factor and allows larger debt positions. ## What `addCollateral` transfers tokens from your wallet to the Credit Account. On execution: 1. The Credit Manager calls `transferFrom` to move tokens from caller to Credit Account 2. The token is enabled as collateral (if not already enabled) 3. Quoted tokens are NOT auto-enabled - you must set a quota separately **Important:** Approve tokens to the **Credit Manager**, not the Credit Facade. The Credit Manager is the contract that actually executes the transfer. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); // Build the multicall const calls = [ service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), ]; // First, approve to Credit Manager (not Facade!) const market = sdk.marketRegister.findByCreditManager(cmAddress); await usdcContract.write.approve([ market.creditManager.address, 10_000n * 10n ** 6n, ]); // Execute on existing account await market.creditFacade.write.multicall([creditAccountAddress, calls]); // Or open new account with collateral await market.creditFacade.write.openCreditAccount([ ownerAddress, calls, 0n, // referralCode ]); ``` ### Using Permit (No Separate Approval) For EIP-2612 compatible tokens, you can avoid the separate approval transaction: ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Sign permit message (details depend on your wallet setup) const { v, r, s, deadline } = await signPermit(/* ... */); const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'addCollateralWithPermit', args: [tokenAddress, amount, deadline, v, r, s], }), }, ]; ``` ## Gotchas ### Approve to Credit Manager, Not Facade The most common mistake. The Credit Manager executes the `transferFrom`, so it needs the approval: ```typescript // CORRECT await token.write.approve([creditManager.address, amount]); // WRONG - will fail await token.write.approve([creditFacade.address, amount]); ``` ### Quoted Tokens Need Quota Adding a quoted token as collateral does NOT automatically enable it. You must also call `updateQuota`: ```typescript const calls = [ service.prepareAddCollateral(quotedTokenAddress, amount), service.prepareUpdateQuota(quotedTokenAddress, quotaAmount, minQuota), ]; ``` ### Direct Transfers Don't Enable Sending tokens directly to a Credit Account (via `transfer`) does NOT enable them as collateral. You still need a multicall with `enableToken` to count them in the health factor. ### Invalid Collateral Tokens Only tokens recognized by the Credit Manager can be used as collateral. Transferring unrecognized tokens to a Credit Account may result in them being stuck (only governance can recover). Check if a token is valid: ```typescript // This reverts if token is not valid collateral const mask = await creditManager.read.getTokenMaskOrRevert([tokenAddress]); ``` ## Learn More * [Withdrawing Collateral](https://docs.gearbox.finance/developers/gm-mc-withdraw) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) - Often combined with adding collateral * [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) - Required for quoted tokens ## Frontend Applications Source: https://docs.gearbox.finance/developers/frontend-applications File: content/developers/frontend-applications.mdx Build dashboards, portfolio trackers, and trading UIs that display Gearbox protocol data and let users manage positions. ## Overview Frontend applications typically need to: 1. Display pool and market data 2. Show collateral exposure and limits 3. Monitor credit account health 4. Enable position management This guide maps each requirement to specific SDK methods. *** ## Pool Display **WHY:** Users want to see pool health, yields, and utilization before depositing or borrowing. ### Data Requirements | Display | SDK Source | Field | | ------------------- | ------------- | -------------------------------------------------- | | Underlying token | `market.pool` | `underlying.symbol`, `underlying.address` | | Total supplied | `market.pool` | `totalAssets` | | Available liquidity | `market.pool` | `availableLiquidity` | | Utilization | Calculated | `(totalAssets - availableLiquidity) / totalAssets` | | Supply APY | `market.pool` | `supplyRate` (RAY scaled) | | Borrow APR | `market.pool` | `baseInterestRate` (RAY scaled) | | Share price | `market.pool` | `dieselRate` (RAY scaled) | ### How to Fetch ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Find market by pool address const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; // Display data const RAY = 10n ** 27n; console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total Supplied: ${pool.totalAssets}`); console.log(`Available: ${pool.availableLiquidity}`); // Calculate utilization const borrowed = pool.totalAssets - pool.availableLiquidity; const utilization = Number(borrowed * 10000n / pool.totalAssets) / 100; console.log(`Utilization: ${utilization.toFixed(2)}%`); // Convert RAY rates to percentages const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Borrow APR: ${borrowAPR.toFixed(2)}%`); ``` ### Where Data Comes From The SDK caches market data on initialization via `GearboxSDK.attach()`. This data comes from the `MarketCompressor` contract. For real-time updates, either: * Re-initialize the SDK periodically * Call compressors directly (see [Real-Time Updates](#real-time-updates)) *** ## Collateral Exposure **WHY:** Users want to see what collaterals the pool is exposed to and current utilization against limits. ### Data Requirements | Display | SDK Source | Field | | --------------------- | ------------------------ | ------------------- | | Quoted tokens | `MarketData.quotaKeeper` | `tokens[]` | | Token quota limit | `quotaKeeper.tokens[]` | `limit` | | Current quoted amount | `quotaKeeper.tokens[]` | `totalQuoted` | | Quota rate | `quotaKeeper.tokens[]` | `rate` (RAY scaled) | ### How to Fetch For quota data, use the `MarketCompressor` directly: ```typescript import { marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; // Get compressor address const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Fetch market data with quota keeper const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); // QuotaKeeper token data for (const token of marketData.quotaKeeper.tokens) { const utilizationPct = Number(token.totalQuoted * 10000n / token.limit) / 100; console.log(`Token: ${token.token}`); console.log(` Limit: ${token.limit}`); console.log(` Quoted: ${token.totalQuoted}`); console.log(` Utilization: ${utilizationPct.toFixed(2)}%`); console.log(` Rate: ${token.rate}`); // RAY scaled } ``` ### Gotcha: Quota Limits Before letting users open positions with a specific collateral, check that `totalQuoted < limit`. If the limit is reached, new positions with that collateral will fail. *** ## Credit Manager Configuration **WHY:** Users need to know debt limits, collateral requirements, and fees before opening positions. ### Data Requirements | Display | SDK Source | Field | | --------------------- | -------------------- | ------------------------------------- | | Min debt | `creditManager` | `minDebt` | | Max debt | `creditManager` | `maxDebt` | | Collateral tokens | `creditManager` | `collateralTokens[]` | | Liquidation threshold | `collateralTokens[]` | `liquidationThreshold` (basis points) | | Fees | `creditManager` | `fees` | | Liquidation premium | `creditManager` | `liquidationPremium` | ### How to Fetch ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); for (const cm of market.creditManagers) { console.log(`Credit Manager: ${cm.address}`); console.log(`Credit Facade: ${cm.creditFacade}`); // Debt limits console.log(`Min Debt: ${cm.minDebt}`); console.log(`Max Debt: ${cm.maxDebt}`); // Collateral configuration console.log('Allowed Collaterals:'); for (const token of cm.collateralTokens) { // LT is in basis points (10000 = 100%) const ltPct = Number(token.liquidationThreshold) / 100; console.log(` ${token.symbol}: LT ${ltPct.toFixed(1)}%`); } } ``` ### Understanding Liquidation Threshold The liquidation threshold (LT) determines how much each collateral contributes to the weighted collateral value: ``` Weighted Value = Token Balance * Token Price * LT Health Factor = Total Weighted Value / Total Debt ``` A lower LT means the protocol values that collateral more conservatively. *** ## Credit Account Monitoring **WHY:** Users need to track their position health, collateral values, and accrued interest. ### Data Requirements | Display | SDK Source | Field | | ---------------- | ------------------- | ---------------------------- | | Account address | `CreditAccountData` | `addr` | | Owner | `CreditAccountData` | `owner` | | Total debt | `CreditAccountData` | `debt` | | Health factor | `CreditAccountData` | `healthFactor` (10000 = 1.0) | | Is liquidatable | `CreditAccountData` | `isLiquidatable` | | Token balances | `CreditAccountData` | `tokens[]` | | Token values | `tokens[]` | `balanceInUnderlying` | | Accrued interest | `CreditAccountData` | `cumulativeQuotaInterest` | | Quota fees | `CreditAccountData` | `quotaFees` | ### How to Fetch ```typescript import { createCreditAccountService } from '@gearbox-protocol/sdk'; const service = createCreditAccountService(sdk, 310); // Get user's accounts const accounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: userAddress, }, sdk.currentBlock ); for (const account of accounts) { // Health factor: 10000 = 1.0 const hf = Number(account.healthFactor) / 10000; console.log(`Account: ${account.addr}`); console.log(`Health Factor: ${hf.toFixed(4)}`); console.log(`Liquidatable: ${account.isLiquidatable}`); // Debt breakdown console.log(`Total Debt: ${account.debt}`); console.log(`Accrued Interest: ${account.cumulativeQuotaInterest}`); console.log(`Quota Fees: ${account.quotaFees}`); // Token positions console.log('Positions:'); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}:`); console.log(` Balance: ${token.balance}`); console.log(` Value (underlying): ${token.balanceInUnderlying}`); console.log(` LT: ${token.lt / 100}%`); } } } ``` ### Interest Breakdown Credit account debt consists of: * **Principal:** Original borrowed amount * **Base interest:** Accrues on principal based on pool utilization * **Quota interest:** Per-collateral rate for non-underlying tokens * **Quota fees:** Fixed fee component for quotas ```typescript // Total debt = principal + base interest + quota interest + quota fees const principal = account.borrowedAmount; const quotaInterest = account.cumulativeQuotaInterest; const quotaFees = account.quotaFees; const baseInterest = account.debt - principal - quotaInterest - quotaFees; ``` ### Health Factor Thresholds Display appropriate warnings based on health factor: ```typescript function getHealthStatus(hf: number): string { if (hf < 1.0) return 'LIQUIDATABLE'; if (hf < 1.05) return 'CRITICAL'; if (hf < 1.1) return 'WARNING'; return 'HEALTHY'; } const status = getHealthStatus(Number(account.healthFactor) / 10000); ``` *** ## Price Feed Information **WHY:** Users want to understand which oracles determine their collateral values. ### How to Fetch ```typescript import { priceFeedCompressorAbi, AP_PRICE_FEED_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; // Get price feed compressor const [priceFeedCompressor] = sdk.addressProvider.mustGetLatest( AP_PRICE_FEED_COMPRESSOR, VERSION_RANGE_310 ); // Get all price feeds for a price oracle const feeds = await client.readContract({ address: priceFeedCompressor, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [priceOracleAddress], }); for (const feed of feeds) { console.log(`Token: ${feed.token}`); console.log(` Feed: ${feed.priceFeed}`); console.log(` Needs Update: ${feed.needsUpdate}`); } ``` *** ## Position Management **WHY:** Users take actions on their positions (add collateral, borrow, repay, etc.). ### Linking to Operations Position management uses multicalls. Link to the appropriate operation guide: | User Action | Operation Guide | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | Deposit collateral | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | | Borrow more | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Repay debt | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Update quota | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | | Withdraw | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | | Swap collateral | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | ### Pre-Operation Data Checks Before letting users perform operations, validate: ```typescript // Before addCollateral: Check token is allowed const isAllowed = market.creditManagers[0].collateralTokens .some(t => t.address === tokenAddress); // Before increaseDebt: Check against max debt const newDebt = account.debt + borrowAmount; const isWithinLimit = newDebt <= cm.maxDebt; // Before updateQuota: Check quota capacity const quotaToken = marketData.quotaKeeper.tokens .find(t => t.token === tokenAddress); const hasCapacity = quotaToken && quotaToken.totalQuoted < quotaToken.limit; // Before any operation: Estimate health factor impact // (Use simulation or calculate locally) ``` *** ## Real-Time Updates **WHY:** UI needs to stay current as blockchain state changes. ### Option 1: Poll Compressors For most dashboards, polling every few seconds is sufficient: ```typescript const POLL_INTERVAL = 5000; // 5 seconds async function pollMarketData() { const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); // Update UI state setPoolData(marketData.pool); setQuotaData(marketData.quotaKeeper); } // Start polling setInterval(pollMarketData, POLL_INTERVAL); ``` ### Option 2: Watch Events For specific state changes, watch contract events: ```typescript import { parseAbiItem } from 'viem'; // Watch for credit account state changes const unwatch = client.watchContractEvent({ address: creditManagerAddress, abi: creditManagerAbi, eventName: 'ExecuteOrder', onLogs: (logs) => { // Refresh account data for affected accounts for (const log of logs) { refreshAccountData(log.args.creditAccount); } }, }); // Cleanup on unmount return () => unwatch(); ``` ### Recommendation Use **polling** for: * General market data * Pool state (rates, liquidity) * Quota utilization Use **events** for: * User's own account changes * Critical health factor alerts *** ## Complete Example: Dashboard Component ```typescript import { GearboxSDK, createCreditAccountService, marketCompressorAbi } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; interface DashboardData { pool: { underlying: string; totalAssets: bigint; availableLiquidity: bigint; supplyAPY: number; borrowAPR: number; }; userAccounts: Array<{ address: string; healthFactor: number; debt: bigint; isLiquidatable: boolean; }>; } async function fetchDashboardData( poolAddress: `0x${string}`, cmAddress: `0x${string}`, userAddress: `0x${string}` ): Promise { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const RAY = 10n ** 27n; // Pool data const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; // User accounts const service = createCreditAccountService(sdk, 310); const accounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: userAddress }, sdk.currentBlock ); return { pool: { underlying: pool.underlying.symbol, totalAssets: pool.totalAssets, availableLiquidity: pool.availableLiquidity, supplyAPY: Number(pool.supplyRate * 10000n / RAY) / 100, borrowAPR: Number(pool.baseInterestRate * 10000n / RAY) / 100, }, userAccounts: accounts.map(a => ({ address: a.addr, healthFactor: Number(a.healthFactor) / 10000, debt: a.debt, isLiquidatable: a.isLiquidatable, })), }; } ``` *** ## Next Steps * [Multicall Operations](https://docs.gearbox.finance/developers/multicalls) - Implement position management * [Backend Services](https://docs.gearbox.finance/developers/backend-services) - If you also need historical data * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete compressor API ## Backend Services Source: https://docs.gearbox.finance/developers/backend-services File: content/developers/backend-services.mdx Build indexers, analytics pipelines, and data warehouses that track Gearbox protocol state over time. ## Overview Backend services typically need to: 1. Capture historical snapshots at specific blocks 2. Index events for efficient state reconstruction 3. Track rates, values, and utilization over time 4. Store and query historical data This guide shows patterns for each requirement. *** ## Historical Snapshots **WHY:** Track how protocol state changes over time for analytics, reporting, and historical queries. ### What to Snapshot | Data | Source | Change Frequency | | -------------------- | ------------------- | ------------------------- | | Pool rates | `PoolState` | Every block with activity | | Pool liquidity | `PoolState` | Every deposit/borrow | | Quota utilization | `QuotaKeeperState` | Every position change | | Credit account state | `CreditAccountData` | Every account operation | | Token prices | `PriceOracle` | External feed updates | ### How to Query at Specific Blocks Compressors support querying at historical blocks using viem's `blockTag` or `blockNumber`: ```typescript import { marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Query at specific block const historicalData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber: 19000000n, // Specific block }); console.log(`Pool state at block 19000000:`); console.log(` Available liquidity: ${historicalData.pool.availableLiquidity}`); console.log(` Supply rate: ${historicalData.pool.supplyRate}`); ``` ### Archive Node Requirements Historical queries require an archive node. Standard nodes only keep recent state (\~128 blocks). **RPC providers with archive access:** * Alchemy (archive add-on) * Infura (archive add-on) * QuickNode (archive plans) * Self-hosted Erigon/Reth ### Snapshot Pattern ```typescript interface PoolSnapshot { blockNumber: bigint; timestamp: number; availableLiquidity: bigint; totalAssets: bigint; supplyRate: bigint; borrowRate: bigint; } async function capturePoolSnapshot( blockNumber: bigint ): Promise { const block = await client.getBlock({ blockNumber }); const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber, }); return { blockNumber, timestamp: Number(block.timestamp), availableLiquidity: marketData.pool.availableLiquidity, totalAssets: marketData.pool.totalAssets, supplyRate: marketData.pool.supplyRate, borrowRate: marketData.pool.baseInterestRate, }; } // Capture hourly snapshots const BLOCKS_PER_HOUR = 300n; // ~12 second blocks let currentBlock = startBlock; while (currentBlock <= endBlock) { const snapshot = await capturePoolSnapshot(currentBlock); await saveToDatabase(snapshot); currentBlock += BLOCKS_PER_HOUR; } ``` *** ## Event Indexing **WHY:** Events provide efficient tracking of specific state changes without polling. ### Key Events Credit Facade emits events for all account operations: | Event | When Emitted | Key Data | | ------------------------ | ------------------ | ----------------------------------------- | | `OpenCreditAccount` | Account opened | owner, creditAccount, borrowAmount | | `CloseCreditAccount` | Account closed | creditAccount | | `LiquidateCreditAccount` | Account liquidated | creditAccount, liquidator, remainingFunds | | `StartMultiCall` | Multicall begins | creditAccount | | `FinishMultiCall` | Multicall ends | creditAccount | Pool emits events for liquidity changes: | Event | When Emitted | Key Data | | ---------- | ---------------------- | ----------------------------------- | | `Deposit` | LP deposits | sender, owner, assets, shares | | `Withdraw` | LP withdraws | sender, receiver, assets, shares | | `Borrow` | Credit Manager borrows | creditAccount, amount | | `Repay` | Debt repaid | creditAccount, amount, profit, loss | ### Watching Events with viem ```typescript import { parseAbiItem } from 'viem'; // Watch for new credit accounts const unwatchOpen = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', onLogs: async (logs) => { for (const log of logs) { console.log(`New account: ${log.args.creditAccount}`); console.log(` Owner: ${log.args.owner}`); console.log(` Initial debt: ${log.args.borrowAmount}`); await indexCreditAccount(log); } }, }); // Watch for liquidations const unwatchLiquidate = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'LiquidateCreditAccount', onLogs: async (logs) => { for (const log of logs) { console.log(`Liquidated: ${log.args.creditAccount}`); console.log(` Liquidator: ${log.args.liquidator}`); await recordLiquidation(log); } }, }); ``` ### Fetching Historical Events For backfilling, fetch events in block ranges: ```typescript async function fetchHistoricalEvents( fromBlock: bigint, toBlock: bigint ) { // Fetch in chunks to avoid RPC limits const CHUNK_SIZE = 10000n; let current = fromBlock; while (current <= toBlock) { const chunkEnd = current + CHUNK_SIZE > toBlock ? toBlock : current + CHUNK_SIZE; const logs = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', fromBlock: current, toBlock: chunkEnd, }); for (const log of logs) { await processEvent(log); } current = chunkEnd + 1n; } } ``` *** ## State Tracking **WHY:** Build complete account or pool history over time by combining events and snapshots. ### Credit Account Lifecycle Track an account from open to close: ```typescript interface AccountHistory { creditAccount: string; owner: string; openBlock: bigint; closeBlock: bigint | null; operations: AccountOperation[]; } interface AccountOperation { blockNumber: bigint; txHash: string; type: 'open' | 'multicall' | 'liquidate' | 'close'; healthFactorAfter?: bigint; } async function trackAccountLifecycle(creditAccount: string): Promise { // Find open event const openEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', args: { creditAccount }, fromBlock: 0n, toBlock: 'latest', }); const openEvent = openEvents[0]; // Find all multicall events const multicallEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'FinishMultiCall', args: { creditAccount }, fromBlock: openEvent.blockNumber, toBlock: 'latest', }); // Find close event (if any) const closeEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'CloseCreditAccount', args: { creditAccount }, fromBlock: openEvent.blockNumber, toBlock: 'latest', }); return { creditAccount, owner: openEvent.args.owner, openBlock: openEvent.blockNumber, closeBlock: closeEvents[0]?.blockNumber ?? null, operations: [ { blockNumber: openEvent.blockNumber, txHash: openEvent.transactionHash, type: 'open' }, ...multicallEvents.map(e => ({ blockNumber: e.blockNumber, txHash: e.transactionHash, type: 'multicall' as const, })), ...(closeEvents[0] ? [{ blockNumber: closeEvents[0].blockNumber, txHash: closeEvents[0].transactionHash, type: 'close' as const, }] : []), ].sort((a, b) => Number(a.blockNumber - b.blockNumber)), }; } ``` ### Combining Events and Snapshots For complete state reconstruction: ```typescript async function reconstructAccountStateAtBlock( creditAccount: string, targetBlock: bigint ): Promise { // Check if account existed at this block const history = await trackAccountLifecycle(creditAccount); if (history.openBlock > targetBlock) { return null; // Account didn't exist yet } if (history.closeBlock && history.closeBlock <= targetBlock) { return null; // Account was closed } // Query compressor at target block const [accountData] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccountData', args: [creditManagerAddress, creditAccount], blockNumber: targetBlock, }); return accountData; } ``` *** ## Rate History **WHY:** Analytics on yield, utilization trends, and rate changes over time. ### Rates to Track | Rate | Source | Notes | | --------------- | --------------------------- | -------------------------------------------------- | | Supply APY | `pool.supplyRate` | RAY scaled (10^27) | | Base borrow APR | `pool.baseInterestRate` | RAY scaled | | Quota rates | `quotaKeeper.tokens[].rate` | Per-token, RAY scaled | | Utilization | Calculated | `(totalAssets - availableLiquidity) / totalAssets` | ### Polling Pattern ```typescript interface RateSnapshot { blockNumber: bigint; timestamp: number; supplyRate: bigint; borrowRate: bigint; utilization: number; quotaRates: Map; } async function pollRates(): Promise { const block = await client.getBlock({ blockTag: 'latest' }); const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); const pool = marketData.pool; const borrowed = pool.totalAssets - pool.availableLiquidity; const utilization = pool.totalAssets > 0n ? Number(borrowed * 10000n / pool.totalAssets) / 100 : 0; const quotaRates = new Map(); for (const token of marketData.quotaKeeper.tokens) { quotaRates.set(token.token, token.rate); } return { blockNumber: block.number, timestamp: Number(block.timestamp), supplyRate: pool.supplyRate, borrowRate: pool.baseInterestRate, utilization, quotaRates, }; } // Poll every minute setInterval(async () => { const snapshot = await pollRates(); await saveRateSnapshot(snapshot); }, 60_000); ``` ### Rate Conversion Convert RAY-scaled rates to annual percentages: ```typescript const RAY = 10n ** 27n; function rayToAnnualPercent(rayRate: bigint): number { // rate is per-second, annualize it const SECONDS_PER_YEAR = 365n * 24n * 60n * 60n; const annualRate = rayRate * SECONDS_PER_YEAR; return Number(annualRate * 10000n / RAY) / 100; } const supplyAPY = rayToAnnualPercent(pool.supplyRate); console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); ``` *** ## Complete Example: Simple Indexer ```typescript import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; import { GearboxSDK, marketCompressorAbi, creditAccountCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; interface IndexerState { lastIndexedBlock: bigint; pools: Map; accounts: Map; } async function runIndexer( startBlock: bigint, poolAddress: `0x${string}`, creditManagerAddress: `0x${string}` ) { const client = createPublicClient({ chain: mainnet, transport: http(process.env.ARCHIVE_RPC_URL), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const [marketCompressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); let currentBlock = startBlock; while (true) { const latestBlock = await client.getBlockNumber(); while (currentBlock <= latestBlock) { // Snapshot pool state const marketData = await client.readContract({ address: marketCompressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber: currentBlock, }); await savePoolSnapshot(currentBlock, marketData.pool); // Index events in this block range const events = await client.getContractEvents({ address: creditManagerAddress, abi: creditFacadeAbi, fromBlock: currentBlock, toBlock: currentBlock + 100n, }); for (const event of events) { await processEvent(event); } currentBlock += 100n; } // Wait for new blocks await sleep(12_000); } } function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } ``` *** ## Next Steps * [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) - If you need to act on indexed data * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete compressor API * [Frontend Applications](https://docs.gearbox.finance/developers/frontend-applications) - If you also need real-time display ## Debt Management Source: https://docs.gearbox.finance/developers/gm-mc-debt File: content/developers/gm-mc-debt.mdx Borrow from or repay to the pool. Debt operations directly affect your credit account's health factor. ## Why You manage debt when: * **Increasing leverage** - Borrow more to amplify exposure * **Taking profit** - Repay debt while keeping collateral positions * **Reducing risk** - Lower debt to improve health factor * **Closing account** - Repay all debt before withdrawal ## What ### Increase Debt `increaseDebt` borrows the underlying asset from the pool to your Credit Account: 1. Pool transfers underlying to Credit Account 2. Debt parameters (principal + interest) are recalculated 3. Health factor decreases ### Decrease Debt `decreaseDebt` repays debt from Credit Account's underlying balance: 1. Underlying is transferred from Credit Account to pool 2. Debt parameters are recalculated 3. Health factor increases **Repayment order** (when not paying full debt): 1. Quota-related fees (quota increase fees) 2. Accrued quota interest 3. Interest + interest fee (split pro-rata if partial) 4. Principal This means partial payments may not reduce your principal at all. ## How ### Borrow More ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); const calls = [ service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Borrow 40,000 USDC ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Repay Debt ```typescript const calls = [ service.prepareDecreaseDebt(10_000n * 10n ** 6n), // Repay 10,000 USDC ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Repay All Debt Pass `type(uint256).max` equivalent to repay everything: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ // Zero all quotas FIRST (required before full repayment) service.prepareUpdateQuota(quotedToken1, BigInt.asIntN(96, -1n * 2n ** 95n), 0n), service.prepareUpdateQuota(quotedToken2, BigInt.asIntN(96, -1n * 2n ** 95n), 0n), // Then repay all debt service.prepareDecreaseDebt(MAX_UINT256), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Common Pattern: Add Collateral + Borrow ```typescript const calls = [ // First add collateral service.prepareAddCollateral(usdcAddress, 10_000n * 10n ** 6n), // Then borrow (5x leverage) service.prepareIncreaseDebt(40_000n * 10n ** 6n), // Set quota for destination token service.prepareUpdateQuota(wethAddress, 50_000n * 10n ** 6n, 50_000n * 10n ** 6n), ]; // Don't forget approval to Credit Manager! await usdcContract.write.approve([market.creditManager.address, 10_000n * 10n ** 6n]); await market.creditFacade.write.openCreditAccount([ownerAddress, calls, 0n]); ``` ## Gotchas ### One Debt Update Per Block You cannot increase AND decrease debt in the same block. This constraint prevents manipulation: ```typescript // WRONG - will revert on second operation const calls = [ service.prepareIncreaseDebt(amount1), service.prepareDecreaseDebt(amount2), // Reverts! ]; // CORRECT - one multicall, one debt operation const calls = [ service.prepareIncreaseDebt(netAmount), ]; ``` ### Zero All Quotas Before Full Repayment Non-zero quotas with zero debt is an invalid state. Zero your quotas BEFORE the final debt repayment: ```typescript const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quotas first service.prepareUpdateQuota(token1, INT96_MIN, 0n), service.prepareUpdateQuota(token2, INT96_MIN, 0n), // Then full repayment service.prepareDecreaseDebt(MAX_UINT256), ]; ``` ### Debt Must Stay in Range After any debt change, the principal must be either: * Zero (fully repaid), OR * Within `[minDebt, maxDebt]` range You cannot have debt between 0 and `minDebt`. ### Forbidden Tokens Block Borrowing If your account has forbidden tokens enabled as collateral, you cannot increase debt. Disable them first. ### Interest Accrues Continuously When repaying "the full amount", the debt may have grown since you read it. Add a buffer: ```typescript // Read current total debt const accountData = await service.getCreditAccountData(creditAccountAddress); const totalDebt = accountData.debt; // Add 0.1% buffer for interest accrual const repayAmount = totalDebt + (totalDebt / 1000n); // Or just use MAX_UINT256 to repay whatever the current amount is const calls = [service.prepareDecreaseDebt(MAX_UINT256)]; ``` ### Cannot Decrease on Open / Increase on Close * `decreaseDebt` is prohibited when opening an account * `increaseDebt` is prohibited when closing an account ## Learn More * [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) - Quota interest affects total debt * [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) - Often combined with borrowing * [Withdrawing Collateral](https://docs.gearbox.finance/developers/gm-mc-withdraw) - Often combined with repaying ## Liquidation Bots Source: https://docs.gearbox.finance/developers/liquidation-bots File: content/developers/liquidation-bots.mdx Build bots that monitor credit accounts and execute profitable liquidations. ## Overview Liquidation bots need to: 1. Find accounts with low health factors 2. Filter for liquidatable accounts 3. Compute optimal liquidation paths 4. Execute liquidations profitably This guide covers each step with verified SDK patterns. *** ## Understanding Liquidation **WHY:** Know what you're building before writing code. ### When Accounts Become Liquidatable An account becomes liquidatable when its health factor drops below 1.0: ``` Health Factor = Total Weighted Collateral Value / Total Debt Where: - Weighted Value = Sum of (Token Balance * Price * Liquidation Threshold) - Total Debt = Principal + Accrued Interest + Quota Fees ``` Health factor is scaled by 10000, so `healthFactor < 10000` means liquidatable. ### The Liquidation Process 1. **Liquidator calls** `creditFacade.liquidateCreditAccount()` 2. **Protocol converts** collateral to underlying token 3. **Debt is repaid** from converted collateral 4. **Liquidator receives** premium (configured per Credit Manager) 5. **Remaining funds** go to account owner (if any) The liquidator provides the multicall that handles collateral conversion. This is where profit comes from - efficient routing means better conversion rates. *** ## Finding Liquidatable Accounts **WHY:** Efficiently scan all accounts to find opportunities. ### Using CreditAccountCompressor The `CreditAccountCompressor` has built-in health factor filtering: ```typescript import { creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); // Find accounts with HF < 1.0 (10000 in basis points) const [accounts, total] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', // Any owner minHealthFactor: 0n, maxHealthFactor: 10000n, // HF < 1.0 includeZeroDebt: false, reverting: false, }, 0n, // offset ], }); console.log(`Found ${accounts.length} accounts with HF < 1.0`); ``` ### Filter by isLiquidatable The `isLiquidatable` field accounts for additional protocol checks: ```typescript const liquidatable = accounts.filter(a => a.isLiquidatable); console.log(`${liquidatable.length} are actually liquidatable`); for (const account of liquidatable) { console.log(`Account: ${account.addr}`); console.log(` Health Factor: ${Number(account.healthFactor) / 10000}`); console.log(` Debt: ${account.debt}`); console.log(` Collaterals:`); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}: ${token.balance}`); } } } ``` ### Pagination for Large Result Sets The compressor returns paginated results. Iterate through all pages: ```typescript async function getAllLiquidatableAccounts( creditManager: `0x${string}` ): Promise { const filter = { owner: '0x0000000000000000000000000000000000000000' as const, minHealthFactor: 0n, maxHealthFactor: 10000n, includeZeroDebt: false, reverting: false, }; let offset = 0n; let allAccounts: CreditAccountData[] = []; while (true) { const [accounts, total] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [creditManager, filter, offset], }); const liquidatable = accounts.filter(a => a.isLiquidatable); allAccounts.push(...liquidatable); offset += BigInt(accounts.length); if (offset >= total) break; } return allAccounts; } ``` *** ## Account Analysis **WHY:** Understand an account's composition before liquidating. ### Collateral Breakdown ```typescript interface CollateralPosition { token: string; symbol: string; balance: bigint; valueInUnderlying: bigint; liquidationThreshold: number; } function analyzeCollateral(account: CreditAccountData): CollateralPosition[] { return account.tokens .filter(t => t.balance > 0n) .map(t => ({ token: t.token, symbol: t.symbol, balance: t.balance, valueInUnderlying: t.balanceInUnderlying, liquidationThreshold: Number(t.lt) / 100, })) .sort((a, b) => Number(b.valueInUnderlying - a.valueInUnderlying)); } const positions = analyzeCollateral(account); console.log('Collateral by value:'); for (const pos of positions) { console.log(` ${pos.symbol}: ${pos.valueInUnderlying} (LT: ${pos.liquidationThreshold}%)`); } ``` ### Estimating Profit ```typescript interface LiquidationEstimate { totalCollateralValue: bigint; debt: bigint; liquidationPremium: bigint; estimatedProfit: bigint; } function estimateLiquidation( account: CreditAccountData, premiumBps: number // e.g., 400 = 4% ): LiquidationEstimate { const totalValue = account.tokens.reduce( (sum, t) => sum + t.balanceInUnderlying, 0n ); const premium = totalValue * BigInt(premiumBps) / 10000n; // Simplified: assumes perfect conversion const estimatedProfit = totalValue - account.debt; return { totalCollateralValue: totalValue, debt: account.debt, liquidationPremium: premium, estimatedProfit: estimatedProfit > 0n ? estimatedProfit : 0n, }; } ``` *** ## Building the Liquidation Multicall **WHY:** The multicall handles collateral conversion and determines profit. ### Basic Structure A liquidation multicall typically: 1. Updates stale price feeds (if needed) 2. Swaps collateral tokens to underlying 3. Repays debt (handled by protocol) ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Build liquidation multicall const calls: Array<{ target: `0x${string}`; callData: `0x${string}` }> = []; // 1. Update any stale price feeds first for (const feed of stalePriceFeeds) { calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [feed.token, feed.reserve, feed.data], }), }); } // 2. Swap collateral to underlying via adapters for (const collateral of collateralToSwap) { const adapter = await creditManager.read.contractToAdapter([ collateral.protocol, ]); calls.push({ target: adapter, callData: encodeFunctionData({ abi: adapterAbi, functionName: 'swap', args: [collateral.swapParams], }), }); } ``` ### Using Slippage Protection Always protect against sandwich attacks: ```typescript // Store expected minimum output calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: underlyingToken, amount: minExpectedOutput }]], }), }); // Perform swap calls.push({ target: adapter, callData: encodeFunctionData({ abi: adapterAbi, functionName: 'swap', args: [swapParams], }), }); // Verify slippage calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }); ``` See [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) for details. *** ## Executing Liquidation **WHY:** Actually perform the liquidation and capture profit. ### The liquidateCreditAccount Call ```typescript // Get credit facade for the account's credit manager const market = sdk.marketRegister.findByCreditManager(account.creditManager); // Execute liquidation const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [ account.addr, // Credit account to liquidate receiverAddress, // Where to send remaining funds calls, // Liquidation multicall ], }); console.log(`Liquidation submitted: ${hash}`); // Wait for confirmation const receipt = await client.waitForTransactionReceipt({ hash }); console.log(`Liquidation ${receipt.status === 'success' ? 'succeeded' : 'failed'}`); ``` ### Handling Partial Liquidation In some configurations, partial liquidation is possible. Check the Credit Manager configuration: ```typescript // Full liquidation only if account is deeply underwater // Partial liquidation may be allowed above certain HF threshold ``` *** ## Bot Architecture **WHY:** Production bots need proper design for reliability and competitiveness. ### Monitoring Loop ```typescript async function monitoringLoop() { const POLL_INTERVAL = 3000; // 3 seconds while (true) { try { // Scan all credit managers for (const cm of creditManagers) { const accounts = await getAllLiquidatableAccounts(cm); for (const account of accounts) { // Analyze opportunity const estimate = estimateLiquidation(account, liquidationPremiumBps); if (estimate.estimatedProfit > minProfitThreshold) { await attemptLiquidation(account); } } } } catch (error) { console.error('Monitoring error:', error); } await sleep(POLL_INTERVAL); } } ``` ### Simulation Before Execution Always simulate before sending transactions: ```typescript async function attemptLiquidation(account: CreditAccountData) { const calls = buildLiquidationMulticall(account); // Simulate first try { await client.simulateContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], account: liquidatorAddress, }); } catch (error) { console.log(`Simulation failed for ${account.addr}:`, error); return; } // Simulation passed, execute try { const hash = await walletClient.writeContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], }); console.log(`Liquidation tx: ${hash}`); } catch (error) { console.error(`Execution failed:`, error); } } ``` ### Competition Considerations Liquidation is competitive. Other bots are scanning the same accounts. **Strategies:** * **Speed:** Use faster RPC endpoints, optimize code paths * **Gas:** Pay higher gas for priority (use `maxPriorityFeePerGas`) * **Efficiency:** Better swap routing means higher profit, can afford more gas * **Flashbots:** Use MEV-protected submission to avoid frontrunning ```typescript // Higher priority fee for competitive liquidations const hash = await walletClient.writeContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], maxPriorityFeePerGas: parseGwei('3'), // Higher tip }); ``` *** ## Complete Example: Simple Liquidation Bot ```typescript import { createPublicClient, createWalletClient, http, parseGwei } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { mainnet } from 'viem/chains'; import { GearboxSDK, creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const MIN_PROFIT_USD = 100n * 10n ** 6n; // $100 minimum profit async function runLiquidationBot(creditManagerAddress: `0x${string}`) { const client = createPublicClient({ chain: mainnet, transport: http(), }); const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const walletClient = createWalletClient({ account, chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); const market = sdk.marketRegister.findByCreditManager(creditManagerAddress); console.log(`Monitoring ${market.creditManagers[0].address}`); console.log(`Liquidator: ${account.address}`); while (true) { try { // Find liquidatable accounts const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', minHealthFactor: 0n, maxHealthFactor: 10000n, includeZeroDebt: false, reverting: false, }, 0n, ], }); const liquidatable = accounts.filter(a => a.isLiquidatable); if (liquidatable.length > 0) { console.log(`Found ${liquidatable.length} liquidatable accounts`); for (const target of liquidatable) { const totalValue = target.tokens.reduce( (sum, t) => sum + t.balanceInUnderlying, 0n ); const estimatedProfit = totalValue - target.debt; if (estimatedProfit > MIN_PROFIT_USD) { console.log(`Profitable opportunity: ${target.addr}`); console.log(` Debt: ${target.debt}`); console.log(` Value: ${totalValue}`); console.log(` Est. Profit: ${estimatedProfit}`); // Build and execute liquidation // (simplified - real bot would compute optimal swaps) const calls = buildLiquidationCalls(target, market); try { // Simulate await client.simulateContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [target.addr, account.address, calls], account: account.address, }); // Execute const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [target.addr, account.address, calls], maxPriorityFeePerGas: parseGwei('2'), }); console.log(`Liquidation submitted: ${hash}`); } catch (error) { console.log(`Failed to liquidate ${target.addr}:`, error); } } } } } catch (error) { console.error('Loop error:', error); } // Poll every 3 seconds await new Promise(resolve => setTimeout(resolve, 3000)); } } function buildLiquidationCalls( account: CreditAccountData, market: MarketData ): Array<{ target: `0x${string}`; callData: `0x${string}` }> { const calls: Array<{ target: `0x${string}`; callData: `0x${string}` }> = []; const creditFacade = market.creditFacade.address; const underlying = market.pool.underlying.address; // 1. Swap each non-underlying collateral token to underlying via adapter for (const token of account.tokens) { if (token.balance <= 1n) continue; // skip dust if (token.token === underlying) continue; // skip underlying itself // Use Uniswap V3 adapter for swaps (simplified: hardcoded router) const uniswapAdapter = market.adapters?.['UNISWAP_V3_ROUTER']; if (!uniswapAdapter) continue; // exactAllInputSingle swaps entire balance minus 1 wei calls.push({ target: uniswapAdapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactAllInputSingle', args: [{ tokenIn: token.token, tokenOut: underlying, fee: 3000, // 0.3% pool (use 500 for stablecoin pairs) deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), rateMinRAY: 0n, // No slippage protection (simplified) sqrtPriceLimitX96: 0n, }], }), }); } return calls; } ``` *** ## Gotchas ### Price Updates Must Come First If any price feeds are stale, update them at the start of your multicall: ```typescript // WRONG: Swap first, then update prices (will fail) // CORRECT: Update prices first, then swap const calls = [ ...priceUpdateCalls, ...swapCalls, ]; ``` See [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds). ### Account State Can Change Between scanning and executing, another bot may liquidate the account: ```typescript try { await walletClient.writeContract({ ... }); } catch (error) { if (error.message.includes('account not liquidatable')) { console.log('Account already liquidated by another bot'); } } ``` ### Gas Estimation Liquidation gas costs vary based on: * Number of collateral tokens * Complexity of swaps * Price feed updates needed Always estimate gas before calculating profitability. *** ## Next Steps * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Swap patterns via adapters * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protect against MEV * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - Required for stale oracles * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete filter options ## Updating Quotas Source: https://docs.gearbox.finance/developers/gm-mc-quotas File: content/developers/gm-mc-quotas.mdx Enable or adjust exposure to quota-based collateral tokens. Without a quota, even holding a quota token contributes zero to your health factor. ## Why You update quotas when: * **Enabling a quota token** - Required before that token counts as collateral * **Increasing exposure** - Need more of a token to count toward health factor * **Reducing exposure** - Lower quota to reduce quota interest costs * **Closing positions** - Zero quotas before full debt repayment ## What `updateQuota` changes your quota for a specific token: 1. If increasing from zero, the token is enabled as collateral 2. If decreasing to zero, the token is disabled as collateral 3. Quota increase may be limited by global capacity (per-pool limits) 4. Quota interest accrues based on your quota amount **Key parameters:** | Parameter | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `token` | `address`| The quota token address | | `quotaChange` | `int96` | Delta to apply (positive = increase, negative = decrease) | | `minQuota` | `uint96` | Minimum acceptable resulting quota (prevents partial fills) | The `minQuota` parameter protects you: if the pool can only give you 80% of your requested quota, and you set `minQuota` to 100% of your request, the transaction reverts instead of accepting partial quota. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Request 50,000 USDC worth of quota const quotaAmount = 50_000n * 10n ** 6n; const calls = [ service.prepareUpdateQuota(wethAddress, quotaAmount, quotaAmount), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Decrease Quota ```typescript // Decrease quota by 20,000 (negative change) const decrease = -20_000n * 10n ** 6n; const calls = [ service.prepareUpdateQuota(wethAddress, decrease, 0n), ]; ``` ### Zero Quota Entirely Pass `type(int96).min` to disable quota completely: ```typescript // int96 minimum value const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), ]; ``` ### Common Pattern: Enable Quota After Swap After swapping into a quota token, you need to enable quota for it to count: ```typescript const calls = [ // Swap USDC to WETH via adapter { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // Enable quota for the received WETH service.prepareUpdateQuota(wethAddress, quotaAmount, quotaAmount), ]; ``` ## Gotchas ### Check Quota Limits Before Requesting Each quota token has a pool-wide limit. If the limit is reached, your request fails (or gets partial fill): ```typescript // Check available quota capacity const quotaKeeper = market.quotaKeeper; const quotaInfo = await quotaKeeper.read.getQuotaInfo([wethAddress]); const available = quotaInfo.limit - quotaInfo.totalQuoted; if (requested > available) { console.log(`Only ${available} quota available, requested ${requested}`); } ``` ### minQuota Prevents Partial Fills If you need exactly 100 units of quota: ```typescript // SAFE - reverts if less than 100 available service.prepareUpdateQuota(token, 100n, 100n); // RISKY - accepts partial fill service.prepareUpdateQuota(token, 100n, 0n); ``` ### Per-Account Quota Maximum Each account has an implicit max quota of `8 * maxDebt` per asset. You cannot exceed this even if pool capacity exists. ### Zero Quotas Before Zero Debt You cannot have active quotas with zero debt. When closing an account: ```typescript const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ // Zero ALL quotas first service.prepareUpdateQuota(token1, INT96_MIN, 0n), service.prepareUpdateQuota(token2, INT96_MIN, 0n), // Then repay debt service.prepareDecreaseDebt(MAX_UINT256), ]; ``` ### Cannot Update Quotas on Zero-Debt Account If your account has zero debt, quota updates fail. You must have active debt to hold quotas. ### Quota Tokens vs Non-Quota Tokens Not all tokens are quota tokens. Non-quota tokens are enabled/disabled via `enableToken`/`disableToken` and don't require quota to count as collateral. Check if a token is quota-based by examining the Credit Manager configuration. ### Forbidden Tokens Block Quota Increases If your account has forbidden tokens enabled, you cannot increase any quotas. Disable forbidden tokens first. ## Learn More * [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) - Quotas require active debt * [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) - Often combined with quota updates * [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) - For non-quota token enable/disable ## Health Factor Monitoring Source: https://docs.gearbox.finance/developers/health-factor-monitoring File: content/developers/health-factor-monitoring.mdx Track credit account health factors over time and alert on liquidation risk. ## Overview Health factor monitoring needs to: 1. Query current health factor for accounts 2. Track changes over time 3. Alert when accounts approach liquidation 4. Provide actionable data for position management This guide covers each step with SDK patterns. *** ## Understanding Health Factor **WHY:** Know what you're measuring before building monitoring. ### The Formula ``` Health Factor = Total Weighted Value / Total Debt Where: - Weighted Value = Sum of (Token Balance * Price * Liquidation Threshold) - Total Debt = Principal + Accrued Interest + Quota Fees ``` Health factor is scaled by 10000 in the protocol. `healthFactor = 10000` means HF = 1.0. ### Risk Thresholds | Health Factor | Status | Action | | ----------------------- | ------------ | ------------------------------------- | | > 1.5 (15000) | Healthy | No action needed | | 1.1 - 1.5 (11000-15000) | Moderate | Monitor more frequently | | 1.0 - 1.1 (10000-11000) | Critical | Alert user, suggest adding collateral | | < 1.0 (< 10000) | Liquidatable | Account can be liquidated | ### What Moves Health Factor Health factor changes when: * **Token prices change** (most common) - market moves affect collateral values * **Interest accrues** - debt grows over time, reducing HF * **Quota fees accumulate** - adds to total debt * **User actions** - adding/removing collateral, borrowing/repaying *** ## Querying Current Health Factor **WHY:** Get a snapshot of account health for display or alerting. ### Single Account ```typescript import { creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); // Get specific account data const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: userAddress, minHealthFactor: 0n, maxHealthFactor: 65535n, // All HF values includeZeroDebt: false, reverting: false, }, 0n, ], }); for (const account of accounts) { const hf = Number(account.healthFactor) / 10000; console.log(`Account ${account.addr}: HF = ${hf.toFixed(4)}`); } ``` ### All Accounts at Risk Filter for accounts approaching liquidation: ```typescript // Accounts with HF between 1.0 and 1.1 (at risk but not yet liquidatable) const [atRiskAccounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', minHealthFactor: 10000n, // HF >= 1.0 maxHealthFactor: 11000n, // HF < 1.1 includeZeroDebt: false, reverting: false, }, 0n, ], }); console.log(`${atRiskAccounts.length} accounts in critical range`); ``` *** ## Continuous Monitoring **WHY:** Health factors change with every block as prices move and interest accrues. ### Polling Pattern ```typescript interface HealthSnapshot { account: string; healthFactor: number; debt: bigint; totalValue: bigint; timestamp: number; } async function monitorAccounts( creditManager: `0x${string}`, owner: `0x${string}`, onAlert: (snapshot: HealthSnapshot) => void ) { const POLL_INTERVAL = 5000; // 5 seconds const ALERT_THRESHOLD = 1.1; // Alert when HF < 1.1 while (true) { try { const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManager, { owner, minHealthFactor: 0n, maxHealthFactor: 65535n, includeZeroDebt: false, reverting: false, }, 0n, ], }); for (const account of accounts) { const hf = Number(account.healthFactor) / 10000; const totalValue = account.tokens.reduce( (sum, t) => sum + t.balanceInUnderlying, 0n ); const snapshot: HealthSnapshot = { account: account.addr, healthFactor: hf, debt: account.debt, totalValue, timestamp: Date.now(), }; if (hf < ALERT_THRESHOLD) { onAlert(snapshot); } } } catch (error) { console.error('Monitor error:', error); } await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } } ``` ### Event-Driven Updates For more efficient monitoring, watch for events that affect health factor: ```typescript // Watch for multicall completions (position changes) const unwatchMulticall = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'FinishMultiCall', onLogs: async (logs) => { for (const log of logs) { // Refresh HF for affected account await refreshHealthFactor(log.args.creditAccount); } }, }); // Watch for liquidations const unwatchLiquidation = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'LiquidateCreditAccount', onLogs: async (logs) => { for (const log of logs) { console.log(`Account ${log.args.creditAccount} was liquidated`); } }, }); ``` *** ## Health Factor Breakdown **WHY:** Understanding why HF is low helps users take the right corrective action. ### Decomposing Health Factor ```typescript interface HealthBreakdown { healthFactor: number; totalWeightedValue: bigint; totalDebt: bigint; principal: bigint; accruedInterest: bigint; quotaFees: bigint; topCollaterals: Array<{ symbol: string; balance: bigint; valueInUnderlying: bigint; liquidationThreshold: number; weightedContribution: bigint; }>; } function analyzeHealthFactor(account: CreditAccountData): HealthBreakdown { const tokens = account.tokens .filter(t => t.balance > 0n) .map(t => ({ symbol: t.symbol, balance: t.balance, valueInUnderlying: t.balanceInUnderlying, liquidationThreshold: Number(t.lt) / 10000, weightedContribution: t.balanceInUnderlying * BigInt(t.lt) / 10000n, })) .sort((a, b) => Number(b.weightedContribution - a.weightedContribution)); const totalWeightedValue = tokens.reduce( (sum, t) => sum + t.weightedContribution, 0n ); const quotaInterest = account.cumulativeQuotaInterest; const quotaFees = account.quotaFees; const baseInterest = account.debt - account.borrowedAmount - quotaInterest - quotaFees; return { healthFactor: Number(account.healthFactor) / 10000, totalWeightedValue, totalDebt: account.debt, principal: account.borrowedAmount, accruedInterest: baseInterest, quotaFees, topCollaterals: tokens, }; } // Usage const breakdown = analyzeHealthFactor(account); console.log(`Health Factor: ${breakdown.healthFactor.toFixed(4)}`); console.log(`Debt: ${breakdown.totalDebt} (principal: ${breakdown.principal})`); console.log(`Interest: ${breakdown.accruedInterest}, Quota fees: ${breakdown.quotaFees}`); console.log('Collateral contributions:'); for (const col of breakdown.topCollaterals) { console.log(` ${col.symbol}: ${col.weightedContribution} (LT: ${(col.liquidationThreshold * 100).toFixed(1)}%)`); } ``` *** ## Alerting Strategies **WHY:** Different users need different alert thresholds and delivery methods. ### Tiered Alerts ```typescript type AlertLevel = 'info' | 'warning' | 'critical' | 'liquidatable'; function classifyRisk(healthFactor: number): AlertLevel { if (healthFactor < 1.0) return 'liquidatable'; if (healthFactor < 1.05) return 'critical'; if (healthFactor < 1.1) return 'warning'; return 'info'; } interface AlertConfig { account: `0x${string}`; creditManager: `0x${string}`; thresholds: { warning: number; // e.g. 1.2 critical: number; // e.g. 1.1 }; cooldown: number; // milliseconds between repeat alerts } const lastAlertTime = new Map(); function shouldAlert( account: string, level: AlertLevel, config: AlertConfig ): boolean { if (level === 'info') return false; const key = `${account}-${level}`; const lastTime = lastAlertTime.get(key) ?? 0; const now = Date.now(); if (now - lastTime < config.cooldown) return false; lastAlertTime.set(key, now); return true; } ``` ### Suggested Actions per Level ```typescript function suggestAction(breakdown: HealthBreakdown, level: AlertLevel): string { switch (level) { case 'warning': return 'Consider adding collateral or reducing debt'; case 'critical': return `Add at least ${formatValue(breakdown.totalDebt / 10n)} collateral immediately`; case 'liquidatable': return 'Account is liquidatable. Add collateral or repay debt NOW'; default: return 'Position is healthy'; } } ``` *** ## Complete Example: Health Monitor Service ```typescript import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; import { GearboxSDK, creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const WARNING_HF = 1.15; const CRITICAL_HF = 1.05; const POLL_INTERVAL = 10_000; // 10 seconds async function runHealthMonitor( creditManagerAddress: `0x${string}`, ownerAddress: `0x${string}` ) { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); let previousHFs = new Map(); while (true) { try { const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: ownerAddress, minHealthFactor: 0n, maxHealthFactor: 65535n, includeZeroDebt: false, reverting: false, }, 0n, ], }); for (const account of accounts) { const hf = Number(account.healthFactor) / 10000; const prevHF = previousHFs.get(account.addr); previousHFs.set(account.addr, hf); // Determine direction const direction = prevHF !== undefined ? (hf > prevHF ? 'improving' : hf < prevHF ? 'declining' : 'stable') : 'initial'; // Classify and alert if (hf < 1.0) { console.log(`LIQUIDATABLE: ${account.addr} HF=${hf.toFixed(4)} [${direction}]`); } else if (hf < CRITICAL_HF) { console.log(`CRITICAL: ${account.addr} HF=${hf.toFixed(4)} [${direction}]`); } else if (hf < WARNING_HF) { console.log(`WARNING: ${account.addr} HF=${hf.toFixed(4)} [${direction}]`); } else if (direction !== 'stable') { console.log(`OK: ${account.addr} HF=${hf.toFixed(4)} [${direction}]`); } } } catch (error) { console.error('Monitor error:', error); } await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } } runHealthMonitor('0x...', '0x...').catch(console.error); ``` *** ## Gotchas ### Stale Price Feeds If on-demand price feeds (Pyth, Redstone) haven't been updated recently, the health factor from compressors may not reflect current market prices. For accurate monitoring: ```typescript // Check if any feeds need updating const feeds = await client.readContract({ address: priceFeedCompressor, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [priceOracleAddress], }); const staleFeeds = feeds.filter(f => f.needsUpdate); if (staleFeeds.length > 0) { console.log(`Warning: ${staleFeeds.length} price feeds are stale`); } ``` ### Health Factor Precision Health factor is an integer scaled by 10000. Small changes (e.g., 10001 to 10000) can cross the liquidation boundary. Always use precise comparison: ```typescript // WRONG: floating point comparison if (hf < 1.0) { ... } // CORRECT: compare raw values if (account.healthFactor < 10000n) { ... } ``` ### Interest Accumulation Health factor decreases over time even without price changes, because interest accrues continuously. Factor this into alert timing - an account at HF 1.05 today may be liquidatable tomorrow purely from interest. *** ## Next Steps * [Frontend Applications](https://docs.gearbox.finance/developers/frontend-applications) - Display health data in a UI * [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) - Act on liquidatable accounts * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Full compressor API * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Repay debt to improve health * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Add collateral to improve health ## Withdrawing Collateral Source: https://docs.gearbox.finance/developers/gm-mc-withdraw File: content/developers/gm-mc-withdraw.mdx Remove tokens from your credit account. Withdrawals decrease your health factor since you are removing value from the account. ## Why You withdraw collateral when: * **Taking profit** - Extract gains while keeping the position open * **Rebalancing** - Move assets between Credit Account and wallet * **Closing positions** - Extract remaining value after repaying debt * **Emergency exit** - Quickly reduce exposure ## What `withdrawCollateral` transfers tokens from Credit Account to a specified address: 1. Token is transferred from Credit Account to `to` address 2. If balance goes to zero, token is auto-disabled 3. **Safe pricing** is triggered for the final collateral check **Safe pricing** is critical to understand: when any withdrawal occurs in a multicall, the final health check uses `min(mainPrice, reservePrice)` for ALL collateral. This can cause withdrawals to fail even when the account looks healthy based on main prices alone. ## How ```typescript import { GearboxSDK, createCreditAccountService } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Withdraw 5,000 USDC to your wallet const calls = [ service.prepareWithdrawCollateral( usdcAddress, 5_000n * 10n ** 6n, myWalletAddress, ), ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Withdraw Entire Balance Pass max uint256 to withdraw all of a token: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const calls = [ service.prepareWithdrawCollateral(usdcAddress, MAX_UINT256, myWalletAddress), ]; ``` ### Withdraw to Different Address The `to` parameter can be any address: ```typescript const calls = [ service.prepareWithdrawCollateral( usdcAddress, amount, recipientAddress, // Can be different from caller ), ]; ``` ### Common Pattern: Repay + Withdraw After repaying debt, withdraw remaining funds: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quotas service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), // Repay all debt service.prepareDecreaseDebt(MAX_UINT256), // Withdraw remaining collateral service.prepareWithdrawCollateral(usdcAddress, MAX_UINT256, myWalletAddress), service.prepareWithdrawCollateral(wethAddress, MAX_UINT256, myWalletAddress), ]; ``` ## Gotchas ### Safe Pricing Can Block Withdrawals When you withdraw, ALL collateral is valued at `min(mainPrice, reservePrice)`: ``` Regular check: collateral valued at main price Withdrawal: collateral valued at min(main, reserve) ``` An account that looks healthy at main prices may fail the withdrawal check at safe prices. For example, if main price is \$100 and reserve price is \$80, your account might have HF 1.2 normally but only HF 0.96 under safe pricing. **Workaround:** Add extra collateral buffer or reduce debt before withdrawing if you're close to the threshold. ### Forbidden Tokens Block Withdrawals If your account has forbidden tokens enabled, withdrawals are prohibited. You must disable forbidden tokens first (usually by swapping them away). ### Token Auto-Disables at Zero Balance When you withdraw the entire balance of a token: * Non-quota tokens are auto-disabled * Quota tokens remain enabled until quota is zeroed ### Reserve Price May Be Zero Some tokens have a reserve price of zero (untrusted tokens). Any withdrawal will fail if such tokens are enabled, because their value becomes zero under safe pricing. ### Withdrawal Doesn't Auto-Disable Quota Tokens Unlike non-quota tokens, quota tokens remain enabled even at zero balance. You must explicitly zero the quota: ```typescript const MAX_UINT256 = 2n ** 256n - 1n; const INT96_MIN = BigInt.asIntN(96, -1n * 2n ** 95n); const calls = [ // Zero quota first service.prepareUpdateQuota(wethAddress, INT96_MIN, 0n), // Then withdraw service.prepareWithdrawCollateral(wethAddress, MAX_UINT256, myWalletAddress), ]; ``` ### Can't Withdraw Below Minimum Debt After withdrawal, your account must still satisfy debt constraints. If withdrawal would leave you with debt between 0 and `minDebt`, it fails. ## Learn More * [Adding Collateral](https://docs.gearbox.finance/developers/gm-mc-collateral) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/gm-mc-debt) - Often combined with withdrawals * [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) - Zero quotas before withdrawing quota tokens ## External Calls Source: https://docs.gearbox.finance/developers/gm-mc-external File: content/developers/gm-mc-external.mdx Interact with external protocols (Uniswap, Curve, Yearn, etc.) from your credit account through whitelisted adapter contracts. ## Why You make external calls when: * **Swapping tokens** - Trade via Uniswap, Curve, or other DEXs * **Depositing to vaults** - Stake in Yearn, Lido, or yield strategies * **Managing LP positions** - Add/remove liquidity on various protocols * **Executing complex strategies** - Chain multiple protocol interactions ## What External calls flow through adapters: 1. You encode a call targeting an adapter address 2. Credit Facade routes the call to the adapter 3. Adapter builds the actual calldata for the external protocol 4. Adapter requests token approvals if needed 5. Credit Manager executes the call from the Credit Account 6. Credit Account acts as the "user" from the external protocol's perspective 7. Adapter returns which tokens to enable/disable based on the operation **Key insight:** The Credit Account makes the actual call, so it receives the output tokens directly. You never touch the funds - they stay in the Credit Account. ## How ### Step 1: Get Adapter Address ```typescript import { getContract } from 'viem'; import { creditManagerAbi } from '@gearbox-protocol/sdk'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get adapter for a protocol (e.g., Uniswap V3 Router) const uniswapV3Adapter = await creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); // Returns 0x0 if no adapter exists for this protocol if (uniswapV3Adapter === '0x0000000000000000000000000000000000000000') { throw new Error('No adapter for this protocol'); } ``` ### Step 2: Encode the Adapter Call ```typescript import { encodeFunctionData } from 'viem'; import { uniswapV3AdapterAbi } from '@gearbox-protocol/integrations-v3'; const swapParams = { tokenIn: usdcAddress, tokenOut: wethAddress, fee: 500, recipient: '0x0000000000000000000000000000000000000000', // Adapter overrides this deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), amountIn: 50_000n * 10n ** 6n, amountOutMinimum: 24n * 10n ** 18n, // Slippage protection sqrtPriceLimitX96: 0n, }; const calls = [ { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, ]; ``` ### Complete Example: Swap with Slippage Protection ```typescript import { encodeFunctionData } from 'viem'; import { GearboxSDK, createCreditAccountService, iCreditFacadeV300MulticallAbi, } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); const service = createCreditAccountService(sdk, 310); const market = sdk.marketRegister.findByCreditManager(cmAddress); // Get adapter const uniswapV3Adapter = await market.creditManager.read.contractToAdapter([ UNISWAP_V3_ROUTER, ]); const calls = [ // Add collateral (SDK helper) service.prepareAddCollateral(usdcAddress, 50_000n * 10n ** 6n), // Borrow (SDK helper) service.prepareIncreaseDebt(200_000n * 10n ** 6n), // Slippage protection start { target: market.creditFacade.address, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: wethAddress, amount: 99n * 10n ** 18n }]], }), }, // Swap via adapter (manual encoding) { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // Slippage protection end { target: market.creditFacade.address, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, // Set quota for received token (SDK helper) service.prepareUpdateQuota(wethAddress, 250_000n * 10n ** 6n, 250_000n * 10n ** 6n), ]; // Approve collateral to Credit Manager await usdcContract.write.approve([market.creditManager.address, 50_000n * 10n ** 6n]); // Execute await market.creditFacade.write.openCreditAccount([ownerAddress, calls, 0n]); ``` ### Diff Functions Many adapters have `_diff` variants that operate on "entire balance minus 1": ```typescript // Instead of specifying exact amount... { functionName: 'deposit', args: [exactAmount] } // Use diff to deposit all USDC (minus 1 wei) { functionName: 'depositDiff', args: [1n] } ``` This is useful when you don't know the exact balance after previous operations. ## Gotchas ### Adapter ABIs Need Separate Import SDK exports core ABIs, but adapter ABIs often need a separate import: ```typescript // Core ABIs from SDK import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Adapter ABIs from integrations package import { uniswapV3AdapterAbi } from '@gearbox-protocol/integrations-v3'; ``` ### Not All Protocols Have Adapters An adapter must exist for each protocol you want to interact with. Check with `contractToAdapter` - it returns the zero address if no adapter exists. ### Recipient Parameter is Overridden Many DEX functions have a `recipient` parameter. Adapters override this to ensure tokens go to the Credit Account, not an arbitrary address. You can pass any value. ### Always Use Slippage Protection External calls are vulnerable to sandwich attacks. Always wrap swaps with slippage checks using `storeExpectedBalances` / `compareBalances`. ### Token Enable/Disable is Automatic After adapter calls, tokens are automatically enabled/disabled based on balance changes. You usually don't need manual `enableToken`/`disableToken` after adapter calls. ## Learn More * [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) - Protect your swaps * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Combining SDK helpers with manual encoding * [Token Management](https://docs.gearbox.finance/developers/gm-mc-tokens) - Manual token management ## Overview Source: https://docs.gearbox.finance/developers/overview-2 File: content/developers/overview-2.mdx Integrate directly with Gearbox contracts from your Solidity code. This guide covers on-chain integration patterns for smart contract developers. ## Prerequisites * Solidity 0.8.x experience * Familiarity with interface-based contract interaction * Understanding of ERC-20 and common DeFi patterns ## What You'll Learn | Topic | Description | | ------------------ | ------------------------------------------------------------ | | Credit Accounts | Contract discovery, CreditFacade, CreditManager interactions | | Multicall Encoding | Build and execute multicalls in Solidity | | Pool Operations | Deposit, withdraw, and read pool state | ## Guide Structure 1. [**Credit Accounts**](https://docs.gearbox.finance/developers/credit-accounts) - Contract discovery, ICreditFacadeV3, ICreditManagerV3 interfaces 2. [**Multicalls**](https://docs.gearbox.finance/developers/multicalls) - MultiCall struct encoding, adapter calls 3. [**Pool Operations**](https://docs.gearbox.finance/developers/pool-operations) - IPoolV3 deposit/withdraw, ERC-4626 functions Note: Contract discovery patterns (AddressProvider, ContractsRegister) are covered in the Credit Accounts guide. ## Key Interfaces ```solidity // Core interfaces you'll use import {IAddressProviderV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {IPoolV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolV3.sol"; ``` ## When to Use Solidity Integration | Use Case | Recommended | | ----------------------------- | ------------------ | | On-chain protocol integration | Yes | | Building adapters | Yes | | Composable strategies | Yes | | Backend services | No (use SDK Guide) | | Frontend applications | No (use SDK Guide) | For TypeScript/JavaScript applications, see the [SDK Guide](https://docs.gearbox.finance/developers/overview). ## Architecture Understanding For conceptual background on how Gearbox works: * [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite) - How Credit Managers, Facades, and Configurators work together * [Pool Architecture](https://docs.gearbox.finance/developers/pools) - Lending pools and ERC-4626 compliance * [Multicall System](https://docs.gearbox.finance/developers/multicall-system) - How multicalls execute and validate *** ## Detailed Guides ### Multicall Operations Complete reference for each multicall operation with Solidity examples: | Operation | Description | | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | Transfer tokens to credit account | | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | Borrow and repay | | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | Manage collateral quotas | | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | Remove tokens from account | | [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) | Protect against price movement | | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | Interact with DeFi protocols via adapters | | [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) | Manage active collateral | | [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) | On-demand oracle updates (Pyth, Redstone) | | [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) | Optimize health checks with hints | | [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) | Security cleanup | See [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) for the diff pattern and complete encoding examples. ### Use Case Guides Integration-specific guides for common development scenarios: | Building | Guide | Focus | | -------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- | | New DeFi Adapter | [Adapter Development](https://docs.gearbox.finance/developers/adapter-development) | AbstractAdapter, security patterns, diff functions | | Strategy Contract | [Protocol Integration](https://docs.gearbox.finance/developers/protocol-integration) | Multicall building, access control, automation | | Core Extensions | [Core Extension](https://docs.gearbox.finance/developers/core-extension) | Extending core contracts, advanced customizations | | Liquidation Contract | [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) | On-chain liquidation, flash loans, keeper integration | ## Credit Accounts Source: https://docs.gearbox.finance/developers/credit-accounts-2 File: content/developers/credit-accounts-2.mdx Manage leveraged positions on Gearbox from Solidity. This guide covers discovering markets, opening accounts, managing positions, and monitoring health. > For SDK credit account operations, see [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts). ## Discovering Markets Find credit managers and their facades programmatically using ContractsRegister: ```solidity import {IAddressProviderV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; IAddressProviderV3 ap = IAddressProviderV3(ADDRESS_PROVIDER); address cr = ap.getAddressOrRevert("CONTRACTS_REGISTER", 3_10); // Get all credit managers address[] memory allCMs = IContractsRegister(cr).getCreditManagers(); ``` **Example:** Find USDC credit managers ```solidity function findUSDCCreditManagers(address addressProvider, address usdc) external view returns (address[] memory facades) { IAddressProviderV3 ap = IAddressProviderV3(addressProvider); address cr = ap.getAddressOrRevert("CONTRACTS_REGISTER", 3_10); address[] memory allCMs = IContractsRegister(cr).getCreditManagers(); uint256 count; for (uint256 i = 0; i < allCMs.length; i++) { if (ICreditManagerV3(allCMs[i]).underlying() == usdc) count++; } facades = new address[](count); uint256 j; for (uint256 i = 0; i < allCMs.length; i++) { ICreditManagerV3 cm = ICreditManagerV3(allCMs[i]); if (cm.underlying() == usdc) { facades[j++] = cm.creditFacade(); } } } ``` ## Opening a Credit Account Open a leveraged position by providing collateral and borrowing funds through the CreditFacade: ```solidity import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; function openCreditAccount( address onBehalfOf, MultiCall[] calldata calls, uint256 referralCode ) external payable returns (address creditAccount); ``` **Example:** ```solidity ICreditFacadeV3 facade = ICreditFacadeV3(facadeAddress); ICreditManagerV3 creditManager = ICreditManagerV3(facade.creditManager()); address underlying = creditManager.underlying(); // Build initial multicall: add collateral + increase debt MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: facadeAddress, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (underlying, 10_000e6) ) }); calls[1] = MultiCall({ target: facadeAddress, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000e6) ) }); // Approve collateral to Credit Manager (not Facade!) IERC20(underlying).approve(address(creditManager), 10_000e6); // Open account address creditAccount = facade.openCreditAccount( msg.sender, // onBehalfOf calls, // initial operations 0 // referralCode ); ``` ### Debt Limits Check borrowing constraints before opening: ```solidity // Get min and max debt allowed (uint128 minDebt, uint128 maxDebt) = facade.debtLimits(); require(borrowAmount >= minDebt && borrowAmount <= maxDebt, "Invalid debt"); ``` ## Managing Active Positions Execute operations on existing credit accounts using multicalls: ```solidity function multicall( address creditAccount, MultiCall[] calldata calls ) external payable; ``` ### Adding Collateral Deposit additional tokens to improve health factor: ```solidity MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (tokenAddress, amount) ) }); // Approve to Credit Manager first IERC20(tokenAddress).approve(address(creditManager), amount); facade.multicall(creditAccount, calls); ``` ### Increasing Debt Borrow more funds from the pool: ```solidity MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (additionalDebt) ) }); facade.multicall(creditAccount, calls); ``` ### Executing Swaps via Adapters Trade collateral through whitelisted protocols. First, discover the adapter: ```solidity // Get adapter for target protocol address uniswapAdapter = creditManager.contractToAdapter(UNISWAP_V3_ROUTER); require(uniswapAdapter != address(0), "Protocol not allowed"); ``` **Example:** Swap using Uniswap V3 adapter ```solidity MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( ISwapRouter.exactInputSingle, ISwapRouter.ExactInputSingleParams({ tokenIn: usdc, tokenOut: weth, fee: 3000, recipient: creditAccount, deadline: block.timestamp, amountIn: 10_000e6, amountOutMinimum: 3e18, sqrtPriceLimitX96: 0 }) ) }); facade.multicall(creditAccount, calls); ``` ### Withdrawing Collateral Remove excess collateral while maintaining health: ```solidity MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (tokenAddress, amount, msg.sender) ) }); facade.multicall(creditAccount, calls); ``` ## Monitoring Account Health Calculate health factor to check liquidation risk: ```solidity import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; enum CollateralCalcTask { GENERIC_PARAMS, // Basic info (debt, cumulative index) DEBT_ONLY, // Detailed debt (base + quota interest) FULL_COLLATERAL_CHECK_LAZY, // Internal use DEBT_COLLATERAL, // Full debt + Total Value (for Health Factor) DEBT_COLLATERAL_SAFE_PRICES // Uses safe pricing } function calcDebtAndCollateral( address creditAccount, CollateralCalcTask task ) external view returns (CollateralDebtData memory cdd); ``` **Example:** ```solidity ICreditManagerV3 cm = ICreditManagerV3(creditManagerAddress); CollateralDebtData memory cdd = cm.calcDebtAndCollateral( creditAccount, CollateralCalcTask.DEBT_COLLATERAL ); // Health factor: 10000 = 100% = HF of 1.0 uint256 healthFactor = (cdd.twvUSD * 10000) / cdd.totalDebtUSD; // Account is liquidatable if HF < 10000 bool liquidatable = healthFactor < 10000; // Account has buffer if HF > 10100 (1% above liquidation) bool safe = healthFactor > 10100; ``` ### CollateralDebtData Structure ```solidity struct CollateralDebtData { uint256 totalDebtUSD; // Total debt in USD uint256 twvUSD; // Total Weighted Value in USD uint256 enabledTokensMask; // Bitmask of enabled tokens uint256 quotedTokensMask; // Bitmask of tokens with quota address[] quotedTokens; // Addresses of quoted tokens address _poolQuotaKeeper; // Pool quota keeper address } ``` ## Closing Positions Close account and return remaining funds after repaying debt: ```solidity function closeCreditAccount( address creditAccount, MultiCall[] calldata calls ) external payable; ``` **Example:** Unwind position and close ```solidity // Build multicall to: // 1. Swap all tokens back to underlying // 2. Repay remaining debt // 3. Withdraw leftover to owner MultiCall[] memory calls = new MultiCall[](3); // Swap collateral token to underlying via adapter calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( ISwapRouter.exactInputSingle, ISwapRouter.ExactInputSingleParams({ tokenIn: weth, tokenOut: underlying, fee: 3000, recipient: creditAccount, deadline: block.timestamp, amountIn: wethBalance, amountOutMinimum: minUnderlyingOut, sqrtPriceLimitX96: 0 }) ) }); // Decrease debt to zero (protocol calculates exact amount) calls[1] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) // max = full repayment ) }); // Withdraw remaining underlying calls[2] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (underlying, type(uint256).max, msg.sender) ) }); facade.closeCreditAccount(creditAccount, calls); ``` ## Configuration and Limits Query market parameters and restrictions: ### Debt Limits ```solidity // Min and max borrowable amount (uint128 minDebt, uint128 maxDebt) = facade.debtLimits(); ``` ### Liquidation Thresholds ```solidity // Get liquidation threshold for specific token (basis points) uint16 lt = creditManager.liquidationThresholds(tokenAddress); // LT of 8000 = 80% = token contributes 80% of value to collateral ``` ### Forbidden Tokens ```solidity // Bitmask of forbidden tokens uint256 forbiddenMask = facade.forbiddenTokenMask(); // Check if specific token mask is forbidden bool isForbidden = (forbiddenMask & tokenMask) != 0; ``` ### Collateral Tokens Enumerate all allowed collateral: ```solidity uint8 tokenCount = creditManager.collateralTokensCount(); for (uint8 i = 0; i < tokenCount; i++) { uint256 mask = 1 << i; (address token, uint16 lt) = creditManager.collateralTokenByMask(mask); // token address and liquidation threshold } ``` ### Fee Configuration ```solidity ( uint16 feeInterest, // Interest fee (bp) uint16 feeLiquidation, // Liquidation fee (bp) uint16 liquidationPremium, // Liquidator premium (bp) uint16 feeLiquidationExpired, // Expired liquidation fee (bp) uint16 liquidationPremiumExpired // Expired liquidator premium (bp) ) = creditManager.fees(); ``` ## Complete Example Full integration showing position lifecycle: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {CollateralDebtData, CollateralCalcTask} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract LeveragedTrading { ICreditFacadeV3 public immutable facade; ICreditManagerV3 public immutable creditManager; address public immutable underlying; constructor(address _facade) { facade = ICreditFacadeV3(_facade); creditManager = ICreditManagerV3(facade.creditManager()); underlying = creditManager.underlying(); } function openPosition( uint256 collateral, uint256 leverage ) external returns (address creditAccount) { // Calculate debt for desired leverage uint256 borrowAmount = (collateral * (leverage - 1)); // Validate against limits (uint128 minDebt, uint128 maxDebt) = facade.debtLimits(); require(borrowAmount >= minDebt && borrowAmount <= maxDebt, "Invalid debt"); // Build multicall: add collateral + increase debt MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (underlying, collateral) ) }); calls[1] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (borrowAmount) ) }); // Transfer collateral from user IERC20(underlying).transferFrom(msg.sender, address(this), collateral); // Approve to credit manager IERC20(underlying).approve(address(creditManager), collateral); // Open account creditAccount = facade.openCreditAccount(msg.sender, calls, 0); } function getHealthFactor(address creditAccount) external view returns (uint256 healthFactor, bool liquidatable) { CollateralDebtData memory cdd = creditManager.calcDebtAndCollateral( creditAccount, CollateralCalcTask.DEBT_COLLATERAL ); healthFactor = (cdd.twvUSD * 10000) / cdd.totalDebtUSD; liquidatable = healthFactor < 10000; } function addCollateralToPosition( address creditAccount, uint256 amount ) external { MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (underlying, amount) ) }); // Transfer from user and approve IERC20(underlying).transferFrom(msg.sender, address(this), amount); IERC20(underlying).approve(address(creditManager), amount); facade.multicall(creditAccount, calls); } function closePosition(address creditAccount) external { // Build multicall to repay debt and withdraw MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) // full repayment ) }); calls[1] = MultiCall({ target: address(facade), callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (underlying, type(uint256).max, msg.sender) ) }); facade.closeCreditAccount(creditAccount, calls); } } ``` For architectural background, see [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite). ## Slippage & Safety Source: https://docs.gearbox.finance/developers/gm-mc-safety File: content/developers/gm-mc-safety.mdx Control slippage, update on-demand price feeds, and optimize collateral checks. These safety mechanisms protect your multicalls from unexpected losses. ## Slippage Protection ### Why You need slippage protection when performing swaps or multi-step operations. Without it, sandwich bots can exploit unprotected transactions and you may receive significantly fewer tokens than expected. ### How It Works Gearbox uses a two-step pattern: 1. **`storeExpectedBalances`** - Record expected minimum balances BEFORE operations 2. **`compareBalances`** - Verify actual balances meet expectations AFTER operations If the final balance is less than expected, the entire multicall reverts. ### Basic Usage ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Calculate minimum expected output with 0.5% slippage tolerance const expectedOutput = 25n * 10n ** 18n; // 25 WETH const slippageBps = 50n; // 0.5% const minExpected = expectedOutput - (expectedOutput * slippageBps / 10000n); const calls = [ // 1. Store expected balance BEFORE swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: wethAddress, amount: minExpected }]], }), }, // 2. Perform the swap { target: uniswapV3Adapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactInputSingle', args: [swapParams], }), }, // 3. Verify slippage AFTER swap { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, ]; ``` ### Multiple Tokens Check slippage on multiple output tokens in a single store/compare pair: ```typescript const calls = [ // Store expectations for both tokens { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[ { token: wethAddress, amount: minWethExpected }, { token: usdcAddress, amount: minUsdcExpected }, ]], }), }, // Multiple swaps... { /* swap 1 */ }, { /* swap 2 */ }, // Single compare covers all stored expectations { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }, ]; ``` ### Slippage Gotchas * **Keep checks close to operations** - Place `storeExpectedBalances` immediately before swaps, and `compareBalances` immediately after. Internal operations like `addCollateral` between them can affect balances unexpectedly. * **`compareBalances` without `storeExpectedBalances` reverts** - You must always call store first. * **Auto-compare at multicall end** - If you forget `compareBalances`, the check happens automatically, but explicit is better. * **Amount is a delta, not final balance** - The `amount` in `BalanceDelta` is the change you expect, not the resulting balance. * **Cannot reuse stored balances** - After `compareBalances`, stored expectations are cleared. Call `storeExpectedBalances` again for subsequent swaps. --- ## On-Demand Price Updates ### Why Some tokens use pull-based oracles (Pyth, Redstone) that don't update automatically. You must push fresh price data before operations that depend on accurate pricing. ### How It Works `onDemandPriceUpdate` pushes oracle data to the price feed. You obtain signed price data from the oracle provider off-chain, then include the update as the FIRST call in your multicall. **Critical rule:** All price updates must be at the **beginning** of the calls array. Any `onDemandPriceUpdate` after another call type will revert. ### Basic Usage ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Get price data from oracle provider (e.g., Pyth) const priceData = await pythClient.getPriceUpdateData([feedId]); const calls = [ // Price update MUST be first { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [ tokenAddress, // Token to update price for false, // reserve: false = main feed, true = reserve feed priceData, // Signed price data from oracle ], }), }, // Now other operations service.prepareAddCollateral(usdcAddress, amount), service.prepareIncreaseDebt(debtAmount), ]; ``` ### Reserve Feed Updates (For Withdrawals) Withdrawals trigger safe pricing, which uses both main and reserve feeds: ```typescript const calls = [ // Main feed update { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [tokenAddress, false, mainPriceData], // reserve = false }), }, // Reserve feed update { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [tokenAddress, true, reservePriceData], // reserve = true }), }, // Now the withdrawal will work with safe pricing service.prepareWithdrawCollateral(otherToken, amount, recipient), ]; ``` ### Detecting Which Feeds Need Updates ```typescript import { priceFeedCompressorAbi } from '@gearbox-protocol/sdk'; const feedInfo = await client.readContract({ address: priceFeedCompressorAddress, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [creditManagerAddress], }); // feedInfo contains tokens that need on-demand updates const tokensNeedingUpdate = feedInfo.filter(f => f.needsUpdate); ``` ### Price Update Gotchas * **Price updates MUST be first** - Any price update after a non-price-update call reverts. * **Fresh data required** - Price data has a short validity window (usually a few minutes). Generate it right before the transaction. * **Not all tokens need updates** - Only Pyth and Redstone feeds need on-demand updates. Chainlink feeds update automatically. * **Disabled tokens don't need updates** - If a token will be disabled by the end of the multicall, skip its price update. --- ## Collateral Check Parameters ### Why You set collateral check params to optimize gas or enforce a minimum health factor above 1.0. The collateral check iterates through enabled tokens, summing value until it exceeds debt. Hints tell it which tokens to check first, potentially skipping expensive oracle calls. ### How It Works `setFullCheckParams` configures two things: 1. **Collateral hints** - Token masks to prioritize during the check 2. **Min health factor** - Minimum acceptable HF (in basis points, 10000 = 1.0) ### Basic Usage ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi, creditManagerAbi } from '@gearbox-protocol/sdk'; import { getContract } from 'viem'; const creditManager = getContract({ address: cmAddress, abi: creditManagerAbi, client: publicClient, }); // Get token masks (each token has a unique bitmask) const usdcMask = await creditManager.read.getTokenMaskOrRevert([usdcAddress]); const wethMask = await creditManager.read.getTokenMaskOrRevert([wethAddress]); const calls = [ // Set hints at the start of multicall { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'setFullCheckParams', args: [ [usdcMask, wethMask], // Check these tokens first 10000, // minHealthFactor: 1.0 (10000 bps) ], }), }, // Rest of your multicall service.prepareAddCollateral(usdcAddress, amount), ]; ``` ### Setting Higher Min Health Factor Require the account to maintain at least 1.2 HF: ```typescript const MIN_HF_120 = 12000; // 1.2 in basis points const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'setFullCheckParams', args: [ [], // No hints MIN_HF_120, ], }), }, // Operations... ]; ``` ### Check Params Gotchas * **Use masks, not addresses** - The hints array takes token masks from `getTokenMaskOrRevert`, not token addresses. * **Hints are optimization, not guarantee** - All enabled tokens are still validated; hints just change the evaluation order. * **Min health factor must be >= 10000** - You cannot set a health factor below 1.0. * **Params reset after multicall** - `setFullCheckParams` only affects the current multicall's final check. The next multicall uses defaults. * **Small accounts don't benefit** - For accounts with fewer than 5 enabled tokens, hints add overhead without meaningful gas savings. * **Order matters** - Put your highest-value collateral first in the hints array for the best gas savings. ## Learn More * [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) - Adapter calls that need slippage protection * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Building and executing multicalls * [Withdrawing Collateral](https://docs.gearbox.finance/developers/gm-mc-withdraw) - May need reserve feed updates ## Multicalls Source: https://docs.gearbox.finance/developers/multicalls-2 File: content/developers/multicalls-2.mdx Build and execute multicalls in Solidity. > For SDK multicall helpers, see [Multicalls](https://docs.gearbox.finance/developers/multicalls). ## Detailed Operation Guides For comprehensive documentation of each operation: * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Transfer tokens with approval patterns * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Borrowing, repayment, and constraints * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Quota mechanics and limits * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - Safe pricing and health impact * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Balance delta protection * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Adapter interaction patterns * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Token mask management * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - On-demand oracle data * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Health check optimization * [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) - Security cleanup ## The MultiCall Structure ```solidity struct MultiCall { address target; // CreditFacade or allowed Adapter bytes callData; // Encoded function call } ``` ## ICreditFacadeV3Multicall Operations All multicall operations are defined in `ICreditFacadeV3Multicall`: ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; ``` ### Protocol Operations | Function | Signature | | ------------------------- | ---------------------------------------------------------------------------------- | | `addCollateral` | `(address token, uint256 amount)` | | `addCollateralWithPermit` | `(address token, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s)` | | `withdrawCollateral` | `(address token, uint256 amount, address to)` | | `increaseDebt` | `(uint256 amount)` | | `decreaseDebt` | `(uint256 amount)` | | `updateQuota` | `(address token, int96 quotaChange, uint96 minQuota)` | ### Safety Operations | Function | Signature | | ----------------------- | ------------------------------------------- | | `onDemandPriceUpdate` | `(address token, bool reserve, bytes data)` | | `storeExpectedBalances` | `(BalanceDelta[] deltas)` | | `compareBalances` | `()` | | `setFullCheckParams` | `(uint256[] hints, uint16 minHF)` | | `setBotPermissions` | `(address bot, uint192 permissions)` | ## Encoding Multicalls Use `abi.encodeCall` for type-safe encoding: ```solidity MultiCall[] memory calls = new MultiCall[](3); // Add collateral calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 10_000 * 10**6) ) }); // Borrow calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000 * 10**6) ) }); // Set quota calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (weth, 50_000 * 10**6, 50_000 * 10**6) ) }); ``` ## Adapter Calls External protocol calls go through adapters. Get the adapter address from Credit Manager: ```solidity // Get adapter for Uniswap V3 address uniswapV3Adapter = ICreditManagerV3(creditManager).contractToAdapter(UNISWAP_V3_ROUTER); require(uniswapV3Adapter != address(0), "Adapter not found"); // Encode adapter call calls[4] = MultiCall({ target: uniswapV3Adapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (ISwapRouter.ExactInputSingleParams({ tokenIn: usdc, tokenOut: weth, fee: 500, recipient: address(0), // Adapter overrides to credit account deadline: block.timestamp + 3600, amountIn: 50_000 * 10**6, amountOutMinimum: 0, // Using Gearbox slippage check instead sqrtPriceLimitX96: 0 })) ) }); ``` ## Complete Multicall Example 8-call strategy: price update, collateral, borrow, slippage setup, swap, deposit, slippage check, quota: ```solidity address accountOwner; address creditManager; address creditFacade; address usdc; address weth; address yvWETH; address uniswapV3Router; bytes memory yvWETH_priceData; // Assume exchange rate: 2000 USDC/yvWETH MultiCall[] memory calls = new MultiCall[](8); // 1. On-demand price update (must be first) calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (yvWETH, false, yvWETH_priceData) ) }); // 2. Add collateral calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 10_000 * 10**6) ) }); // 3. Borrow calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000 * 10**6) ) }); // 4. Store expected balances for slippage check // Min output: (50000 / 2000) * 0.995 = 24.875 yvWETH BalanceDelta[] memory deltas = new BalanceDelta[](1); deltas[0] = BalanceDelta({ token: yvWETH, amount: (25 * 10**18) * 995 / 1000 }); calls[3] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.storeExpectedBalances, (deltas) ) }); // 5. Swap via Uniswap address uniswapV3Adapter = ICreditManagerV3(creditManager).contractToAdapter(uniswapV3Router); ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: usdc, tokenOut: weth, fee: 500, recipient: address(0), // Adapter overrides deadline: block.timestamp + 3600, amountIn: 50_000 * 10**6, amountOutMinimum: 0, // Using Gearbox slippage check sqrtPriceLimitX96: 0 }); calls[4] = MultiCall({ target: uniswapV3Adapter, callData: abi.encodeCall(IUniswapV3Adapter.exactInputSingle, (params)) }); // 6. Deposit to Yearn using diff pattern address yvWETHAdapter = ICreditManagerV3(creditManager).contractToAdapter(yvWETH); calls[5] = MultiCall({ target: yvWETHAdapter, callData: abi.encodeCall(IYearnV2Adapter.depositDiff, (1)) // Leave 1 wei }); // 7. Compare balances (slippage check) calls[6] = MultiCall({ target: creditFacade, callData: abi.encodeCall(ICreditFacadeV3Multicall.compareBalances, ()) }); // 8. Set quota for yvWETH calls[7] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (yvWETH, 50_000 * 10**6, 50_000 * 10**6) ) }); // Approve collateral to Credit Manager (not Facade!) IERC20(usdc).approve(creditManager, 10_000 * 10**6); // Execute ICreditFacadeV3(creditFacade).openCreditAccount(accountOwner, calls, 0); ``` ## The "Diff" Pattern Adapters implement `*_diff` functions for handling unknown amounts: * **Standard function**: Requires exact `amountIn` * **Diff function**: Calculates `amountIn = currentBalance - leftoverAmount` This is essential when the exact output of a previous operation is unknown. ```solidity // After a swap, deposit all WETH except 1 wei to Yearn calls[5] = MultiCall({ target: yvWETHAdapter, callData: abi.encodeCall(IYearnV2Adapter.depositDiff, (1)) }); ``` ## Best Practices 1. **Price updates first**: Always put `onDemandPriceUpdates` at the start if using pull-based oracles 2. **Slippage protection**: Always use `storeExpectedBalances` before swaps and `compareBalances` after 3. **Approve to Credit Manager**: Token approvals go to Credit Manager, not Credit Facade 4. **Gas optimization**: Use `setFullCheckParams` with hints for accounts with many tokens 5. **Dust management**: Use `type(uint256).max` in `withdrawCollateral` to empty balances ## Next Steps * [Multicall Operations](https://docs.gearbox.finance/developers/multicalls) - Individual operation guides * [Use Cases](https://docs.gearbox.finance/developers/use-cases) - Adapter development and protocol integration * [Pool Operations](https://docs.gearbox.finance/developers/pool-operations) - Direct pool interaction For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Token Management Source: https://docs.gearbox.finance/developers/gm-mc-tokens File: content/developers/gm-mc-tokens.mdx Manage which tokens count as collateral and revoke unnecessary token allowances on your credit account. ## Enabling and Disabling Tokens ### Why You manually enable/disable tokens when: * **Direct transfers** - Tokens sent directly to a Credit Account aren't auto-enabled * **Gas optimization** - Disable unused tokens to reduce collateral check cost * **Risk management** - Prevent certain tokens from counting in health factor Most of the time you don't need this - tokens auto-enable/disable based on balance changes from adapter calls. Manual control is only necessary in edge cases. ### Auto-Enable/Disable Behavior Non-quoted tokens have automatic behavior: | Balance Change | Action | | -------------- | ------------ | | 0/1 to > 1 | Auto-enable | | > 1 to 0/1 | Auto-disable | **Important:** These functions only work on **non-quoted tokens**. Quota tokens can only be enabled/disabled via `updateQuota`. ### Enable a Token ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'enableToken', args: [tokenAddress], }), }, ]; ``` ### Disable a Token ```typescript const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'disableToken', args: [tokenAddress], }), }, ]; ``` ### Disable Unused Tokens to Save Gas Each enabled token requires a price oracle call during collateral check. Disable tokens with zero balance: ```typescript // Check which tokens have zero balance const accountData = await service.getCreditAccountData(creditAccountAddress); const tokensToDisable = accountData.tokens .filter(t => t.balance <= 1n && t.isEnabled) .map(t => t.token); const calls = tokensToDisable.map(token => ({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'disableToken', args: [token], }), })); ``` ### Enable/Disable Gotchas * **No-op for quota tokens** - Calling `enableToken` or `disableToken` on a quota token does nothing. Use `updateQuota` instead. * **Cannot enable forbidden tokens** - Forbidden tokens will revert. They must be swapped away. * **Max enabled tokens limit** - Each Credit Manager has a maximum number of enabled tokens per account. Check with `creditManager.read.maxEnabledTokens()`. * **Disabled tokens still on account** - Disabling doesn't remove the balance, it just excludes the token from the health factor calculation. During liquidation, liquidators can claim disabled token balances as bonus. * **Balance of 1 is "zero"** - Gearbox treats balances of 0 and 1 the same (due to ERC20 rounding). Auto-disable triggers at balance <= 1. --- ## Revoking Allowances ### Why You revoke allowances when: * **Security incident** - A third-party contract may be compromised * **Legacy cleanup** - Old accounts from V2.1 may have stale approvals * **Defense in depth** - Proactively remove unnecessary approvals Current Gearbox V3 automatically resets allowances to 1 after each interaction, so this is mainly needed for legacy accounts or incident response. ### How It Works `revokeAdapterAllowances` resets token approvals from your Credit Account to specified contracts. Allowance is set to 1 (not 0) due to gas optimization. ### Basic Usage ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Define which approvals to revoke const revocations = [ { spender: uniswapRouterAddress, token: usdcAddress, }, { spender: curvePoolAddress, token: daiAddress, }, ]; const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'revokeAdapterAllowances', args: [revocations], }), }, ]; await market.creditFacade.write.multicall([creditAccountAddress, calls]); ``` ### Check Existing Allowances ```typescript import { erc20Abi, getContract } from 'viem'; const token = getContract({ address: tokenAddress, abi: erc20Abi, client: publicClient, }); // Check allowance from Credit Account to a spender const allowance = await token.read.allowance([ creditAccountAddress, spenderAddress, ]); if (allowance > 1n) { console.log(`Credit Account has ${allowance} allowance to ${spenderAddress}`); } ``` ### Revoke All Known Adapters ```typescript // Get all adapters from Credit Manager const adapters = await creditManager.read.adapters(); // Build revocations for all adapters const revocations = adapters.map(adapter => ({ spender: adapter, token: tokenAddress, })); const calls = [ { target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'revokeAdapterAllowances', args: [revocations], }), }, ]; ``` ### Revocation Gotchas * **Usually not needed in V3** - Gearbox V3 automatically resets allowances after each adapter interaction. * **Sets to 1, not 0** - For gas efficiency, allowances are set to 1 instead of 0. An allowance of 1 wei is functionally zero. * **Target the adapter, not the protocol** - Revocations target the spender (adapter address), not the underlying protocol. * **Batch multiple revocations** - Pass multiple `(spender, token)` pairs in a single `revokeAdapterAllowances` call for efficiency. ### When to Actually Revoke Real scenarios where revocation makes sense: 1. **Third-party exploit** - A protocol Gearbox integrates with gets hacked 2. **Account migration** - Moving from V2.1 account with old allowances 3. **Compliance requirement** - Regulatory frameworks requiring revoked unused approvals 4. **Personal security policy** - Explicit control over all approvals ## Learn More * [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) - How adapters use allowances * [Updating Quotas](https://docs.gearbox.finance/developers/gm-mc-quotas) - How to enable/disable quota tokens * [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) - Related safety controls ## Liquidations Source: https://docs.gearbox.finance/developers/gm-liquidations File: content/developers/gm-liquidations.mdx Liquidations close undercollateralized positions to ensure protocol solvency. Gearbox V3 supports both full liquidations (complete account closure) and partial liquidations (debt reduction while keeping the account open). ## When Liquidations Occur An account becomes liquidatable when: - **Health Factor < 1.0** (undercollateralized) - **Account has expired** (past the configured expiration timestamp) Anyone can liquidate an unhealthy account - there is no whitelist. ## Full Liquidation ### Liquidation Parameters | Parameter | Description | | ---------------- | ------------------------------------------ | | `creditAccount` | The account to liquidate | | `to` | Where liquidator receives remaining assets | | `calls` | Multicall array for converting collateral | | `lossPolicyData` | Custom data for loss handling | ### Liquidation Math | Parameter | Description | | ------------------------ | ------------------------------------------------ | | **Liquidation Premium** | % of account value liquidator receives as reward | | **Liquidation Discount** | % used to cover debt and fees (100% - Premium) | ### Outcomes **Solvent Liquidation** (totalFunds > liabilities): - Pool repaid in full - DAO receives liquidation fee - Remaining funds go to original borrower **Bad Debt** (totalFunds < liabilities): - DAO profit reduced first - If insufficient, loss reported to Pool - Pool burns Treasury shares to cover - If Treasury empty: "uncovered loss" (socialized across LPs) ### Executing a Full Liquidation ```typescript // Liquidation bot example const calls = [ // Swap collateral tokens to underlying via adapters { target: uniswapAdapterAddress, callData: encodeFunctionData({ abi: uniswapAdapterAbi, functionName: "exactAllInputSingle", args: [{ tokenIn: wbtcAddress, tokenOut: usdcAddress /* ... */ }], }), }, // Additional swaps as needed... ]; await creditFacade.write.liquidateCreditAccount([ creditAccountAddress, liquidatorAddress, // receives remaining assets calls, "0x", // lossPolicyData ]); ``` ### Internal Execution Steps 1. Calculate payments via `CreditLogic.calcLiquidationPayments` 2. Execute multicall (convert collateral to underlying) 3. Transfer `amountToPool` to the Pool 4. Remove active quotas via PoolQuotaKeeper ### Fee Types | Fee Type | Description | | ---------------------------- | --------------------------------- | | `feeLiquidation` | Standard liquidation fee to DAO | | `feeLiquidationExpired` | Higher fee for expired accounts | | `liquidationDiscount` | Discount for healthy liquidations | | `liquidationDiscountExpired` | Discount for expired liquidations | Expired accounts have higher fees to incentivize timely liquidation. --- ## Partial Liquidation Partial liquidation reduces debt while keeping the account open. This is useful when market liquidity is insufficient for full conversion or a "deleverage" strategy is preferred. ### Constraints - Account must remain open after liquidation - Must pass collateral check post-liquidation (HF >= 1) - Cannot leave "dust" debt below `minDebt` ### Executing a Partial Liquidation ```typescript const seizedAmount = await creditFacade.write.partiallyLiquidateCreditAccount([ creditAccountAddress, wbtcAddress, // token to seize parseUnits("1000", 6), // repaid USDC amount parseUnits("0.03", 8), // min BTC to receive liquidatorAddress, [], // price updates ]); ``` ### Health Factor Thresholds **Protocol Level:** - **Liquidation Trigger**: HF < 1.0 - **Post-Liquidation**: HF >= 1.0 (enforced) **Bot-Specific** (configurable in `PartialLiquidationBotV3`): - `minHealthFactor`: HF threshold for intervention - `maxHealthFactor`: Maximum HF after partial liquidation (prevents over-liquidation) --- ## RWA Liquidations RWA-backed positions use the same liquidation entrypoints as permissionless collateral, but liquidators face different capital, compliance, and unwind constraints. ### On-Chain Flow A liquidation is a swap between RWA collateral and the market underlying: the liquidator supplies underlying to repay debt and receives RWA tokens (typically to the `to` address on full liquidation, or directly on partial liquidation). There is no DEX conversion step inside the credit account. | Step | What happens | | --------------------- | ------------------------------------------------------------------------------------ | | **Liquidation tx** | Liquidator repays debt with underlying; protocol transfers RWA to the liquidator | | **After liquidation** | Liquidator redeems or disposes of RWA outside Gearbox (issuer redemption, OTC, etc.) | Redemption cannot be performed on the credit account. The liquidator must hold the RWA in their own wallet and complete issuer flows off-protocol. ### Liquidator Requirements - **Upfront capital**: Underlying must be available before the liquidation tx; debt is repaid immediately and RWA is received in the same transaction - **No market liquidity**: RWA tokens generally lack deep secondary-market liquidity, so exit timing depends on issuer redemption or other off-protocol disposal - **Duration risk**: Capital is locked until the RWA is unwound; economics depend on issuer timelines and terms, not spot DEX prices - **KYC** (if applicable): Many RWAs are permissioned; the liquidator may need issuer onboarding before they can hold or redeem the specific asset ### Entrypoints Otherwise the interface matches permissionless tokens: - Full liquidation: `liquidateCreditAccount` (often with an empty or minimal `calls` array — no adapter swaps) - Partial liquidation: `partiallyLiquidateCreditAccount` ### Additional Liquidation Flows Some RWA integrations expose helper contracts for positions that do not fit the standard RWA ↔ underlying swap. The Securitize integration is the primary example today. #### Securitize: Pending Redemption Liquidations Securitize offramp is asynchronous: when a credit account redeems DS tokens, each request is held in a dedicated `SecuritizeRedeemer` clone while Securitize processes the redemption. Stablecoins settle to the redeemer address later; until then, pending value is reflected as phantom-token collateral on the account. If the account is liquidatable but **immediate liquid balances are insufficient to cover debt** — wrapper underlying, stablecoin on the account, and stablecoin already received on redeemers (after liquidation discount) — use `SecuritizeLiquidator` instead of calling `liquidateCreditAccount` directly. That path transfers **unclaimed redeemers** to the liquidator and closes the account in one transaction. | Condition | Use | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | | Discounted liquid balances on account + redeemers ≥ total debt | Standard `liquidateCreditAccount` | | Liquid balances < total debt, but collateral (including pending redemptions at **current NAV**) supports closure | `SecuritizeLiquidator.liquidatePendingRedemption` | **What the liquidator receives** - Ownership of each unclaimed `SecuritizeRedeemer` (transferred to `msg.sender` via the redemption gateway adapter) - Any DS token balance withdrawn from the credit account - Stablecoin on redeemers can be claimed later through normal Securitize flows once settlement arrives The liquidator still supplies **upfront underlying** (computed from collateral value and liquidation discount). The helper pulls it from the caller, adds it as collateral, and invokes `liquidateCreditAccount` with a composed multicall. Pending redemption value in this path uses **current NAV** (not the conservative `min(starting, current)` cap used for passive collateral), so liquidation pricing reflects the NAV at liquidation time. ```typescript const phantomToken = getContract({ address: redemptionPhantomTokenAddress, abi: securitizeRedemptionPhantomTokenAbi, client: publicClient, }); const redemptionGatewayAddress = await phantomToken.read.redemptionGateway(); const redemptionGateway = getContract({ address: redemptionGatewayAddress, abi: securitizeRedemptionGatewayAbi, client: publicClient, }); const securitizeLiquidator = getContract({ address: await redemptionGateway.read.transferMaster(), abi: securitizeLiquidatorAbi, client: walletClient, }); await securitizeLiquidator.write.liquidatePendingRedemption([ creditAccountAddress, redemptionGatewayAddress, priceUpdates, // optional PriceFeedStore updates "0x", // lossPolicyData ]); ``` The liquidator can then claim the settled redemption via `SecuritizeRedeemer.claim` (called through the redemption gateway once stablecoin has arrived on the redeemer clones): ```typescript const redeemers = await redemptionGateway.read.getUnclaimedRedeemers([ liquidatorAddress, ]); const stableCoin = getContract({ address: await redemptionGateway.read.stableCoinToken(), abi: erc20Abi, client: publicClient, }); const settledRedeemers: Address[] = []; for (const redeemer of redeemers) { const balance = await stableCoin.read.balanceOf([redeemer]); if (balance > 0n) settledRedeemers.push(redeemer); } await redemptionGateway.write.claim([settledRedeemers]); ``` **Liquidator requirements** - Securitize **KYC / wallet allowlisting** for the address that receives redeemers (`msg.sender` must be an approved Securitize investor wallet) - **Upfront underlying** approved to `SecuritizeLiquidator` for the computed `underlyingAmount` - **Duration risk** on redeemers until Securitize settles stablecoins to them; the liquidator claims via the redemption gateway after settlement - Market must use the Securitize collateral set (wrapper underlying, stablecoin, DS token, redemption phantom token) with a single configured redemption gateway --- ## Emergency Liquidations ### Regular vs Emergency | Type | When | Who | | ------------- | ----------------------------- | -------------------------------- | | **Regular** | Protocol functioning normally | Anyone | | **Emergency** | Protocol/Facade paused | `EMERGENCY_LIQUIDATOR` role only | Emergency liquidations ensure positions can be closed even during protocol pause, preventing bad debt accumulation. ### Treasury Backstop The `TreasuryLiquidator` contract allows the DAO treasury to provide emergency liquidity when external liquidators are absent, acting as a backstop during extreme market conditions. --- ## Checking If an Account Is Liquidatable ```typescript const creditFacade = getContract({ address: facadeAddress, abi: creditFacadeV3Abi, client: publicClient, }); const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get health factor const debtData = await creditManager.read.calcDebtAndCollateral([ creditAccount, 2, // DEBT_COLLATERAL ]); const hf = (debtData.twvUSD * 10000n) / debtData.totalDebtUSD; // Check expiration const expirationDate = await creditFacade.read.expirationDate(); const isExpired = BigInt(Math.floor(Date.now() / 1000)) > expirationDate; const isLiquidatable = hf < 10000n || isExpired; console.log(`Liquidatable: ${isLiquidatable}, HF: ${Number(hf) / 100}%`); ``` ## Learn More - [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) - Account data and querying - [External Calls](https://docs.gearbox.finance/developers/gm-mc-external) - Adapter calls used during liquidation multicalls - [Slippage & Safety](https://docs.gearbox.finance/developers/gm-mc-safety) - Price feeds and collateral checks ## Operations Reference Source: https://docs.gearbox.finance/developers/operations-reference-2 File: content/developers/operations-reference-2.mdx Detailed guides for encoding each multicall operation in Solidity. > For TypeScript/SDK implementation, see [Multicalls](https://docs.gearbox.finance/developers/multicalls). ## Why This Section? The main [multicalls.md](https://docs.gearbox.finance/developers/multicalls) covers the fundamentals: MultiCall struct encoding, call order, and the diff pattern. This section goes deeper on each operation - when you need it, complete Solidity examples, and what can go wrong. ## Quick Reference | Operation | Function | When to Use | Guide | | -------------------- | ------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | Add Collateral | `addCollateral` | Deposit tokens to increase health factor | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | | Increase Debt | `increaseDebt` | Borrow from pool | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Decrease Debt | `decreaseDebt` | Repay borrowed funds | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Update Quota | `updateQuota` | Enable/adjust quota token exposure | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | | Withdraw Collateral | `withdrawCollateral` | Remove tokens from account | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | | Slippage Control | `storeExpectedBalances` / `compareBalances` | Protect swaps from sandwich attacks | [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) | | External Calls | Adapter-specific | Interact with Uniswap, Curve, etc. | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | | Enable/Disable Token | `enableToken` / `disableToken` | Explicit collateral management | [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) | | Price Updates | `onDemandPriceUpdate` | Update Pyth/Redstone feeds | [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) | | Check Params | `setFullCheckParams` | Optimize gas, set min health factor | [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) | | Revoke Allowances | `revokeAdapterAllowances` | Security measure after suspicious activity | [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances) | ## Page Structure Each operation guide follows the same structure: 1. **Why** - When you need this operation 2. **What** - What it does and how it fits the system 3. **How** - Working Solidity code 4. **Gotchas** - Common mistakes and edge cases 5. **See Also** - Related operations and SDK reference ## Core Encoding Pattern All multicall operations use the same encoding pattern: ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: creditFacade, // or adapter address for external calls callData: abi.encodeCall( ICreditFacadeV3Multicall.functionName, (param1, param2, ...) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ## Call Order Requirements Some operations have strict ordering rules: 1. **Price updates (`onDemandPriceUpdate`)** - Must be first in the calls array 2. **Collateral check params (`setFullCheckParams`)** - Should be early, affects final check 3. **External calls** - Can be anywhere after price updates 4. **Slippage checks** - `storeExpectedBalances` before swaps, `compareBalances` after ## Import Patterns Standard imports for multicall operations: ```solidity // Core interfaces import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; // For slippage protection import {BalanceDelta} from "@gearbox-protocol/core-v3/contracts/libraries/BalancesLogic.sol"; // For allowance revocation import {RevocationPair} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; // Adapter interfaces (examples) import {IUniswapV3Adapter} from "@gearbox-protocol/integrations-v3/contracts/interfaces/uniswap/IUniswapV3Adapter.sol"; import {IYearnV2Adapter} from "@gearbox-protocol/integrations-v3/contracts/interfaces/yearn/IYearnV2Adapter.sol"; ``` ## Related * [Multicalls Overview](https://docs.gearbox.finance/developers/multicalls) - Fundamentals and the diff pattern * [Credit Manager](https://docs.gearbox.finance/developers/credit-manager) - Underlying execution layer * [SDK Multicalls](https://docs.gearbox.finance/developers/multicalls) - TypeScript implementation ## Adding Collateral Source: https://docs.gearbox.finance/developers/adding-collateral-2 File: content/developers/adding-collateral-2.mdx Deposit tokens from an external address to a Credit Account. > For SDK implementation, see [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral). ## Why You need to add collateral when: * **Opening an account** - Initial deposit to enable borrowing * **Improving health factor** - Account approaching liquidation threshold * **Enabling more borrowing** - Current collateral limits how much you can borrow Adding collateral increases your account's total weighted value (TWV), which improves the health factor and allows larger debt positions. ## What `addCollateral` transfers tokens from the caller to the Credit Account. On execution: 1. The Credit Manager calls `transferFrom` to move tokens from caller to Credit Account 2. The token is enabled as collateral (if not already enabled) 3. Quoted tokens are NOT auto-enabled - you must set a quota separately **Important:** Approve tokens to the **Credit Manager**, not the Credit Facade. The Credit Manager is the contract that actually executes the transfer. ## How ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditManager; address usdc; address creditAccount; address owner; MultiCall[] memory calls = new MultiCall[](1); // Add 10,000 USDC as collateral calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 10_000 * 10**6) ) }); // First, approve tokens to Credit Manager (not Facade!) IERC20(usdc).approve(creditManager, 10_000 * 10**6); // Execute on existing account ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); // Or open new account with collateral ICreditFacadeV3(creditFacade).openCreditAccount(owner, calls, 0); ``` ### Using Permit (No Separate Approval) For EIP-2612 compatible tokens, avoid the separate approval transaction: ```solidity // Sign permit off-chain, then use addCollateralWithPermit calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateralWithPermit, (usdc, 10_000 * 10**6, deadline, v, r, s) ) }); ``` ## Gotchas ### Approve to Credit Manager, Not Facade The most common mistake. The Credit Manager executes the `transferFrom`, so it needs the approval: ```solidity // CORRECT IERC20(token).approve(creditManager, amount); // WRONG - will fail IERC20(token).approve(creditFacade, amount); ``` ### Quoted Tokens Need Quota Adding a quoted token as collateral does NOT automatically enable it. You must also call `updateQuota`: ```solidity MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (quotedToken, amount) ) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotedToken, int96(quotaAmount), uint96(minQuota)) ) }); ``` ### Direct Transfers Don't Enable Sending tokens directly to a Credit Account (via `transfer`) does NOT enable them as collateral. You still need a multicall with the proper operation to count them in the health factor. ### Invalid Collateral Tokens Only tokens recognized by the Credit Manager can be used as collateral. Transferring unrecognized tokens to a Credit Account may result in them being stuck (only governance can recover). Check if a token is valid: ```solidity // This reverts if token is not valid collateral uint256 mask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(token); ``` ## See Also * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Often combined with adding collateral * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Required for quoted tokens ## Bot System Source: https://docs.gearbox.finance/developers/gm-bots File: content/developers/gm-bots.mdx Gearbox features a granular bot permission system that allows automated management of Credit Accounts. Bots can execute operations on behalf of account owners with specific, revocable permissions. ## Architecture | Component | Description | | --- | --- | | **BotListV3** | Registry storing `(bot, creditManager, creditAccount) → permissions` | | **IBot interface** | Bots implement `requiredPermissions()` to declare needed permissions | | **CreditFacadeV3** | Entry point for bot execution via `botMulticall` | ## Permission Model Permissions are stored as a `uint192` bitmask. Account owners grant specific permissions to specific bots: | Permission | Operation | | --- | --- | | `ADD_COLLATERAL` | Add funds to account | | `INCREASE_DEBT` | Borrow more from pool | | `DECREASE_DEBT` | Repay debt | | `WITHDRAW_COLLATERAL` | Withdraw assets | | `UPDATE_QUOTA` | Change token quotas | | `SET_BOT_PERMISSIONS` | Manage other bots | | `EXTERNAL_CALLS` | Execute adapter calls | ## Granting Permissions Account owners grant permissions via a multicall operation: ```typescript const calls = [ service.setBotPermissions( creditAccount, botAddress, ADD_COLLATERAL_PERMISSION | EXTERNAL_CALLS_PERMISSION ), ]; await facade.multicall(creditAccount, calls); ``` ## Bot Execution Bots execute via `botMulticall` on the Credit Facade: ```typescript // Bot calls botMulticall with the account and operations await facade.botMulticall(creditAccount, [ service.addCollateral(creditAccount, tokenAddress, amount), // ... other permitted operations ]); ``` The Facade checks the bot's permissions before each operation. If a bot attempts an unpermitted action, the transaction reverts. ## Safety Model - **Isolation** — each permission grant is scoped to a specific `(bot, creditManager, creditAccount)` tuple - **Immutability** — bots cannot modify their own permissions - **DAO forbid list** — the DAO can globally forbid malicious bots - **Collateral check** — bot multicalls undergo the same solvency check as user multicalls ## Revoking Permissions ```typescript // Revoke all permissions for a bot await facade.multicall(creditAccount, [ service.setBotPermissions(creditAccount, botAddress, 0), ]); ``` ## Learn More - [Liquidation Bots Guide](https://docs.gearbox.finance/developers/gm-guide-bots) — Building a complete liquidation bot - [Credit Accounts](https://docs.gearbox.finance/developers/gm-accounts) — Account lifecycle and operations - [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) — Building multicall transactions ## Debt Management Source: https://docs.gearbox.finance/developers/debt-management-2 File: content/developers/debt-management-2.mdx Borrow and repay the underlying token. > For SDK implementation, see [Debt Management](https://docs.gearbox.finance/developers/debt-management). ## Why Debt management enables leveraged positions: * **Increase debt** - Borrow to deploy capital into DeFi strategies * **Decrease debt** - Repay to reduce interest costs or close position * **Partial repayment** - Reduce exposure while keeping position open Debt directly affects your health factor. Higher debt lowers health factor; lower debt raises it. ## What Two operations manage debt: | Operation | Description | | -------------- | ------------------------------------- | | `increaseDebt` | Borrow underlying token from the pool | | `decreaseDebt` | Repay underlying token to the pool | ### Increase Debt Flow 1. Pool transfers underlying to Credit Account 2. Debt counter incremented 3. Interest starts accruing immediately ### Decrease Debt Flow 1. Underlying transferred from Credit Account to pool 2. Debt counter decremented 3. Full repayment enables special "zero debt" mode ## How ### Borrowing ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; MultiCall[] memory calls = new MultiCall[](1); // Borrow 40,000 USDC calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000 * 10**6) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Repaying ```solidity // Repay 10,000 USDC calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (10_000 * 10**6) ) }); // Or repay everything calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) // Full repayment ) }); ``` ### Combined Strategy: Add Collateral + Borrow Typical account opening pattern: ```solidity MultiCall[] memory calls = new MultiCall[](2); // 1. Add initial collateral calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 10_000 * 10**6) // 10k USDC as collateral ) }); // 2. Borrow against it calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000 * 10**6) // Borrow 40k USDC (4x leverage) ) }); // Approve collateral to Credit Manager first IERC20(usdc).approve(creditManager, 10_000 * 10**6); // Open account with collateral and debt ICreditFacadeV3(creditFacade).openCreditAccount(owner, calls, 0); ``` ## Gotchas ### Same-Block Restriction Debt cannot be increased AND decreased in the same block: ```solidity // Block N: increase debt ICreditFacadeV3(creditFacade).multicall(account, increaseCalls); // Block N: try to decrease - REVERTS! ICreditFacadeV3(creditFacade).multicall(account, decreaseCalls); // Block N+1: now decrease works ICreditFacadeV3(creditFacade).multicall(account, decreaseCalls); ``` ### Debt Limits The resulting debt must be within configured limits: ```solidity (uint128 minDebt, uint128 maxDebt) = ICreditFacadeV3(creditFacade).debtLimits(); // Debt after operation must satisfy: // 0 (for full closure) OR minDebt <= debt <= maxDebt ``` ### Per-Block Borrowing Limit Total borrowing in a block is limited: ```solidity uint8 multiplier = ICreditFacadeV3(creditFacade).maxDebtPerBlockMultiplier(); // maxDebtPerBlock = maxDebt * multiplier // If block limit reached, increaseDebt reverts ``` ### Forbidden Tokens Block Borrowing If account has forbidden tokens enabled as collateral, borrowing is blocked: ```solidity // This reverts if account has forbidden tokens ICreditFacadeV3Multicall.increaseDebt(amount); ``` Solution: Swap forbidden tokens to allowed ones first. ### Zero Debt Mode Full repayment (`decreaseDebt(type(uint256).max)`) enables zero debt mode: * Collateral checks are skipped * All quotas must be disabled (zero) * Account can be closed without collateral check ```solidity MultiCall[] memory calls = new MultiCall[](2); // 1. Disable all quotas first calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotedToken, type(int96).min, 0) // Disable quota ) }); // 2. Full repayment calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) ) }); ``` ### Opening vs Closing Restrictions | Operation | On Open | On Close | | -------------- | ---------- | ---------- | | `increaseDebt` | Allowed | Prohibited | | `decreaseDebt` | Prohibited | Allowed | ```solidity // Opening: can only borrow openCreditAccount(owner, [increaseDebtCall], 0); // OK openCreditAccount(owner, [decreaseDebtCall], 0); // REVERTS // Closing: can only repay closeCreditAccount(account, [decreaseDebtCall]); // OK closeCreditAccount(account, [increaseDebtCall]); // REVERTS ``` ## See Also * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Add before borrowing * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Enable quoted tokens as collateral * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protect borrowed funds during swaps ## Updating Quotas Source: https://docs.gearbox.finance/developers/updating-quotas-2 File: content/developers/updating-quotas-2.mdx Manage collateral quotas for quoted tokens. > For SDK implementation, see [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas). ## Why Quotas are required for non-underlying tokens to count as collateral: * **Enable collateral** - Purchase quota to enable a token as collateral * **Increase exposure** - Raise quota limit to hold more of a token * **Reduce fees** - Lower quota to reduce ongoing quota fees * **Disable collateral** - Set quota to zero to disable token Without a quota, holding a token on a Credit Account with debt risks losing it to liquidators since it doesn't count toward your health factor. ## What `updateQuota` changes the quota for a specific token. On execution: 1. Quota change is applied (can be positive or negative) 2. If quota goes from zero to positive, token is enabled as collateral 3. If quota goes to zero, token is disabled as collateral 4. Quota fees are applied based on the change | Parameter | Type | Description | | ------------- | ------- | ---------------------------------------------- | | `token` | address | The quoted token (cannot be underlying) | | `quotaChange` | int96 | Delta in underlying units (negative to reduce) | | `minQuota` | uint96 | Minimum resulting quota to not revert | ## How ### Enable a Token as Collateral ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; address weth; MultiCall[] memory calls = new MultiCall[](1); // Enable WETH with 50,000 USDC quota calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (weth, int96(50_000 * 10**6), uint96(50_000 * 10**6)) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Increase Existing Quota ```solidity // Add 25,000 USDC to existing quota calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (weth, int96(25_000 * 10**6), uint96(75_000 * 10**6)) // minQuota = expected total ) }); ``` ### Decrease Quota ```solidity // Reduce quota by 20,000 USDC calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (weth, int96(-20_000 * 10**6), uint96(0)) // minQuota = 0 (any remaining is fine) ) }); ``` ### Disable Token Completely Use `type(int96).min` to fully disable: ```solidity // Completely disable WETH as collateral calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (weth, type(int96).min, uint96(0)) ) }); ``` ### Combined: Add Token + Set Quota When adding a quoted token as collateral, always pair with quota update: ```solidity MultiCall[] memory calls = new MultiCall[](2); // 1. Add the token calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (quotedToken, amount) ) }); // 2. Enable it with quota calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotedToken, int96(quotaAmount), uint96(quotaAmount)) ) }); ``` ## Gotchas ### Account Must Have Debt Quotas can only be updated when the account has non-zero debt: ```solidity // This reverts if debt == 0 ICreditFacadeV3Multicall.updateQuota(token, change, minQuota); ``` For zero-debt accounts, first borrow, then update quotas. ### Forbidden Tokens Cannot Increase If a token is forbidden, you cannot increase its quota: ```solidity // Reverts if WETH is forbidden updateQuota(weth, int96(1000 * 10**6), 0); // But you CAN decrease or disable updateQuota(weth, int96(-1000 * 10**6), 0); // OK updateQuota(weth, type(int96).min, 0); // OK ``` ### Quota Limits Each market has per-account quota limits: ```solidity // Check the limit uint96 maxQuota = IPoolQuotaKeeperV3(quotaKeeper).getQuotaLimit(token); // Resulting quota must be <= maxQuota ``` ### Quota Fees Quotas incur ongoing fees: * **Increase fee**: One-time fee on quota increase * **Quota rate**: Ongoing rate (similar to interest) on quota amount Check rates before setting large quotas: ```solidity (uint16 rate, uint192 cumulativeIndexLU, uint96 quotaIncreaseFee) = IPoolQuotaKeeperV3(quotaKeeper).getQuotaRate(token); ``` ### minQuota Parameter Use `minQuota` to prevent front-running: ```solidity // If someone reduces your quota before your tx, this reverts instead of accepting less updateQuota(token, int96(50_000e6), uint96(50_000e6)); // With minQuota = 0, any resulting quota is accepted (more gas efficient but risky) updateQuota(token, int96(50_000e6), uint96(0)); ``` ### Zero Debt Closure Requirement Before closing with zero debt, all quotas must be disabled: ```solidity MultiCall[] memory calls = new MultiCall[](3); // 1. Disable all quotas calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotedToken1, type(int96).min, 0) ) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotedToken2, type(int96).min, 0) ) }); // 2. Repay all debt calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) ) }); ``` ## See Also * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - Add tokens before setting quota * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Debt required for quota updates * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Token state management ## Withdrawing Collateral Source: https://docs.gearbox.finance/developers/withdrawing-collateral-2 File: content/developers/withdrawing-collateral-2.mdx Remove tokens from a Credit Account to an external address. > For SDK implementation, see [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral). ## Why You need to withdraw collateral when: * **Taking profits** - Remove excess collateral while maintaining healthy position * **Rebalancing** - Move funds between accounts or protocols * **Closing account** - Extract all remaining tokens before closing Withdrawing collateral decreases your account's total weighted value (TWV), which lowers the health factor. ## What `withdrawCollateral` transfers tokens from the Credit Account to a specified recipient. On execution: 1. Tokens are transferred from the Credit Account to the recipient 2. If withdrawing the full balance, the token may be disabled as collateral 3. Safe pricing is activated for the collateral check (uses min of main and reserve price feeds) **Important:** Withdrawals trigger stricter collateral checks using safe prices, so ensure sufficient buffer above the liquidation threshold. ## How ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address usdc; address creditAccount; address recipient; MultiCall[] memory calls = new MultiCall[](1); // Withdraw 5,000 USDC calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (usdc, 5_000 * 10**6, recipient) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Withdraw Full Balance Use `type(uint256).max` to withdraw the entire token balance: ```solidity // Withdraw all USDC from the account calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (usdc, type(uint256).max, recipient) ) }); ``` ### Withdraw During Closure When closing an account, you can withdraw all tokens to yourself: ```solidity MultiCall[] memory calls = new MultiCall[](2); // Repay all debt first calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (type(uint256).max) ) }); // Withdraw all collateral calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (token, type(uint256).max, recipient) ) }); ICreditFacadeV3(creditFacade).closeCreditAccount(creditAccount, calls); ``` ## Gotchas ### Safe Prices Are Used After withdrawal, collateral checks use safe pricing (minimum of main and reserve feeds). This means: * Your effective collateral value may be lower than expected * Maintain buffer above liquidation threshold * Check safe prices before withdrawing large amounts ### Forbidden Tokens Block Withdrawal If any forbidden token is enabled as collateral on the account, withdrawals are blocked in multicalls: ```solidity // This will revert if the account has forbidden tokens enabled ICreditFacadeV3(creditFacade).multicall(creditAccount, withdrawCalls); ``` Solution: First swap forbidden tokens to allowed ones, then withdraw. ### Phantom Token Unwrapping If the token being withdrawn is a phantom token (e.g., staked position token), it's automatically unwrapped: 1. Phantom token is withdrawn from the vault/pool 2. The underlying deposited token is sent to the recipient 3. No slippage protection - assumed to happen at non-manipulatable rate ### Dust Handling For clean account closure, use `type(uint256).max` to handle dust amounts: ```solidity // This handles any remaining wei callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (token, type(uint256).max, recipient) ) ``` ### Recipient Validation The recipient address must be a valid address. Common patterns: ```solidity // Withdraw to account owner withdrawCollateral(token, amount, owner); // Withdraw to another protocol/contract withdrawCollateral(token, amount, anotherContract); // NEVER use address(0) - tokens will be lost! ``` ## See Also * [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) - The reverse operation * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Repay debt before large withdrawals * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Token state management ## Controlling Slippage Source: https://docs.gearbox.finance/developers/controlling-slippage-2 File: content/developers/controlling-slippage-2.mdx Protect against price movement during swaps and other operations. > For SDK implementation, see [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage). ## Why Slippage protection is critical for: * **Swaps** - Ensure minimum output from DEX trades * **Deposits/Withdrawals** - Protect against unfavorable rates * **Multi-step strategies** - Verify final position matches expectations Without slippage protection, MEV bots can sandwich your transactions, extracting value through price manipulation. ## What Two operations work together for slippage control: | Operation | Description | | ----------------------- | -------------------------------------------------------- | | `storeExpectedBalances` | Record expected token balances (current + delta) | | `compareBalances` | Verify current balances meet expectations, revert if not | The pattern: Store expectations before swaps, compare after swaps. ## How ### Basic Slippage Check ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {BalanceDelta} from "@gearbox-protocol/core-v3/contracts/libraries/BalancesLogic.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; address usdc; address weth; address uniswapAdapter; // Calculate minimum expected output (with 0.5% slippage tolerance) // If swapping 50,000 USDC at rate of 2000 USDC/ETH: // Expected: 25 ETH, with 0.5% slippage: 24.875 ETH int256 minWethOut = int256(24.875 ether); MultiCall[] memory calls = new MultiCall[](3); // 1. Store expected balance (current + delta) BalanceDelta[] memory deltas = new BalanceDelta[](1); deltas[0] = BalanceDelta({ token: weth, amount: minWethOut // Can be negative for tokens spent }); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.storeExpectedBalances, (deltas) ) }); // 2. Perform swap via adapter calls[1] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); // 3. Compare balances - reverts if WETH balance < expected calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.compareBalances, () ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Multiple Token Checks Check multiple tokens in one comparison: ```solidity BalanceDelta[] memory deltas = new BalanceDelta[](2); // Expect to receive WETH deltas[0] = BalanceDelta({ token: weth, amount: int256(minWethOut) }); // Expect to spend USDC (negative delta) deltas[1] = BalanceDelta({ token: usdc, amount: -int256(maxUsdcIn) }); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.storeExpectedBalances, (deltas) ) }); ``` ### Complete Strategy with Slippage Protection 8-step leveraged yield farming with full slippage protection: ```solidity MultiCall[] memory calls = new MultiCall[](8); // 1. Price update (if using pull oracles) calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (yvWETH, false, priceData) ) }); // 2. Add collateral calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 10_000 * 10**6) ) }); // 3. Borrow calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (40_000 * 10**6) ) }); // 4. Store expected: min yvWETH output after swap + deposit // 50k USDC -> ~25 WETH -> ~25 yvWETH, minus 0.5% slippage BalanceDelta[] memory deltas = new BalanceDelta[](1); deltas[0] = BalanceDelta({ token: yvWETH, amount: int256(24.875 ether) }); calls[3] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.storeExpectedBalances, (deltas) ) }); // 5. Swap USDC -> WETH calls[4] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall(IUniswapV3Adapter.exactInputSingle, (swapParams)) }); // 6. Deposit WETH -> yvWETH (using diff pattern) calls[5] = MultiCall({ target: yearnAdapter, callData: abi.encodeCall(IYearnV2Adapter.depositDiff, (1)) // Leave 1 wei }); // 7. Compare balances - CRITICAL slippage check calls[6] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.compareBalances, () ) }); // 8. Set quota for yvWETH calls[7] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (yvWETH, int96(50_000 * 10**6), uint96(50_000 * 10**6)) ) }); ``` ## Gotchas ### Expected Balances Must Not Already Be Set `storeExpectedBalances` reverts if already called without a `compareBalances`: ```solidity // First store - OK storeExpectedBalances(deltas1); // Second store without compare - REVERTS storeExpectedBalances(deltas2); // Correct pattern: storeExpectedBalances(deltas1); // ... operations ... compareBalances(); storeExpectedBalances(deltas2); // Now OK ``` ### Compare Reverts If Not Stored `compareBalances` reverts if no expected balances were stored: ```solidity // This reverts - nothing to compare against compareBalances(); ``` ### Delta Calculation The delta is added to CURRENT balance, not starting balance: ```solidity // If account has 10 WETH and you set delta = +5 // Expected balance = 10 + 5 = 15 WETH // For spending tokens, use negative delta // If spending up to 1000 USDC: BalanceDelta({token: usdc, amount: -1000 * 10**6}) ``` ### Available in All Multicalls Unlike most operations, slippage checks work in: * `openCreditAccount` * `multicall` * `closeCreditAccount` * `botMulticall` * `liquidateCreditAccount` ### Gas Optimization For simple swaps, you can rely on the DEX's slippage parameter: ```solidity // Uniswap's amountOutMinimum is often sufficient ISwapRouter.ExactInputSingleParams({ // ... amountOutMinimum: minWethOut, // Built-in slippage protection // ... }) ``` Use `storeExpectedBalances`/`compareBalances` for: * Multi-hop routes where intermediate tokens aren't checked * Complex strategies with multiple operations * When you need to check multiple tokens atomically ## See Also * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Swap via adapters * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - Price updates before operations * [The Diff Pattern](https://docs.gearbox.finance/developers/overview-2) - Handling unknown amounts ## Making External Calls Source: https://docs.gearbox.finance/developers/making-external-calls-2 File: content/developers/making-external-calls-2.mdx Interact with DeFi protocols through adapters. > For SDK implementation, see [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls). ## Why External calls enable Credit Accounts to: * **Swap tokens** - Trade via Uniswap, Curve, Balancer * **Provide liquidity** - Add to LP pools * **Stake** - Deposit into yield protocols (Yearn, Convex, Aura) * **Leverage farm** - Build complex DeFi strategies All external interactions go through adapters - specialized contracts that translate Credit Account calls to protocol-specific formats. ## What Adapters wrap external protocol contracts. On execution: 1. Credit Account calls adapter (not the protocol directly) 2. Adapter translates the call and executes on the target protocol 3. Adapter routes output tokens back to Credit Account 4. Token states are updated automatically **Important:** Never call external protocols directly from a Credit Account. Only use registered adapters. ## How ### Finding Adapters Get the adapter address for a target protocol: ```solidity address uniswapRouter = 0x...; // Target protocol contract address uniswapAdapter = ICreditManagerV3(creditManager).contractToAdapter(uniswapRouter); require(uniswapAdapter != address(0), "Adapter not found"); ``` ### Basic Swap via Uniswap ```solidity import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import {IUniswapV3Adapter} from "@gearbox-protocol/integrations-v3/contracts/interfaces/uniswap/IUniswapV3Adapter.sol"; address creditFacade; address creditAccount; address creditManager; address usdc; address weth; // Get the Uniswap V3 adapter address uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address uniswapAdapter = ICreditManagerV3(creditManager).contractToAdapter(uniswapRouter); MultiCall[] memory calls = new MultiCall[](1); // Encode the swap params ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: usdc, tokenOut: weth, fee: 500, // 0.05% pool recipient: address(0), // Adapter overrides to credit account deadline: block.timestamp + 3600, amountIn: 50_000 * 10**6, amountOutMinimum: 0, // Use Gearbox slippage check instead sqrtPriceLimitX96: 0 }); calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall(IUniswapV3Adapter.exactInputSingle, (params)) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Using the Diff Pattern When you don't know the exact input amount (e.g., after a previous swap), use diff functions: ```solidity // After swapping to WETH, deposit all but 1 wei to Yearn address yearnVault = 0x...; // yvWETH address address yearnAdapter = ICreditManagerV3(creditManager).contractToAdapter(yearnVault); calls[1] = MultiCall({ target: yearnAdapter, callData: abi.encodeCall( IYearnV2Adapter.depositDiff, (1) // Leave 1 wei of WETH, deposit the rest ) }); ``` ### Multi-Protocol Strategy Swap on Uniswap, then stake in Convex: ```solidity MultiCall[] memory calls = new MultiCall[](3); // 1. Swap USDC -> WETH on Uniswap calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall(IUniswapV3Adapter.exactInputSingle, (swapParams)) }); // 2. Swap WETH -> stETH on Curve calls[1] = MultiCall({ target: curveAdapter, callData: abi.encodeCall( ICurveV1Adapter.exchange, (0, 1, wethAmount, minStethOut) ) }); // 3. Deposit stETH to Convex (using diff) calls[2] = MultiCall({ target: convexBoosterAdapter, callData: abi.encodeCall( IConvexV1BoosterAdapter.depositDiff, (convexPoolId, 1, true) // Leave 1 wei, stake = true ) }); ``` ### Balancer Multi-Hop Swap ```solidity import {IBalancerV2VaultAdapter, SingleSwap, SwapKind} from "..."; SingleSwap memory singleSwap = SingleSwap({ poolId: balancerPoolId, kind: SwapKind.GIVEN_IN, assetIn: IAsset(usdc), assetOut: IAsset(weth), amount: 50_000 * 10**6, userData: "" }); calls[0] = MultiCall({ target: balancerAdapter, callData: abi.encodeCall( IBalancerV2VaultAdapter.swap, (singleSwap, fundManagement, 0, block.timestamp + 3600) ) }); ``` ## Gotchas ### Recipient is Always Credit Account Adapters override the recipient parameter: ```solidity // Even if you specify a different recipient... params.recipient = someOtherAddress; // ...the adapter routes output to the Credit Account // This is a security feature - you can't extract funds via adapters ``` ### Approvals are Handled Automatically Adapters manage token approvals internally. You don't need to approve tokens to the adapter or target protocol. ### Only Allowed Adapters Work The Credit Manager only accepts calls to registered adapters: ```solidity // This reverts if the adapter isn't registered ICreditFacadeV3(creditFacade).multicall(account, [ MultiCall({target: unregisteredAdapter, callData: ...}) ]); ``` ### Check Adapter Type Different adapter versions have different interfaces: ```solidity bytes32 adapterType = IAdapter(adapter).contractType(); // e.g., "ADAPTER::UNISWAP_V3_ROUTER" // "ADAPTER::CURVE_V1_STABLE_NG" // "ADAPTER::CVX_V1_BOOSTER" ``` ### Diff Functions Require Balance Diff functions calculate: `amountIn = currentBalance - leftoverAmount` If the balance is less than `leftoverAmount`, the operation will fail: ```solidity // If WETH balance is 0.5 ETH and you call: depositDiff(1 ether); // REVERTS - not enough balance // Correct: leave less than current balance depositDiff(1); // Deposits 0.5 ETH - 1 wei ``` ### External Calls Set Permission Flag The first adapter call sets `EXTERNAL_CONTRACT_WAS_CALLED_FLAG`: ```solidity // This flag affects: // - Additional validation in collateral checks // - Gas estimation for health factor calculation ``` ### Token Enabling Adapters automatically enable output tokens. After a swap: * Output token mask is set on the Credit Account * Token counts toward collateral (if it has quota or is underlying) ## See Also * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protect swap outputs * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Token state management * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - Enable output tokens as collateral ## Enabling/Disabling Tokens Source: https://docs.gearbox.finance/developers/enabling-disabling-tokens-2 File: content/developers/enabling-disabling-tokens-2.mdx Explicitly manage which tokens count as collateral. > For SDK implementation, see [Enabling and Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens). ## Why You manually enable/disable tokens when: * **Direct transfers** - Tokens sent directly to Credit Account aren't auto-enabled * **Gas optimization** - Disable unused tokens to reduce collateral check cost * **Risk management** - Prevent certain tokens from counting in health factor * **Edge cases** - Override automatic enable/disable behavior Most of the time you don't need this - tokens auto-enable/disable based on balance changes. But sometimes manual control is necessary. ## What Non-quoted tokens have automatic enable/disable behavior: | Balance Change | Action | | -------------- | ------------ | | 0/1 to > 1 | Auto-enable | | > 1 to 0/1 | Auto-disable | `enableToken` and `disableToken` let you override this when needed. **Important:** These functions only work on **non-quoted tokens**. Quota tokens can only be enabled/disabled via `updateQuota`. ## How ### Enable a Token ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; address tokenToEnable; MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.enableToken, (tokenToEnable) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Disable a Token ```solidity // Disable a token to reduce collateral check gas cost calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.disableToken, (tokenToDisable) ) }); ``` ### Enable Token After Direct Transfer If tokens are sent directly to your Credit Account (not through an adapter): ```solidity // Token was transferred directly - won't count as collateral until enabled // First, some external contract sends tokens: // IERC20(token).transfer(creditAccount, amount); // Now enable it to count as collateral MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.enableToken, (directlyTransferredToken) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Disable Multiple Unused Tokens Reduce gas costs by disabling tokens with zero balance: ```solidity // Get enabled token mask from credit manager uint256 enabledTokensMask = ICreditManagerV3(creditManager).enabledTokensMaskOf(creditAccount); // Build calls to disable each zero-balance token // This is a simplified example - in practice, iterate through mask MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.disableToken, (unusedToken1) ) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.disableToken, (unusedToken2) ) }); ``` ## Gotchas ### No-Op for Quota Tokens Calling `enableToken` or `disableToken` on a quota token does nothing: ```solidity // This does nothing - quota tokens use updateQuota MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.enableToken, (quotaTokenAddress) // No effect! ) }); // Use this instead for quota tokens MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.updateQuota, (quotaTokenAddress, int96(quotaAmount), uint96(minQuota)) ) }); ``` ### Cannot Enable Forbidden Tokens Some tokens are marked as "forbidden" and cannot be enabled: ```solidity // This will revert MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.enableToken, (forbiddenTokenAddress) // Reverts! ) }); ``` Forbidden tokens must be swapped away, not disabled. ### Auto-Enable Usually Works Adapter calls automatically enable tokens when balance increases: ```solidity // This swap auto-enables WETH - no need to call enableToken MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) // Swaps USDC -> WETH ) }); // WETH is now enabled automatically ``` You only need manual enable when: * Tokens are transferred directly to Credit Account (not via adapter) * You want to enable a zero-balance token preemptively ### Max Enabled Tokens Limit Each Credit Manager has a maximum number of enabled tokens per account: ```solidity // Check the limit uint8 maxTokens = ICreditManagerV3(creditManager).maxEnabledTokens(); // Check current count via enabled mask popcount uint256 enabledMask = ICreditManagerV3(creditManager).enabledTokensMaskOf(creditAccount); // Count set bits in enabledMask to get current token count // Exceeding limit reverts the multicall ``` ### Disabled Tokens Still on Account Disabling a token doesn't remove it from the account - just excludes it from health factor: ```solidity // Token is disabled but balance stays disableToken(wethAddress); // Balance remains, just not counted as collateral // WARNING: Liquidators can claim disabled token balances as bonus! ``` **Warning:** Don't keep significant value in disabled tokens. During liquidation, liquidators can withdraw disabled tokens on top of their normal premium. ### Balance of 1 is "Zero" Gearbox treats balance of 0 and 1 the same (due to ERC20 rounding issues). Auto-disable triggers at balance <= 1: ```solidity // These are equivalent from Gearbox perspective // balance = 0 -> Disabled // balance = 1 -> Also disabled // This is enabled // balance = 2 ``` ## See Also * [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) - How to enable/disable quota tokens * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Auto-enable behavior with adapters * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Optimize checks for enabled tokens ## Updating Price Feeds Source: https://docs.gearbox.finance/developers/updating-price-feeds-2 File: content/developers/updating-price-feeds-2.mdx Push fresh price data for on-demand oracles (Pyth, Redstone). > For SDK implementation, see [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds). ## Why You update price feeds when: * **Using on-demand oracles** - Pyth and Redstone require fresh data with each transaction * **Multicalls fail** - "Stale price" errors indicate missing price updates * **Withdrawals** - Reserve price feeds may also need updates under safe pricing Some tokens use "pull-based" oracles that don't update automatically. You must push fresh price data before operations that need it. ## What `onDemandPriceUpdate` pushes oracle data to the price feed: 1. You obtain signed price data from the oracle provider (off-chain) 2. You include the price update as the FIRST call in your multicall 3. Credit Facade forwards the data to the price feed contract 4. The price feed validates the signature and updates **Critical rule:** All price updates must be at the **beginning** of the calls array. Any `onDemandPriceUpdate` after another call type will revert. ## How ### Basic Price Update ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; address tokenWithPythFeed; bytes memory priceData; // Obtained off-chain from Pyth/Redstone MultiCall[] memory calls = new MultiCall[](2); // Price update MUST be first calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, ( tokenWithPythFeed, // Token to update price for false, // reserve: false = main feed, true = reserve feed priceData // Signed price data from oracle ) ) }); // Now other operations calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, amount) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Multiple Price Updates Update several tokens at once (all must be at the start): ```solidity MultiCall[] memory calls = new MultiCall[](4); // All price updates first calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token1, false, priceData1) ) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token2, false, priceData2) ) }); // Then other operations calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, amount) ) }); calls[3] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (debtAmount) ) }); ``` ### Updating Reserve Feed (For Withdrawals) Withdrawals trigger safe pricing, which uses both main and reserve feeds: ```solidity MultiCall[] memory calls = new MultiCall[](3); // Main feed update calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (tokenAddress, false, mainPriceData) // reserve = false ) }); // Reserve feed update calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (tokenAddress, true, reservePriceData) // reserve = true ) }); // Now the withdrawal will work with safe pricing calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.withdrawCollateral, (otherToken, amount, recipient) ) }); ``` ### Contract Architecture for Price Updates Your contract must accept price data as a parameter since it cannot fetch oracle data on-chain: ```solidity contract MyGearboxStrategy { address public creditFacade; address public creditAccount; function executeStrategy( bytes[] calldata priceUpdates, // Must be passed from frontend uint256 amount ) external { uint256 numUpdates = priceUpdates.length; MultiCall[] memory calls = new MultiCall[](numUpdates + 1); // Build price update calls first for (uint256 i = 0; i < numUpdates; i++) { (address token, bool reserve, bytes memory data) = abi.decode(priceUpdates[i], (address, bool, bytes)); calls[i] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token, reserve, data) ) }); } // Then add strategy operations calls[numUpdates] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, amount) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); } } ``` ## Gotchas ### Price Updates MUST Be First This is the most common mistake. Price updates after any other call type revert: ```solidity // WRONG - price update after addCollateral MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (token, amount)) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token, false, priceData) // REVERTS! ) }); // CORRECT - price update first calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token, false, priceData) ) }); calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (token, amount)) }); ``` ### Fresh Data Required Price data has a short validity window (usually a few minutes). Fetch fresh data right before the transaction: ```solidity // Off-chain (e.g., in your frontend or bot): // 1. Fetch price data from Pyth/Redstone API // 2. Immediately use it in transaction // 3. DON'T cache price data for later ``` ### Not All Tokens Need Updates Only tokens with on-demand price feeds need updates. Tokens using Chainlink or other push-based oracles don't need `onDemandPriceUpdate`: ```solidity // Check feed type in your integration: // - PYTH feeds: need onDemandPriceUpdate // - REDSTONE feeds: need onDemandPriceUpdate // - Chainlink feeds: no update needed // - Other push oracles: no update needed ``` ### Disabled Tokens Don't Need Updates If a token will be disabled by the end of the multicall, you don't need to update its price: ```solidity // WETH is getting swapped entirely (will be disabled) MultiCall[] memory calls = new MultiCall[](2); // No need for WETH price update since it's being fully swapped calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (usdc, amount)) }); calls[1] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapAllWethToUsdc) // WETH will auto-disable after swap ) }); ``` ### Contracts Cannot Fetch Price Data The oracle's API must be called off-chain. Your smart contract receives price data as a parameter: ```solidity // Your contract CANNOT do this: // bytes memory priceData = pythOracle.fetchPrice(feedId); // Not possible on-chain! // Your contract MUST receive price data from caller: function myFunction(bytes calldata priceData) external { // Use priceData in multicall } ``` ## See Also * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Stale prices can cause slippage issues * [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) - May need reserve feed updates * [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params) - Related to price feed behavior ## Collateral Check Params Source: https://docs.gearbox.finance/developers/collateral-check-params-2 File: content/developers/collateral-check-params-2.mdx Optimize gas and set minimum health factor for collateral checks. > For SDK implementation, see [Collateral Check Params](https://docs.gearbox.finance/developers/collateral-check-params). ## Why You set collateral check params when: * **Optimizing gas** - Hint which tokens cover the debt to skip unnecessary oracle calls * **Risk management** - Enforce a minimum health factor above 1.0 * **Large accounts** - Many enabled tokens make default checks expensive * **Automated systems** - Bots can benefit from consistent gas costs The collateral check iterates through enabled tokens, summing value until it exceeds debt. Hints tell it which tokens to check first, potentially skipping expensive oracle calls. ## What `setFullCheckParams` configures two things: 1. **Collateral hints** - Token masks to prioritize during the check 2. **Min health factor** - Minimum acceptable HF (in basis points, 10000 = 1.0) If you know your USDC and WETH cover the debt, pass their masks as hints. The check evaluates them first and may stop early without checking other tokens. ## How ### Basic Usage with Hints ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; address creditFacade; address creditAccount; address creditManager; address usdc; address weth; // Get token masks (each token has a unique bitmask) uint256 usdcMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(usdc); uint256 wethMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(weth); // Build hints array uint256[] memory hints = new uint256[](2); hints[0] = usdcMask; hints[1] = wethMask; MultiCall[] memory calls = new MultiCall[](2); // Set hints at the start of multicall calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, ( hints, // Check these tokens first 10000 // minHealthFactor: 1.0 (10000 bps) ) ) }); // Rest of your multicall operations calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 100_000 * 10**6) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Setting Higher Min Health Factor Require account to maintain at least 1.2 HF: ```solidity uint256 MIN_HF_120 = 12000; // 1.2 in basis points uint256[] memory hints = new uint256[](0); // No hints MultiCall[] memory calls = new MultiCall[](2); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, MIN_HF_120) ) }); // Operations that must maintain 1.2 HF... calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (borrowAmount) ) }); ``` ### Complete Gas-Optimized Strategy ```solidity address creditManager; address usdc; address uniswapAdapter; // Get mask for primary collateral token uint256 usdcMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(usdc); uint256[] memory hints = new uint256[](1); hints[0] = usdcMask; MultiCall[] memory calls = new MultiCall[](4); // 1. Hints first - USDC will cover most of debt calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 10500) // Require 1.05 HF minimum ) }); // 2. Add collateral calls[1] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (usdc, 100_000 * 10**6) ) }); // 3. Borrow calls[2] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (200_000 * 10**6) ) }); // 4. Swap some USDC to WETH calls[3] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ## Gotchas ### Masks, Not Addresses The hints array takes token **masks**, not addresses: ```solidity // WRONG - passing addresses uint256[] memory hints = new uint256[](2); hints[0] = uint256(uint160(usdcAddress)); // Wrong! hints[1] = uint256(uint160(wethAddress)); // Wrong! // CORRECT - passing masks hints[0] = ICreditManagerV3(creditManager).getTokenMaskOrRevert(usdc); hints[1] = ICreditManagerV3(creditManager).getTokenMaskOrRevert(weth); ``` ### Hints Are Optimization, Not Guarantee The check still validates ALL enabled tokens - hints just change the order. If hints don't cover the debt, it continues with remaining tokens: ```solidity // If USDC hint doesn't fully cover debt, // WETH and other tokens are still checked // Hints just potentially skip some oracle calls ``` ### Min Health Factor Must Be >= 10000 You cannot set a health factor below 1.0: ```solidity // WRONG - less than 10000 reverts calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 9500) // Reverts! ) }); // CORRECT - must be >= 10000 calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 10000) // Exactly 1.0 - OK ) }); ``` ### Hints Don't Help Small Accounts For accounts with few enabled tokens (< 5), hints add gas overhead without saving much. Only use for accounts with many tokens. ### Order Matters in Hints Array Tokens are checked in the order you provide: ```solidity // Check WETH first, then USDC hints[0] = wethMask; hints[1] = usdcMask; // Check USDC first, then WETH hints[0] = usdcMask; hints[1] = wethMask; ``` Put your highest-value collateral first for best gas savings. ### Token Mask Calculation Token masks are powers of 2, assigned sequentially when tokens are added to the Credit Manager: ```solidity // First token: mask = 1 (2^0) // Second token: mask = 2 (2^1) // Third token: mask = 4 (2^2) // etc. // Always use getTokenMaskOrRevert to get the correct mask uint256 mask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(tokenAddress); ``` ### Params Reset After Multicall `setFullCheckParams` only affects the current multicall's final check. Next multicall uses defaults again: ```solidity // Multicall 1 - with hints ICreditFacadeV3(creditFacade).multicall(account, callsWithHints); // Multicall 2 - back to default (no hints) ICreditFacadeV3(creditFacade).multicall(account, callsWithoutHints); ``` ### Combine Hints and Min HF Use hints for gas optimization AND min HF for risk management: ```solidity uint256[] memory hints = new uint256[](2); hints[0] = primaryCollateralMask; // Gas optimization hints[1] = secondaryCollateralMask; calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 11000) // Risk management: require 1.1 HF ) }); ``` ## See Also * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Affects which tokens are checked * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - Oracle calls that hints can skip * [Debt Management](https://docs.gearbox.finance/developers/debt-management) - Debt determines what TWV must cover ## Smart Contracts Source: https://docs.gearbox.finance/developers/gm-contracts File: content/developers/gm-contracts.mdx The Gearbox V3 protocol is composed of several interconnected Solidity contracts. This section provides a contract-level reference organized by **Write Methods**, **View Methods**, and **Events** for each core contract. ## Contract Architecture The Credit Suite handles leveraged positions through three tightly coupled contracts: - **[Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm)** -- Core accounting engine. Tracks debt, collateral, and health factors for all Credit Accounts. - **[Credit Facade](https://docs.gearbox.finance/developers/gm-ref-cf)** -- User-facing entry point. Handles multicall execution, access control, and debt limit enforcement. - **[Credit Configurator](https://docs.gearbox.finance/developers/gm-ref-cc)** -- Administrative gateway. Validates and propagates parameter changes to the Credit Manager and Facade. The Pool layer manages liquidity and risk parameters: - **[Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool)** -- ERC-4626 vault that holds underlying assets. Credit Managers borrow from it; LPs deposit into it. - **[Quota Keeper](https://docs.gearbox.finance/developers/gm-ref-qk)** -- Manages per-token quota limits and interest rates. Controls collateral exposure at the pool level. Data aggregation is handled by periphery contracts: - **[Compressors](https://docs.gearbox.finance/developers/gm-ref-compressors)** -- Read-only contracts that aggregate on-chain state into single calls. Used by the SDK, bots, and on-chain integrations. ## How to Use This Reference Each contract page lists method signatures, parameter tables, return values, and emitted events. Methods are grouped into: | Section | Description | | --- | --- | | **Write Methods** | State-changing functions (transactions) | | **View Methods** | Read-only functions (free calls) | | **Events** | Emitted log entries for indexing and monitoring | For conceptual guides and tutorials, see the main [Developers](/developers) section. ## Credit Manager Source: https://docs.gearbox.finance/developers/gm-ref-cm File: content/developers/gm-ref-cm.mdx The **CreditManagerV3** is the core accounting engine of the Gearbox Protocol. It manages Credit Account lifecycle, tracks debt and collateral, calculates health factors, and enforces risk parameters. All Credit Account state is stored and managed through this contract. ## Core Data Structures ### CreditAccountInfo Every Credit Account's state is tracked in a `CreditAccountInfo` struct: | Field | Type | Description | | --- | --- | --- | | `debt` | `uint256` | Principal amount borrowed from the pool | | `cumulativeIndexLastUpdate` | `uint256` | Pool's interest index at last debt update | | `cumulativeQuotaInterest` | `uint128` | Accrued quota interest not yet added to debt | | `quotaFees` | `uint128` | Quota fees owed | | `enabledTokensMask` | `uint256` | Bitmask of currently enabled collateral tokens | | `flags` | `uint16` | Account state flags (e.g., `BOT_PERMISSIONS_SET_FLAG`) | | `lastDebtUpdate` | `uint64` | Timestamp of last debt change (flash-loan protection) | | `borrower` | `address` | Current account owner | ### Collateral Calculation Modes | Mode | Use Case | | --- | --- | | `DEBT_ONLY` | Calculate debt + interest without collateral | | `DEBT_COLLATERAL` | Full TWV + HF calculation | | `FULL_COLLATERAL_CHECK_LAZY` | Optimized: stops early when HF exceeds threshold | --- ## Write Methods ### openCreditAccount Opens a new Credit Account for a borrower. ```solidity function openCreditAccount(address onBehalfOf) external returns (address creditAccount); ``` | Parameter | Type | Description | | --- | --- | --- | | `onBehalfOf` | `address` | Address that will own the Credit Account | **Returns:** Address of the newly created Credit Account. **Access:** CreditFacade only. --- ### closeCreditAccount Closes a Credit Account and settles all debt. ```solidity function closeCreditAccount(address creditAccount) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Address of the Credit Account to close | **Access:** CreditFacade only. Repays pool debt and returns remaining assets to the borrower. --- ### liquidateCreditAccount Liquidates an unhealthy Credit Account. ```solidity function liquidateCreditAccount( address creditAccount, uint256 collateralDebtData, address to ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to liquidate | | `collateralDebtData` | `uint256` | Pre-computed collateral and debt data | | `to` | `address` | Address to receive remaining assets | **Access:** CreditFacade only. Settles debt with pool, reports profit/loss, and distributes remaining collateral. --- ### manageDebt Increases or decreases the debt of a Credit Account. ```solidity function manageDebt( address creditAccount, uint256 amount, uint256 enabledTokensMask, ManageDebtAction action ) external returns (uint256 newDebt, uint256 tokensToEnable, uint256 tokensToDisable); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Target Credit Account | | `amount` | `uint256` | Amount to increase or decrease | | `enabledTokensMask` | `uint256` | Current enabled tokens mask | | `action` | `ManageDebtAction` | `INCREASE_DEBT` or `DECREASE_DEBT` | **Returns:** New debt amount and token mask changes. **Access:** CreditFacade only. Flash-loan protection prevents multiple debt changes in the same block. --- ### addCollateral Adds collateral tokens to a Credit Account. ```solidity function addCollateral( address payer, address creditAccount, address token, uint256 amount ) external returns (uint256 tokensToEnable); ``` | Parameter | Type | Description | | --- | --- | --- | | `payer` | `address` | Address providing the tokens | | `creditAccount` | `address` | Receiving Credit Account | | `token` | `address` | Token to add | | `amount` | `uint256` | Amount to transfer | **Returns:** Token mask to enable. **Access:** CreditFacade only. --- ### withdrawCollateral Withdraws collateral tokens from a Credit Account. ```solidity function withdrawCollateral( address creditAccount, address token, uint256 amount, address to ) external returns (uint256 tokensToDisable); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Source Credit Account | | `token` | `address` | Token to withdraw | | `amount` | `uint256` | Amount to withdraw | | `to` | `address` | Recipient address | **Returns:** Token mask to disable (if balance reaches zero). **Access:** CreditFacade only. --- ### setActiveCreditAccount Sets the active Credit Account for adapter execution during multicalls. ```solidity function setActiveCreditAccount(address creditAccount) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to set as active | **Access:** CreditFacade only. --- ### execute Executes an external call from a Credit Account through a whitelisted adapter. ```solidity function execute(bytes calldata data) external returns (bytes memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `data` | `bytes` | Encoded function call to execute | **Returns:** Return data from the adapter call. **Access:** Credit Account (via adapter) only. --- ### fullCollateralCheck Performs a full collateral check on a Credit Account after operations. ```solidity function fullCollateralCheck( address creditAccount, uint256 enabledTokensMask, uint256[] calldata collateralHints, uint16 minHealthFactor, bool useSafePrices ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to check | | `enabledTokensMask` | `uint256` | Current enabled tokens mask | | `collateralHints` | `uint256[]` | Hints for optimized collateral iteration | | `minHealthFactor` | `uint16` | Minimum acceptable HF (typically 10000) | | `useSafePrices` | `bool` | Whether to use safe (conservative) prices | **Access:** CreditFacade only. Reverts if health factor is below minimum. --- ## View Methods ### calcDebtAndCollateral Calculates debt, interest, and collateral values for a Credit Account. ```solidity function calcDebtAndCollateral( address creditAccount, CollateralCalcTask task ) external view returns (CollateralDebtData memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to calculate for | | `task` | `CollateralCalcTask` | Calculation mode (see Collateral Calculation Modes) | **Returns:** `CollateralDebtData` struct with `debt`, `accruedInterest`, `accruedFees`, `totalDebtUSD`, `totalValue`, `twvUSD`, `enabledTokensMask`. --- ### creditAccountInfo Returns the full state struct for a Credit Account. ```solidity function creditAccountInfo(address creditAccount) external view returns (CreditAccountInfo memory); ``` --- ### enabledTokensMaskOf Returns the bitmask of enabled collateral tokens for an account. ```solidity function enabledTokensMaskOf(address creditAccount) external view returns (uint256); ``` --- ### flagsOf Returns the flags for a Credit Account. ```solidity function flagsOf(address creditAccount) external view returns (uint16); ``` --- ### getBorrowerOrRevert Returns the borrower address of a Credit Account, reverting if not found. ```solidity function getBorrowerOrRevert(address creditAccount) external view returns (address); ``` --- ### getTokenMaskOrRevert Returns the bitmask for a given token address, reverting if not registered. ```solidity function getTokenMaskOrRevert(address token) external view returns (uint256); ``` --- ### getTokenByMask Returns the token address for a given bitmask. ```solidity function getTokenByMask(uint256 tokenMask) external view returns (address); ``` --- ### collateralTokenByMask Returns collateral token data including liquidation threshold parameters. ```solidity function collateralTokenByMask(uint256 tokenMask) external view returns (address token, uint16 ltInitial, uint16 ltFinal, uint40 timestampRampStart, uint24 rampDuration); ``` --- ### forbiddenTokenMask Returns the bitmask of forbidden tokens. ```solidity function forbiddenTokenMask() external view returns (uint256); ``` --- ### contractToAdapter / adapterToContract Returns the adapter for a target contract, or vice versa. ```solidity function contractToAdapter(address targetContract) external view returns (address); function adapterToContract(address adapter) external view returns (address); ``` --- ### adapters Returns all registered adapter addresses. ```solidity function adapters() external view returns (address[] memory); ``` --- ### creditFacade / creditConfigurator / pool / underlying Returns addresses of associated contracts. ```solidity function creditFacade() external view returns (address); function creditConfigurator() external view returns (address); function pool() external view returns (address); function underlying() external view returns (address); ``` --- ## Events ### OpenCreditAccount ```solidity event OpenCreditAccount(address indexed creditAccount, address indexed onBehalfOf); ``` Emitted when a new Credit Account is opened. ### CloseCreditAccount ```solidity event CloseCreditAccount(address indexed creditAccount, address indexed borrower); ``` Emitted when a Credit Account is closed. ### LiquidateCreditAccount ```solidity event LiquidateCreditAccount( address indexed creditAccount, address indexed liquidator, address to, uint256 remainingFunds ); ``` Emitted when a Credit Account is liquidated. ### ManageDebt ```solidity event ManageDebt(address indexed creditAccount, uint256 amount, ManageDebtAction action); ``` Emitted when debt is increased or decreased. ### AddCollateral ```solidity event AddCollateral(address indexed creditAccount, address indexed token, uint256 amount); ``` Emitted when collateral is added to an account. ### WithdrawCollateral ```solidity event WithdrawCollateral(address indexed creditAccount, address indexed token, uint256 amount, address to); ``` Emitted when collateral is withdrawn from an account. --- ## Related Pages - [Credit Facade](https://docs.gearbox.finance/developers/gm-ref-cf) -- User-facing interface for Credit Account operations - [Credit Configurator](https://docs.gearbox.finance/developers/gm-ref-cc) -- Administrative parameter management - [Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool) -- Liquidity pool that Credit Manager borrows from - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Revoke Allowances Source: https://docs.gearbox.finance/developers/revoke-allowances-2 File: content/developers/revoke-allowances-2.mdx Revoke Credit Account's token approvals to external contracts. > For SDK implementation, see [Revoke Allowances](https://docs.gearbox.finance/developers/revoke-allowances). ## Why You revoke allowances when: * **Security incident** - Third-party contract may be compromised * **Legacy cleanup** - Old accounts may have stale approvals from previous Gearbox versions * **Defense in depth** - Proactively remove unnecessary approvals Current Gearbox V3 automatically resets allowances to 1 after each interaction. However, older accounts from V2.1 may still have active allowances to external protocols. ## What `revokeAdapterAllowances` resets token approvals from your Credit Account to specified contracts: 1. You specify which (spender, token) pairs to revoke 2. Credit Account sets allowance to 1 for each pair 3. External contracts can no longer spend those tokens **Note:** Allowance is set to 1, not 0, due to gas optimization (writing non-zero to non-zero is cheaper than writing zero). ## How ### Basic Revocation ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {RevocationPair} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; address creditFacade; address creditAccount; address uniswapAdapter; address curveAdapter; address usdc; address dai; // Define which approvals to revoke RevocationPair[] memory revocations = new RevocationPair[](2); revocations[0] = RevocationPair({ spender: uniswapAdapter, token: usdc }); revocations[1] = RevocationPair({ spender: curveAdapter, token: dai }); MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.revokeAdapterAllowances, (revocations) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Check Existing Allowances Before revoking, check what allowances exist: ```solidity import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // Check allowance from Credit Account to a spender uint256 allowance = IERC20(tokenAddress).allowance(creditAccount, spenderAddress); if (allowance > 1) { // Credit Account has active allowance to this spender // Consider revoking it } ``` ### Revoke All Adapter Allowances for a Token ```solidity // Get all adapters from Credit Manager address[] memory adapters = ICreditManagerV3(creditManager).adapters(); // Build revocations for all adapters RevocationPair[] memory revocations = new RevocationPair[](adapters.length); for (uint256 i = 0; i < adapters.length; i++) { revocations[i] = RevocationPair({ spender: adapters[i], token: tokenAddress }); } MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: creditFacade, callData: abi.encodeCall( ICreditFacadeV3Multicall.revokeAdapterAllowances, (revocations) ) }); ICreditFacadeV3(creditFacade).multicall(creditAccount, calls); ``` ### Emergency Revocation Pattern For security incidents, revoke immediately: ```solidity contract EmergencyRevoke { ICreditFacadeV3 public creditFacade; function emergencyRevokeAll( address creditAccount, address compromisedAdapter, address[] calldata tokens ) external { RevocationPair[] memory revocations = new RevocationPair[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { revocations[i] = RevocationPair({ spender: compromisedAdapter, token: tokens[i] }); } MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.revokeAdapterAllowances, (revocations) ) }); creditFacade.multicall(creditAccount, calls); } } ``` ## Gotchas ### Usually Not Needed in V3 Gearbox V3 automatically resets allowances after each adapter interaction. This function exists mainly for: 1. Legacy accounts migrated from V2.1 2. Paranoid security posture 3. Specific incident response If you're using a fresh V3 account, allowances are already minimal. ### RevocationPair Struct Format The struct has two fields: ```solidity struct RevocationPair { address spender; // Contract that has the allowance address token; // Token that was approved } ``` Both must be valid addresses. Invalid addresses may cause the call to revert or have no effect. ### Sets to 1, Not 0 For gas efficiency, allowances are set to 1 instead of 0: ```solidity // Before revocation: allowance = 1000000000000000000 (1 token) // After revocation: allowance = 1 (essentially zero for practical purposes) ``` An allowance of 1 wei is functionally zero for any realistic token amount. ### Can't Revoke Non-Existent Allowances Revoking an allowance that doesn't exist (already 0 or 1) is a no-op - it won't revert, but wastes gas: ```solidity // If allowance is already 0 or 1, this does nothing but costs gas revocations[0] = RevocationPair({ spender: adapterWithNoAllowance, token: token }); ``` ### Adapter vs External Contract Revocations target the **spender** (usually an adapter), not the underlying protocol: ```solidity // The adapter has the allowance, not Uniswap directly RevocationPair({ spender: uniswapAdapter, // Adapter address, not Uniswap Router token: usdcAddress }); ``` Adapters are what actually interact with your Credit Account's tokens. ### Batch Multiple Revocations You can revoke multiple (spender, token) pairs in one call: ```solidity // Efficient - single call with multiple revocations RevocationPair[] memory revocations = new RevocationPair[](3); revocations[0] = RevocationPair({spender: adapter1, token: usdc}); revocations[1] = RevocationPair({spender: adapter1, token: dai}); revocations[2] = RevocationPair({spender: adapter2, token: usdc}); // Less efficient - multiple multicalls // Don't do this if you can batch them together ``` ### When to Actually Use This Real scenarios where revocation makes sense: 1. **Third-party exploit:** A protocol Gearbox integrates with gets hacked. Revoke allowances to that protocol's adapter as a precaution. 2. **Account migration:** Moving from V2.1 account with old allowances to ensure clean state. 3. **Compliance requirement:** Some regulatory frameworks require revoking unused approvals. 4. **Personal security policy:** You want explicit control over all approvals. For normal operations, V3's automatic reset is sufficient. ## See Also * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - How adapters use allowances * [Enabling/Disabling Tokens](https://docs.gearbox.finance/developers/enabling-disabling-tokens) - Related account management ## Credit Facade Source: https://docs.gearbox.finance/developers/gm-ref-cf File: content/developers/gm-ref-cf.mdx The **CreditFacadeV3** is the primary user-facing interface for Credit Account operations. It implements atomic multicall batching, enforces debt limits, manages bot permissions, and ensures all operations complete with a healthy account state. ## Core Data Structures ### MultiCall ```solidity struct MultiCall { address target; // Facade itself or whitelisted adapter bytes callData; // Function selector + encoded arguments } ``` ### DebtLimits ```solidity struct DebtLimits { uint128 minDebt; // Minimum principal (except 0) uint128 maxDebt; // Maximum principal } ``` ### Bot Permissions Bot permissions are stored as a `uint192` bitmask: | Permission | Value | Operation | | --- | --- | --- | | `ADD_COLLATERAL_PERMISSION` | `1 << 0` | Add funds to account | | `INCREASE_DEBT_PERMISSION` | `1 << 1` | Borrow more from pool | | `DECREASE_DEBT_PERMISSION` | `1 << 2` | Repay debt | | `WITHDRAW_COLLATERAL_PERMISSION` | `1 << 5` | Withdraw assets | | `UPDATE_QUOTA_PERMISSION` | `1 << 6` | Change token quotas | | `EXTERNAL_CALLS_PERMISSION` | `1 << 16` | Execute adapter calls | --- ## Write Methods ### openCreditAccount Creates a new Credit Account with initial operations executed atomically. ```solidity function openCreditAccount( MultiCall[] calldata calls, address onBehalfOf ) external returns (address creditAccount); ``` | Parameter | Type | Description | | --- | --- | --- | | `calls` | `MultiCall[]` | Initial operations (add collateral, increase debt, etc.) | | `onBehalfOf` | `address` | Owner of the new Credit Account | **Returns:** Address of the created Credit Account. **Access:** Anyone. A full collateral check is performed after all calls execute. --- ### closeCreditAccount Closes a Credit Account, repaying all debt and returning remaining assets. ```solidity function closeCreditAccount( address creditAccount, MultiCall[] calldata calls ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to close | | `calls` | `MultiCall[]` | Operations to execute before closing (e.g., swap to underlying) | **Access:** Account owner only (`creditAccountOwnerOnly`). --- ### multicall Executes a batch of operations on an existing Credit Account. ```solidity function multicall( address creditAccount, MultiCall[] calldata calls ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Target account | | `calls` | `MultiCall[]` | Operations to execute | **Access:** Account owner only. Full collateral check after execution. --- ### botMulticall Executes a batch of operations initiated by an authorized bot. ```solidity function botMulticall( address creditAccount, MultiCall[] calldata calls ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Target account | | `calls` | `MultiCall[]` | Operations to execute | **Access:** Authorized bot only. Each operation is checked against the bot's permission bitmask. --- ### liquidateCreditAccount Liquidates an unhealthy Credit Account (HF < 1.0). ```solidity function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Account to liquidate | | `to` | `address` | Recipient of remaining funds | | `calls` | `MultiCall[]` | Operations to execute during liquidation | **Access:** Anyone (account must be liquidatable). --- ### setBotPermissions Grants or revokes bot permissions for a Credit Account. ```solidity function setBotPermissions( address creditAccount, address bot, uint192 permissions ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Target account | | `bot` | `address` | Bot address to authorize | | `permissions` | `uint192` | Permission bitmask (0 to revoke) | **Access:** Account owner only. --- ## Multicall-Only Methods These methods can only be called as part of a `MultiCall` array (encoded in `callData` with the Facade as `target`). ### addCollateral ```solidity function addCollateral(address token, uint256 amount) external; ``` Transfers tokens from the caller to the Credit Account. ### increaseDebt ```solidity function increaseDebt(uint256 amount) external; ``` Borrows additional funds from the pool. Subject to debt limits and per-block caps. ### decreaseDebt ```solidity function decreaseDebt(uint256 amount) external; ``` Repays debt to the pool. ### withdrawCollateral ```solidity function withdrawCollateral(address token, uint256 amount, address to) external; ``` Withdraws collateral from the Credit Account to the specified address. ### updateQuota ```solidity function updateQuota(address token, int96 quotaChange, uint96 minQuota) external; ``` Adjusts the quota for a specific token on the Credit Account. ### storeExpectedBalances / compareBalances ```solidity function storeExpectedBalances(BalanceWithMask[] calldata balances) external; function compareBalances() external; ``` Slippage protection pair: store expected balances before swaps, then verify after. --- ## View Methods ### debtLimits Returns the min/max debt bounds for this Credit Facade. ```solidity function debtLimits() external view returns (uint128 minDebt, uint128 maxDebt); ``` --- ### maxDebtPerBlockMultiplier Returns the per-block borrowing multiplier. Value of 0 means borrowing is disabled. ```solidity function maxDebtPerBlockMultiplier() external view returns (uint8); ``` --- ### creditManager Returns the associated Credit Manager address. ```solidity function creditManager() external view returns (address); ``` --- ### canLiquidate Checks whether a Credit Account is liquidatable. ```solidity function canLiquidate(address creditAccount) external view returns (bool); ``` --- ### botPermissions Returns the permission bitmask for a bot on a specific Credit Account. ```solidity function botPermissions(address creditAccount, address bot) external view returns (uint192); ``` --- ### expirable / expirationDate Returns whether this Facade has an expiration date and what it is. ```solidity function expirable() external view returns (bool); function expirationDate() external view returns (uint40); ``` --- ## Events ### StartMultiCall ```solidity event StartMultiCall(address indexed creditAccount, address indexed caller); ``` Emitted at the start of a multicall execution. ### FinishMultiCall ```solidity event FinishMultiCall(); ``` Emitted when a multicall completes successfully. ### OpenCreditAccount ```solidity event OpenCreditAccount( address indexed creditAccount, address indexed onBehalfOf, address indexed caller ); ``` Emitted when a Credit Account is opened through the Facade. ### CloseCreditAccount ```solidity event CloseCreditAccount(address indexed creditAccount, address indexed caller); ``` Emitted when a Credit Account is closed. ### LiquidateCreditAccount ```solidity event LiquidateCreditAccount( address indexed creditAccount, address indexed liquidator, address indexed to, uint256 remainingFunds ); ``` Emitted when a Credit Account is liquidated. ### SetBotPermissions ```solidity event SetBotPermissions( address indexed creditAccount, address indexed bot, uint192 permissions ); ``` Emitted when bot permissions are updated. --- ## Related Pages - [Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm) -- Core accounting engine - [Credit Configurator](https://docs.gearbox.finance/developers/gm-ref-cc) -- Administrative configuration - [Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool) -- Liquidity pool - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Pool Operations Source: https://docs.gearbox.finance/developers/pool-operations File: content/developers/pool-operations.mdx Interact with Gearbox pools directly from Solidity. Pools are ERC-4626 compliant vaults. > For SDK data reading, see [Reading Data](https://docs.gearbox.finance/developers/reading-data). ## IPoolV3 ```solidity import {IPoolV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolV3.sol"; IPoolV3 pool = IPoolV3(poolAddress); ``` ## ERC-4626 Standard Functions Gearbox pools implement the full ERC-4626 tokenized vault standard: ### Deposit Deposit underlying assets and receive diesel tokens (shares): ```solidity function deposit(uint256 assets, address receiver) external returns (uint256 shares); ``` **Example:** ```solidity // Approve underlying first IERC20(underlying).approve(address(pool), assets); // Deposit and receive shares uint256 shares = pool.deposit(assets, msg.sender); // Preview how many shares you'd receive uint256 expectedShares = pool.previewDeposit(assets); ``` ### Deposit with Referral Track referrals on-chain: ```solidity function depositWithReferral( uint256 assets, address receiver, uint256 referralCode ) external returns (uint256 shares); ``` **Example:** ```solidity IERC20(underlying).approve(address(pool), assets); uint256 shares = pool.depositWithReferral(assets, msg.sender, 123); ``` ### Mint Mint exact shares, depositing required assets: ```solidity function mint(uint256 shares, address receiver) external returns (uint256 assets); ``` **Example:** ```solidity // Preview required assets uint256 requiredAssets = pool.previewMint(shares); // Approve and mint IERC20(underlying).approve(address(pool), requiredAssets); uint256 assets = pool.mint(shares, msg.sender); ``` ### Withdraw Withdraw exact assets, burning required shares: ```solidity function withdraw( uint256 assets, address receiver, address owner ) external returns (uint256 shares); ``` **Example:** ```solidity // Withdraw exact assets uint256 sharesBurned = pool.withdraw(assets, msg.sender, msg.sender); // Preview how many shares would be burned uint256 expectedShares = pool.previewWithdraw(assets); ``` ### Redeem Burn exact shares, receiving assets: ```solidity function redeem( uint256 shares, address receiver, address owner ) external returns (uint256 assets); ``` **Example:** ```solidity // Redeem shares for underlying uint256 assets = pool.redeem(shares, msg.sender, msg.sender); // Preview how many assets you'd receive uint256 expectedAssets = pool.previewRedeem(shares); ``` ## Gearbox Extensions ### Diesel Rate (Share Price) ```solidity // Get current share price (in RAY - 27 decimals) uint256 rate = pool.dieselRate(); // 1 diesel token = rate / 10^27 underlying tokens ``` ### Interest Rates ```solidity // Annual supply rate for lenders (RAY) uint256 supplyRate = pool.supplyRate(); // Annual borrow rate for Credit Accounts (RAY) uint256 borrowRate = pool.baseInterestRate(); // Convert to percentage uint256 supplyAPY = supplyRate * 10000 / 10**27; // basis points ``` ### Liquidity State ```solidity // Total assets in pool uint256 totalAssets = pool.totalAssets(); // Available for borrowing uint256 available = pool.availableLiquidity(); // Total supply of diesel tokens uint256 totalSupply = pool.totalSupply(); // Calculate utilization uint256 utilization = ((totalAssets - available) * 10000) / totalAssets; // basis points ``` ### Underlying Asset ```solidity // Get underlying token address address underlying = pool.asset(); // Pool decimals (matches underlying) uint8 decimals = pool.decimals(); // Pool name and symbol (e.g., "diesel USDC", "dUSDC") string memory name = pool.name(); string memory symbol = pool.symbol(); ``` ### Maximum Operations ```solidity // Maximum depositable assets uint256 maxDeposit = pool.maxDeposit(receiver); // Maximum mintable shares uint256 maxMint = pool.maxMint(receiver); // Maximum withdrawable assets uint256 maxWithdraw = pool.maxWithdraw(owner); // Maximum redeemable shares uint256 maxRedeem = pool.maxRedeem(owner); ``` ## Credit Manager Interaction Only whitelisted Credit Managers can borrow. Regular users cannot call these: ```solidity // Credit Manager borrows from pool (CM-only) function lendCreditAccount(uint256 borrowedAmount, address creditAccount) external; // Credit Manager repays to pool (CM-only) function repayCreditAccount(uint256 repaidAmount, uint256 profit, uint256 loss) external; ``` ## Related Contracts ### Quota Keeper ```solidity // Get quota keeper address address quotaKeeper = pool.poolQuotaKeeper(); // Query quota parameters IPoolQuotaKeeperV3 qk = IPoolQuotaKeeperV3(quotaKeeper); (uint16 rate, , , uint96 totalQuoted, uint96 limit, bool isActive) = qk.getTokenQuotaParams(tokenAddress); ``` ### Interest Rate Model ```solidity // Get IRM address address irm = pool.interestRateModel(); // Query model parameters ILinearInterestRateModelV3 model = ILinearInterestRateModelV3(irm); (uint16 U1, uint16 U2, uint16 Rbase, uint16 Rslope1, uint16 Rslope2, uint16 Rslope3) = model.getModelParameters(); ``` ## Complete Example ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {IPoolV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolV3.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract PoolInteraction { IPoolV3 public immutable pool; address public immutable underlying; constructor(address _pool) { pool = IPoolV3(_pool); underlying = pool.asset(); } function depositAndGetState(uint256 amount) external returns ( uint256 shares, uint256 supplyAPY, uint256 utilization ) { // Transfer underlying from user IERC20(underlying).transferFrom(msg.sender, address(this), amount); // Approve and deposit IERC20(underlying).approve(address(pool), amount); shares = pool.deposit(amount, msg.sender); // Read state uint256 supplyRate = pool.supplyRate(); supplyAPY = supplyRate * 10000 / 10**27; // basis points uint256 totalAssets = pool.totalAssets(); uint256 available = pool.availableLiquidity(); utilization = ((totalAssets - available) * 10000) / totalAssets; } function redeemAll(address owner) external returns (uint256 assets) { uint256 shares = pool.balanceOf(owner); require(shares > 0, "No shares"); // Transfer shares from owner IERC20(address(pool)).transferFrom(owner, address(this), shares); // Redeem assets = pool.redeem(shares, owner, address(this)); } } ``` For architectural background, see [Pool Architecture](https://docs.gearbox.finance/developers/pools). ## Credit Configurator Source: https://docs.gearbox.finance/developers/gm-ref-cc File: content/developers/gm-ref-cc.mdx The **CreditConfiguratorV3** is the administrative gateway for the Credit Suite. It validates parameter changes and propagates them to the Credit Manager and Credit Facade. It does not store configuration state itself -- it acts as a validation and access-control layer. ``` User/DAO -> CreditConfiguratorV3 (validation) -> CreditManagerV3/CreditFacadeV3 (state) ``` --- ## Write Methods ### Token & Collateral Management #### addCollateralToken Registers a new collateral token with an initial liquidation threshold. ```solidity function addCollateralToken(address token, uint16 liquidationThreshold) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | ERC-20 token to add | | `liquidationThreshold` | `uint16` | Initial LT in basis points (e.g., 8500 = 85%) | **Validation:** Token must have a price feed in PriceOracle, be quoted in PoolQuotaKeeper, and LT cannot exceed the underlying token's LT. **Access:** Configurator role only. --- #### setLiquidationThreshold Sets the liquidation threshold for a collateral token immediately. ```solidity function setLiquidationThreshold(address token, uint16 liquidationThreshold) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Collateral token | | `liquidationThreshold` | `uint16` | New LT in basis points | **Access:** Configurator role only. --- #### rampLiquidationThreshold Gradually changes a token's liquidation threshold over time to prevent sudden liquidation cascades. ```solidity function rampLiquidationThreshold( address token, uint16 ltFinal, uint40 rampStart, uint24 rampDuration ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Collateral token | | `ltFinal` | `uint16` | Target LT after ramping completes | | `rampStart` | `uint40` | Timestamp when ramping begins | | `rampDuration` | `uint24` | Duration of linear interpolation (seconds) | **Access:** Configurator role only. The current LT is linearly interpolated between the initial and final values over the ramp period. --- #### forbidToken Marks a token as forbidden (high-risk). Forbidden tokens still count toward collateral (with safe pricing) but quota increases and balance increases are restricted. ```solidity function forbidToken(address token) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Token to forbid | **Access:** Pausable Admin (no DAO vote required). --- #### allowToken Restores normal status to a previously forbidden token. ```solidity function allowToken(address token) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Token to allow | **Access:** Configurator role only. --- ### Fee Management #### setFees Configures liquidation fees and premiums. ```solidity function setFees( uint16 feeLiquidation, uint16 liquidationPremium, uint16 feeLiquidationExpired, uint16 liquidationPremiumExpired ) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `feeLiquidation` | `uint16` | DAO fee on standard liquidations (bps) | | `liquidationPremium` | `uint16` | Reward for liquidators (bps) | | `feeLiquidationExpired` | `uint16` | DAO fee for expired account liquidations (bps) | | `liquidationPremiumExpired` | `uint16` | Liquidator reward for expired accounts (bps) | **Constraints:** `feeLiquidation <= liquidationPremium`, `feeLiquidationExpired <= feeLiquidation`, and `liquidationPremium + feeLiquidation < 100%`. **Access:** Configurator role only. --- ### Debt Limits #### setDebtLimits Sets the minimum and maximum debt bounds per Credit Account. ```solidity function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `newMinDebt` | `uint128` | Minimum principal (must have non-zero USD value) | | `newMaxDebt` | `uint128` | Maximum principal | **Validation:** `minDebt <= maxDebt`. Safety ratio ensures min debt is large enough to make liquidation economical. **Access:** Configurator role only. --- #### setMaxDebtPerBlockMultiplier Sets the per-block borrowing cap multiplier. ```solidity function setMaxDebtPerBlockMultiplier(uint8 multiplier) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `multiplier` | `uint8` | Multiplier applied to maxDebt for per-block limit | **Access:** Configurator role only. --- #### forbidBorrowing Emergency action that immediately halts all new borrowing by setting the per-block multiplier to 0. ```solidity function forbidBorrowing() external; ``` **Access:** Pausable Admin (no DAO vote required). --- ### Adapter Management #### allowAdapter Registers an adapter for a target DeFi protocol. ```solidity function allowAdapter(address adapter) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `adapter` | `address` | Adapter contract address | **Validation:** Adapter must implement `creditManager()` returning this Credit Manager and `targetContract()` returning the DeFi protocol. Cannot target the Facade or Manager itself. **Access:** Configurator role only. --- #### forbidAdapter Removes a registered adapter. ```solidity function forbidAdapter(address adapter) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `adapter` | `address` | Adapter to remove | **Access:** Configurator role only. --- ### System Upgrades #### setPriceOracle Switches to a new price oracle implementation. ```solidity function setPriceOracle(address newPriceOracle) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `newPriceOracle` | `address` | New PriceOracle contract | **Access:** Configurator role only. New oracle must support all configured collateral tokens. --- #### setCreditFacade Migrates to a new Credit Facade, optionally copying existing parameters. ```solidity function setCreditFacade(address newCreditFacade, bool migrateParams) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `newCreditFacade` | `address` | New CreditFacade contract | | `migrateParams` | `bool` | Whether to copy debt limits and other parameters | **Access:** Configurator role only. --- #### upgradeCreditConfigurator Transfers the configurator role to a new contract. ```solidity function upgradeCreditConfigurator(address newCreditConfigurator) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `newCreditConfigurator` | `address` | New CreditConfigurator contract | **Access:** Configurator role only. --- ## View Methods ### creditManager Returns the Credit Manager this configurator manages. ```solidity function creditManager() external view returns (address); ``` --- ### creditFacade Returns the current Credit Facade address. ```solidity function creditFacade() external view returns (address); ``` --- ### underlying Returns the underlying token address. ```solidity function underlying() external view returns (address); ``` --- ## Events ### AddCollateralToken ```solidity event AddCollateralToken(address indexed token, uint16 liquidationThreshold); ``` Emitted when a new collateral token is registered. ### SetLiquidationThreshold ```solidity event SetLiquidationThreshold(address indexed token, uint16 liquidationThreshold); ``` Emitted when a token's LT is changed immediately. ### RampLiquidationThreshold ```solidity event RampLiquidationThreshold( address indexed token, uint16 ltFinal, uint40 rampStart, uint24 rampDuration ); ``` Emitted when a gradual LT change is initiated. ### ForbidToken ```solidity event ForbidToken(address indexed token); ``` Emitted when a token is marked as forbidden. ### AllowToken ```solidity event AllowToken(address indexed token); ``` Emitted when a forbidden token is restored. ### SetFees ```solidity event SetFees( uint16 feeLiquidation, uint16 liquidationPremium, uint16 feeLiquidationExpired, uint16 liquidationPremiumExpired ); ``` Emitted when liquidation fees are updated. ### SetDebtLimits ```solidity event SetDebtLimits(uint128 minDebt, uint128 maxDebt); ``` Emitted when debt bounds are changed. ### AllowAdapter ```solidity event AllowAdapter(address indexed adapter, address indexed targetContract); ``` Emitted when an adapter is registered. ### ForbidAdapter ```solidity event ForbidAdapter(address indexed adapter); ``` Emitted when an adapter is removed. ### SetCreditFacade ```solidity event SetCreditFacade(address indexed creditFacade); ``` Emitted when the Credit Facade is upgraded. ### SetPriceOracle ```solidity event SetPriceOracle(address indexed priceOracle); ``` Emitted when the price oracle is changed. ### UpgradeCreditConfigurator ```solidity event UpgradeCreditConfigurator(address indexed creditConfigurator); ``` Emitted when the configurator itself is upgraded. --- ## Access Control | Role | Capabilities | | --- | --- | | **Configurator** | All structural changes: tokens, fees, adapters, debt limits, upgrades | | **Pausable Admin** | Emergency actions: `forbidToken`, `forbidBorrowing` (no DAO vote required) | The Credit Manager and Facade verify all configuration calls originate from the registered Configurator via the `creditConfiguratorOnly` modifier. --- ## Related Pages - [Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm) -- Core accounting engine - [Credit Facade](https://docs.gearbox.finance/developers/gm-ref-cf) -- User-facing interface - [Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool) -- Liquidity pool - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Use Cases Source: https://docs.gearbox.finance/developers/use-cases-2 File: content/developers/use-cases-2.mdx Practical guides for common Solidity integration patterns with Gearbox. ## Available Guides | Use Case | Description | Guide | | -------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Adapter Development | Build adapters to integrate new DeFi protocols with Gearbox | [Adapter Development](https://docs.gearbox.finance/developers/adapter-development) | | Protocol Integration | Build protocols that compose with Gearbox Credit Accounts | [Protocol Integration](https://docs.gearbox.finance/developers/protocol-integration) | | Core Extension | Extend Gearbox core contracts (advanced) | [Core Extension](https://docs.gearbox.finance/developers/core-extension) | | Liquidation Bots | Build on-chain liquidation contracts with keeper automation | [Liquidation Bots](https://docs.gearbox.finance/developers/liquidation-bots) | ## Choosing Your Path ### Adapter Development Choose this path if you want to: * Add a new DeFi protocol (DEX, lending, yield) to Gearbox * Enable Credit Accounts to interact with your protocol * Become part of the Gearbox ecosystem Adapters are the bridge between Credit Accounts and external protocols. They enforce security constraints while translating calls to protocol-specific interfaces. ### Protocol Integration Choose this path if you want to: * Build a smart contract that uses Gearbox Credit Accounts * Create automated strategies on top of Gearbox * Compose Gearbox with your own protocol logic Protocol integrations call Gearbox from the outside, building multicalls and executing operations programmatically. ### Core Extension Choose this path if you want to: * Extend Gearbox core functionality (CreditManager, Pool, etc.) * Build custom contract logic that inherits from core contracts * Implement advanced customizations requiring deep protocol knowledge This is an advanced path requiring thorough understanding of Gearbox internals, security considerations, and upgrade patterns. ## Prerequisites All paths require: * Solidity 0.8.x experience * Understanding of the [Multicall System](https://docs.gearbox.finance/developers/multicall-system) * Familiarity with [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) ## Related * [Multicalls](https://docs.gearbox.finance/developers/multicalls) - Core encoding patterns * [Multicall Operations](https://docs.gearbox.finance/developers/multicalls) - Individual operation guides * [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) - Contract discovery and core interfaces ## Adapter Development Source: https://docs.gearbox.finance/developers/adapter-development File: content/developers/adapter-development.mdx Build adapters to integrate new DeFi protocols with Gearbox Credit Accounts. ## When to Build an Adapter Build an adapter when you want to enable Credit Accounts to interact with a new DeFi protocol. Consider these options: | Approach | When to Use | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | | **Build an Adapter** | The protocol is mature, has significant TVL, and you want it available across all Gearbox Credit Managers | | **Protocol Integration** | You're building a protocol that wants to accept Credit Accounts as users (e.g., a DEX accepting leveraged trades) | | **Direct Contract** | Your use case doesn't need leverage or margin trading features | Adapters make sense for: * DEX protocols (Uniswap, Curve, Balancer) * Yield vaults (Yearn, ERC-4626 vaults) * Liquid staking protocols (Lido, Rocket Pool) * Lending protocols (Aave, Compound) Do not build adapters for: * Protocols with admin keys that can rug users * Protocols without audits or battle-testing * Highly experimental or unproven contracts ## Architecture Overview Adapters sit between Credit Accounts and external protocols: ``` User -> CreditFacade -> CreditAccount -> Adapter -> Target Protocol | | | +-> Security enforcement | +-> Token state updates | +-> Approval management | +-> Receives output tokens ``` The adapter's job is to: 1. Accept calls from the CreditFacade (not users directly) 2. Translate the call to the target protocol's interface 3. Override recipient addresses to always be the Credit Account 4. Manage token approvals safely 5. Update token states (enable outputs, optionally disable inputs) For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Building Your First Adapter ### Step 1: Inherit AbstractAdapter All adapters extend `AbstractAdapter`: ```solidity import {AbstractAdapter} from "@gearbox-protocol/core-v3/contracts/adapters/AbstractAdapter.sol"; contract MyVaultAdapter is AbstractAdapter { constructor( address _creditManager, address _targetContract ) AbstractAdapter(_creditManager, _targetContract) {} } ``` **Example:** ```solidity // Adapter for an ERC-4626 vault contract ERC4626Adapter is AbstractAdapter { constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) {} } ``` ### Step 2: Implement Core Functions Add wrapper functions that call the target protocol: ```solidity function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); // Get token addresses address asset = IERC4626(targetContract).asset(); address vault = targetContract; // Build the call bytes memory callData = abi.encodeCall( IERC4626.deposit, (assets, creditAccount) // Override recipient to creditAccount ); // Execute with safe approval pattern shares = abi.decode( _executeSwapSafeApprove(asset, vault, callData, false), (uint256) ); } ``` **Example:** ```solidity // Withdraw function - burn shares, receive assets function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); address vault = targetContract; address asset = IERC4626(vault).asset(); bytes memory callData = abi.encodeCall( IERC4626.redeem, (shares, creditAccount, creditAccount) ); assets = abi.decode( _executeSwapSafeApprove(vault, asset, callData, false), (uint256) ); } ``` ### Step 3: Implement Diff Functions Diff functions calculate the input amount from the current balance: ```solidity // Standard: requires exact amount function deposit(uint256 assets) external creditFacadeOnly returns (uint256); // Diff: calculates amount as (balance - leftoverAmount) function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256); ``` **Example:** ```solidity function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); // Calculate actual deposit amount uint256 balance = IERC20(asset).balanceOf(creditAccount); uint256 amount = balance - leftoverAmount; // Call the standard deposit function return deposit(amount); } ``` **Why diff functions exist:** When chaining operations, you often don't know exact amounts: * After a swap, you don't know exact output until execution * After partial withdrawals, remaining balance is variable * Diff functions say "use everything except X" instead of "use exactly Y" ### Step 4: Add Security Modifiers Every external function must use `creditFacadeOnly`: ```solidity // WRONG - allows anyone to call function deposit(uint256 amount) external returns (uint256) { ... } // CORRECT - only CreditFacade can call function deposit(uint256 amount) external creditFacadeOnly returns (uint256) { ... } ``` **Example:** ```solidity // All user-facing functions need the modifier function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { ... } function depositDiff(uint256 leftover) external creditFacadeOnly returns (uint256 shares) { ... } function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { ... } function redeemDiff(uint256 leftover) external creditFacadeOnly returns (uint256 assets) { ... } ``` ## Complete Example: ERC-4626 Vault Adapter Here's a complete adapter for an ERC-4626 vault with all essential patterns: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {AbstractAdapter} from "@gearbox-protocol/core-v3/contracts/adapters/AbstractAdapter.sol"; import {AdapterType} from "@gearbox-protocol/core-v3/contracts/interfaces/IAdapter.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ERC4626Adapter is AbstractAdapter { AdapterType public constant override _gearboxAdapterType = AdapterType.VAULT; constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) {} // Deposit assets, receive shares function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); bytes memory callData = abi.encodeCall( IERC4626.deposit, (assets, creditAccount) ); shares = abi.decode( _executeSwapSafeApprove(asset, targetContract, callData, false), (uint256) ); } // Deposit all assets except leftoverAmount function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); uint256 balance = IERC20(asset).balanceOf(creditAccount); return deposit(balance - leftoverAmount); } // Redeem shares, receive assets function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); bytes memory callData = abi.encodeCall( IERC4626.redeem, (shares, creditAccount, creditAccount) ); assets = abi.decode( _executeSwapSafeApprove(targetContract, asset, callData, false), (uint256) ); } // Redeem all shares except leftoverAmount function redeemDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); uint256 balance = IERC20(targetContract).balanceOf(creditAccount); return redeem(balance - leftoverAmount); } } ``` ## Security Patterns Deep Dive ### Why creditFacadeOnly Matters Without this modifier, an attacker could call adapter functions directly: **Attack scenario without creditFacadeOnly:** 1. Attacker calls `adapter.deposit(1000)` directly 2. Adapter tries to pull tokens from "current" Credit Account 3. Without CreditFacade context, `_creditAccount()` returns address(0) or wrong account 4. Tokens could be pulled from wrong account or transaction reverts unpredictably **With creditFacadeOnly:** 1. Only CreditFacade can call the adapter 2. CreditFacade sets the active Credit Account before calling 3. Adapter correctly identifies which account to operate on 4. No unauthorized access possible ```solidity // The modifier checks that msg.sender is the CreditFacade modifier creditFacadeOnly() { if (msg.sender != creditFacade) revert CallerNotCreditFacadeException(); _; } ``` ### Recipient Override Pattern Always hardcode the recipient as the Credit Account. Never accept user-specified recipients. **Attack scenario without recipient override:** ```solidity // DANGEROUS - user controls recipient function withdraw(uint256 shares, address recipient) external creditFacadeOnly { IVault(targetContract).redeem(shares, recipient, _creditAccount()); } // Attacker multicall: // 1. Deposit 100 ETH to vault // 2. Call withdraw(shares, attackerAddress) // 3. Funds sent to attacker instead of Credit Account // 4. Credit Account has no collateral, but debt remains ``` **Safe pattern:** ```solidity // CORRECT - recipient is always the Credit Account function withdraw(uint256 shares) external creditFacadeOnly { address creditAccount = _creditAccount(); IVault(targetContract).redeem(shares, creditAccount, creditAccount); } ``` ### Safe Approval Pattern Approvals are reset to 1 (not 0) after each operation. This prevents: * Approval racing attacks * Gas waste from 0 -> N transitions * Front-running of approval transactions ```solidity // _executeSwapSafeApprove does this internally: // 1. Approve tokenIn to target contract // 2. Execute the call // 3. Set approval to 1 (not 0) // 4. Enable tokenOut // 5. Optionally disable tokenIn if balance is 1 wei _executeSwapSafeApprove( tokenIn, // Token being spent tokenOut, // Token being received callData, // The encoded call false // Don't disable tokenIn if non-zero balance remains ); ``` **Why reset to 1 instead of 0:** * ERC-20 standard: 0 -> N costs more gas than 1 -> N * Prevents approval race conditions * Future operations don't need expensive zero-to-nonzero transition ### Token State Management Adapters automatically update which tokens are "enabled" on the Credit Account: ```solidity // After a swap USDC -> WETH: // - WETH mask is enabled (counts toward collateral) // - USDC mask disabled if balance = 1 wei and disableTokenIn = true // If disableTokenIn = false: // - USDC remains enabled even with low balance // - Useful for partial swaps or when you'll receive more USDC later ``` ## Testing with Foundry Test adapters using Foundry fork tests: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {Test} from "forge-std/Test.sol"; import {ERC4626Adapter} from "../adapters/ERC4626Adapter.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; contract ERC4626AdapterTest is Test { ERC4626Adapter adapter; address creditManager = 0x...; // Existing CM on mainnet address vault = 0x...; // Target vault function setUp() public { // Fork mainnet vm.createSelectFork(vm.envString("ETH_RPC_URL")); // Deploy adapter adapter = new ERC4626Adapter(creditManager, vault); } function test_deposit() public { // Setup: get a credit account with assets address creditAccount = _openCreditAccount(); // Execute deposit via CreditFacade uint256 assets = 100e18; uint256 shares = adapter.deposit(assets); // Assertions assertGt(shares, 0, "Should receive shares"); assertEq(IERC20(vault).balanceOf(creditAccount), shares); } function test_depositDiff() public { address creditAccount = _openCreditAccount(); uint256 initialBalance = 100e18; uint256 leftover = 1; uint256 shares = adapter.depositDiff(leftover); assertEq(shares, adapter.previewDeposit(initialBalance - leftover)); } function test_cannotCallDirectly() public { vm.expectRevert(); // CallerNotCreditFacadeException adapter.deposit(100e18); } } ``` **Key test patterns:** 1. **Fork testing** - Test against real protocols on mainnet/testnet 2. **Access control** - Verify creditFacadeOnly works 3. **Balance checks** - Assert correct token transfers 4. **Diff functions** - Verify correct amount calculation 5. **Edge cases** - Test with zero amounts, max values, etc. ## Deployment and Whitelisting After writing and testing your adapter: 1. **Deploy the adapter** - Deploy to mainnet with CreditManager and target addresses 2. **Submit governance proposal** - Request adapter whitelisting via Gearbox governance 3. **Governance vote** - DAO votes on adding adapter to Credit Manager(s) 4. **Adapter registration** - If approved, adapter is added to allowedAdapters mapping **Governance process:** * Submit proposal on Gearbox governance forum * Include audit report (required for new adapters) * Specify which Credit Manager(s) should whitelist it * DAO votes on proposal * If passed, adapter becomes available for Credit Accounts **Note:** Each Credit Manager has its own adapter whitelist. An adapter approved for one Credit Manager isn't automatically available on others. For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Best Practices 1. **Keep it simple** - Adapters should be thin wrappers, not business logic 2. **No state** - Adapters should be stateless (except immutables) 3. **Comprehensive diff** - Implement diff functions for all variable-amount operations 4. **Always override recipients** - Never let users specify where funds go 5. **Test thoroughly** - Test both success and failure paths 6. **Document clearly** - Write NatSpec comments explaining each function 7. **Declare adapter type** - Set `_gearboxAdapterType` for proper categorization ## Key Inherited Functions AbstractAdapter provides these helper functions: | Function | Description | | ------------------------------ | --------------------------------------- | | `_creditAccount()` | Returns current Credit Account address | | `_creditManager()` | Returns Credit Manager address | | `_creditFacade()` | Returns Credit Facade address | | `_getMaskOrRevert(token)` | Gets token mask, reverts if not allowed | | `_execute(callData)` | Executes call on target contract | | `_executeSwapSafeApprove(...)` | Executes call with approval management | | `targetContract` | Immutable address of wrapped protocol | ## Related * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Using adapters in multicalls * [Multicall System](https://docs.gearbox.finance/developers/multicall-system) - Architectural overview ## Pool (PoolV3) Source: https://docs.gearbox.finance/developers/gm-ref-pool File: content/developers/gm-ref-pool.mdx **PoolV3** is the central vault for a specific underlying asset (e.g., USDC, WETH). It implements the ERC-4626 tokenized vault standard, allowing users to deposit assets and receive Diesel Tokens (LP shares). Only whitelisted Credit Managers can borrow from the pool. --- ## Write Methods ### ERC-4626 Standard #### deposit Deposits underlying assets and receives diesel tokens (shares). ```solidity function deposit(uint256 assets, address receiver) external returns (uint256 shares); ``` | Parameter | Type | Description | | --- | --- | --- | | `assets` | `uint256` | Amount of underlying tokens to deposit | | `receiver` | `address` | Address to receive the minted shares | **Returns:** Number of shares minted. --- #### mint Mints an exact number of shares, depositing the required underlying assets. ```solidity function mint(uint256 shares, address receiver) external returns (uint256 assets); ``` | Parameter | Type | Description | | --- | --- | --- | | `shares` | `uint256` | Exact number of shares to mint | | `receiver` | `address` | Address to receive the minted shares | **Returns:** Amount of underlying assets deposited. --- #### withdraw Withdraws an exact amount of underlying assets, burning the required shares. ```solidity function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); ``` | Parameter | Type | Description | | --- | --- | --- | | `assets` | `uint256` | Exact amount of underlying to withdraw | | `receiver` | `address` | Address to receive the assets | | `owner` | `address` | Address whose shares are burned | **Returns:** Number of shares burned. Reverts if pool is paused or insufficient liquidity. --- #### redeem Burns an exact number of shares and receives underlying assets. ```solidity function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); ``` | Parameter | Type | Description | | --- | --- | --- | | `shares` | `uint256` | Exact number of shares to burn | | `receiver` | `address` | Address to receive the assets | | `owner` | `address` | Address whose shares are burned | **Returns:** Amount of underlying assets received. --- ### Gearbox Extensions #### depositWithReferral Deposits underlying assets with on-chain referral tracking. ```solidity function depositWithReferral(uint256 assets, address receiver, uint256 referralCode) external returns (uint256 shares); ``` | Parameter | Type | Description | | --- | --- | --- | | `assets` | `uint256` | Amount of underlying tokens to deposit | | `receiver` | `address` | Address to receive the minted shares | | `referralCode` | `uint256` | Referral code for tracking | **Returns:** Number of shares minted. --- #### lendCreditAccount Lends underlying assets to a Credit Account. Called by the Credit Manager during debt increase. ```solidity function lendCreditAccount(uint256 borrowedAmount, address creditAccount) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `borrowedAmount` | `uint256` | Amount to lend | | `creditAccount` | `address` | Receiving Credit Account | **Access:** Whitelisted Credit Manager only. --- #### repayCreditAccount Repays borrowed assets from a Credit Account. Called by the Credit Manager during debt decrease, closure, or liquidation. ```solidity function repayCreditAccount(uint256 repaidAmount, uint256 profit, uint256 loss) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `repaidAmount` | `uint256` | Amount being repaid | | `profit` | `uint256` | Profit amount (added to treasury) | | `loss` | `uint256` | Loss amount (may burn treasury shares or socialize) | **Access:** Whitelisted Credit Manager only. --- ## View Methods ### ERC-4626 Standard #### totalAssets Returns the total value of underlying assets held by the pool. ```solidity function totalAssets() external view returns (uint256); ``` --- #### convertToShares / convertToAssets Preview conversion between assets and shares. ```solidity function convertToShares(uint256 assets) external view returns (uint256 shares); function convertToAssets(uint256 shares) external view returns (uint256 assets); ``` --- #### previewDeposit / previewMint / previewWithdraw / previewRedeem Preview the result of deposit, mint, withdraw, or redeem operations. ```solidity function previewDeposit(uint256 assets) external view returns (uint256 shares); function previewMint(uint256 shares) external view returns (uint256 assets); function previewWithdraw(uint256 assets) external view returns (uint256 shares); function previewRedeem(uint256 shares) external view returns (uint256 assets); ``` --- #### maxDeposit / maxMint / maxWithdraw / maxRedeem Returns the maximum amount that can be deposited, minted, withdrawn, or redeemed. ```solidity function maxDeposit(address receiver) external view returns (uint256); function maxMint(address receiver) external view returns (uint256); function maxWithdraw(address owner) external view returns (uint256); function maxRedeem(address owner) external view returns (uint256); ``` --- #### asset Returns the underlying token address. ```solidity function asset() external view returns (address); ``` --- ### Gearbox-Specific Views #### availableLiquidity Returns the amount of underlying tokens available for borrowing. ```solidity function availableLiquidity() external view returns (uint256); ``` --- #### dieselRate Returns the current share price in RAY (27 decimals). Starts at `10^27` and increases as interest accrues. ```solidity function dieselRate() external view returns (uint256); ``` --- #### supplyRate Returns the annualized supply rate for lenders in RAY. ```solidity function supplyRate() external view returns (uint256); ``` --- #### baseInterestRate Returns the annualized borrow rate for Credit Accounts in RAY. ```solidity function baseInterestRate() external view returns (uint256); ``` --- #### baseInterestIndex Returns the cumulative interest index used for debt tracking. ```solidity function baseInterestIndex() external view returns (uint256); ``` --- #### poolQuotaKeeper Returns the address of the associated PoolQuotaKeeper. ```solidity function poolQuotaKeeper() external view returns (address); ``` --- #### interestRateModel Returns the address of the Interest Rate Model contract. ```solidity function interestRateModel() external view returns (address); ``` --- ## Events ### Deposit ```solidity event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); ``` Emitted on deposit or mint (ERC-4626 standard). ### Withdraw ```solidity event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); ``` Emitted on withdraw or redeem (ERC-4626 standard). ### Borrow ```solidity event Borrow(address indexed creditManager, address indexed creditAccount, uint256 amount); ``` Emitted when a Credit Manager borrows from the pool. ### Repay ```solidity event Repay(address indexed creditManager, uint256 borrowedAmount, uint256 profit, uint256 loss); ``` Emitted when a Credit Manager repays to the pool. --- ## Related Pages - [Quota Keeper](https://docs.gearbox.finance/developers/gm-ref-qk) -- Manages per-token quota limits and rates - [Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm) -- Borrows from the pool on behalf of Credit Accounts - [Compressors](https://docs.gearbox.finance/developers/gm-ref-compressors) -- Aggregated pool data reading - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Protocol Integration Source: https://docs.gearbox.finance/developers/protocol-integration File: content/developers/protocol-integration.mdx Build smart contracts that compose with Gearbox Credit Accounts. ## Overview Protocol integration means building contracts that: * Call Gearbox Credit Accounts from external contracts * Create automated strategies using multicalls * Compose Gearbox with your own protocol logic Unlike adapters (which are called BY Credit Accounts), protocol integrations CALL Credit Accounts from outside. ## Architecture ``` Your Contract -> CreditFacade -> Credit Account -> Adapters -> DeFi Protocols | +-> Multicall execution +-> Collateral checks +-> Access control ``` ## Basic Integration Pattern ### 1. Find the Right Market ```solidity import {IAddressProviderV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol"; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; contract MyStrategy { IAddressProviderV3 public constant ADDRESS_PROVIDER = IAddressProviderV3(0x9ea7b04Da02a5373317D745c1571c84aaD03321D); ICreditFacadeV3 public creditFacade; address public creditManager; function initialize(address _creditManager) external { creditManager = _creditManager; // Get CreditFacade from CreditManager creditFacade = ICreditFacadeV3( ICreditManagerV3(_creditManager).creditFacade() ); } } ``` ### 2. Build and Execute Multicalls ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; function depositToStrategy( address creditAccount, address token, uint256 amount ) external { MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (token, amount) ) }); // Caller must approve creditManager first creditFacade.multicall(creditAccount, calls); } ``` ## Common Integration Patterns ### Automated Strategy Contract A contract that executes predefined strategies: ```solidity // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.17; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {IUniswapV3Adapter} from "@gearbox-protocol/integrations-v3/contracts/interfaces/uniswap/IUniswapV3Adapter.sol"; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract LeveragedYieldStrategy { ICreditFacadeV3 public immutable creditFacade; ICreditManagerV3 public immutable creditManager; address public immutable underlying; address public immutable targetAsset; address public immutable uniswapAdapter; address public immutable yieldAdapter; constructor( address _creditManager, address _targetAsset, address _yieldVault ) { creditManager = ICreditManagerV3(_creditManager); creditFacade = ICreditFacadeV3(creditManager.creditFacade()); underlying = creditManager.underlying(); targetAsset = _targetAsset; // Find adapters uniswapAdapter = creditManager.contractToAdapter( 0xE592427A0AEce92De3Edee1F18E0157C05861564 // Uniswap V3 Router ); yieldAdapter = creditManager.contractToAdapter(_yieldVault); require(uniswapAdapter != address(0), "Uniswap adapter not found"); require(yieldAdapter != address(0), "Yield adapter not found"); } /// @notice Open leveraged yield position /// @param collateralAmount Initial collateral /// @param leverage Leverage multiplier (e.g., 4 for 4x) /// @param minYieldTokens Minimum yield tokens to receive function openPosition( uint256 collateralAmount, uint256 leverage, uint256 minYieldTokens ) external returns (address creditAccount) { require(leverage >= 1 && leverage <= 10, "Invalid leverage"); uint256 borrowAmount = collateralAmount * (leverage - 1); MultiCall[] memory calls = new MultiCall[](4); // 1. Add collateral calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (underlying, collateralAmount) ) }); // 2. Borrow calls[1] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (borrowAmount) ) }); // 3. Swap to target asset uint256 totalAmount = collateralAmount + borrowAmount; ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({ tokenIn: underlying, tokenOut: targetAsset, fee: 500, recipient: address(0), deadline: block.timestamp + 3600, amountIn: totalAmount, amountOutMinimum: 0, // Using yield token check instead sqrtPriceLimitX96: 0 }); calls[2] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); // 4. Deposit to yield vault using diff pattern calls[3] = MultiCall({ target: yieldAdapter, callData: abi.encodeCall( IYearnV2Adapter.depositDiff, (1) // Leave 1 wei ) }); // Approve and open IERC20(underlying).transferFrom(msg.sender, address(this), collateralAmount); IERC20(underlying).approve(address(creditManager), collateralAmount); creditAccount = creditFacade.openCreditAccount(msg.sender, calls, 0); } } ``` ### Strategy with Price Updates When using Pyth or Redstone oracles, your contract must accept and forward price data: ```solidity /// @notice Execute strategy with price updates /// @param creditAccount Target Credit Account /// @param priceUpdates Array of (token, reserve, data) for on-demand oracles /// @param strategyParams Your strategy-specific parameters function executeWithPriceUpdates( address creditAccount, bytes[] calldata priceUpdates, StrategyParams calldata strategyParams ) external { uint256 numUpdates = priceUpdates.length; // Build calls with price updates first MultiCall[] memory calls = new MultiCall[](numUpdates + strategyParams.numCalls); // Add all price updates at the beginning for (uint256 i = 0; i < numUpdates; i++) { (address token, bool reserve, bytes memory data) = abi.decode(priceUpdates[i], (address, bool, bytes)); calls[i] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token, reserve, data) ) }); } // Add strategy calls after price updates _buildStrategyCalls(calls, numUpdates, strategyParams); creditFacade.multicall(creditAccount, calls); } ``` ### Keeper/Bot Integration For automated position management: ```solidity contract PositionKeeper { ICreditFacadeV3 public immutable creditFacade; address public keeper; mapping(address => bool) public managedAccounts; modifier onlyKeeper() { require(msg.sender == keeper, "Not keeper"); _; } /// @notice Rebalance position when health factor is low function rebalance( address creditAccount, address tokenToSell, uint256 sellAmount, uint256 minRepay ) external onlyKeeper { require(managedAccounts[creditAccount], "Not managed"); MultiCall[] memory calls = new MultiCall[](2); // 1. Swap collateral for underlying calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); // 2. Repay debt using diff pattern calls[1] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (minRepay) ) }); // Execute as bot (requires bot permissions on account) creditFacade.botMulticall(creditAccount, calls); } } ``` ## Access Control Considerations ### Account Ownership Only the account owner (or approved bots) can execute multicalls: ```solidity // This will revert if msg.sender is not account owner creditFacade.multicall(creditAccount, calls); // For keeper/bot access, the owner must grant permissions first // This is done via setBotPermissions in a multicall ``` ### Bot Permissions To allow external contracts to manage accounts: ```solidity // Owner grants bot permissions MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.setBotPermissions, ( botAddress, uint192(1 << 0 | 1 << 1) // Permission flags for allowed operations ) ) }); creditFacade.multicall(creditAccount, calls); ``` Then the bot can use `botMulticall`: ```solidity // Bot executes with limited permissions creditFacade.botMulticall(creditAccount, calls); ``` ## Error Handling ### Reading Account State Before Operations ```solidity function getAccountHealth(address creditAccount) external view returns (uint256) { ( uint256 debt, uint256 cumulativeIndexLastUpdate, uint128 cumulativeQuotaInterest, uint128 quotaFees, uint256 enabledTokensMask, uint16 flags, uint64 lastDebtUpdate, address borrower ) = creditManager.creditAccountInfo(creditAccount); // Calculate health factor using collateral values // ... } ``` ### Handling Reverts ```solidity function safeExecute( address creditAccount, MultiCall[] memory calls ) external returns (bool success) { try creditFacade.multicall(creditAccount, calls) { success = true; } catch Error(string memory reason) { emit ExecutionFailed(creditAccount, reason); success = false; } } ``` ## Gas Optimization ### Use Collateral Hints For accounts with many tokens, provide hints to reduce oracle calls: ```solidity // Get token masks for primary collateral uint256 usdcMask = creditManager.getTokenMaskOrRevert(usdc); uint256 wethMask = creditManager.getTokenMaskOrRevert(weth); uint256[] memory hints = new uint256[](2); hints[0] = usdcMask; hints[1] = wethMask; // Add setFullCheckParams as first non-price-update call calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 10000) // 1.0 min health factor ) }); ``` ### Batch Operations Combine related operations in single multicalls rather than multiple transactions: ```solidity // GOOD - single multicall MultiCall[] memory calls = new MultiCall[](4); calls[0] = addCollateralCall; calls[1] = borrowCall; calls[2] = swapCall; calls[3] = depositCall; creditFacade.multicall(account, calls); // BAD - multiple transactions (more gas, atomicity issues) creditFacade.multicall(account, [addCollateralCall]); creditFacade.multicall(account, [borrowCall]); creditFacade.multicall(account, [swapCall]); ``` ## Testing ### Foundry Setup ```solidity import {Test} from "forge-std/Test.sol"; contract StrategyTest is Test { LeveragedYieldStrategy strategy; address user = address(0x1); function setUp() public { // Fork mainnet vm.createSelectFork("mainnet"); // Deploy strategy strategy = new LeveragedYieldStrategy( CREDIT_MANAGER, TARGET_ASSET, YIELD_VAULT ); } function test_openPosition() public { // Setup deal(USDC, user, 10_000e6); vm.startPrank(user); IERC20(USDC).approve(address(strategy), 10_000e6); // Execute address account = strategy.openPosition(10_000e6, 4, 0); // Verify assertTrue(account != address(0)); assertGt(IERC20(YIELD_TOKEN).balanceOf(account), 0); vm.stopPrank(); } } ``` ## Security Considerations 1. **Reentrancy** - Multicalls are atomic, but be careful with callbacks 2. **Slippage** - Always use `storeExpectedBalances`/`compareBalances` for swaps 3. **Access control** - Verify account ownership before operations 4. **Oracle manipulation** - Use safe pricing for withdrawals 5. **Flash loan attacks** - Consider same-block restrictions on debt changes ## Related * [Multicalls](https://docs.gearbox.finance/developers/multicalls) - Core encoding patterns * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Using adapters * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protecting operations * [Adapter Development](https://docs.gearbox.finance/developers/adapter-development) - Building adapters ## Quota Keeper Source: https://docs.gearbox.finance/developers/gm-ref-qk File: content/developers/gm-ref-qk.mdx The **PoolQuotaKeeperV3** regulates how much pool capital can be exposed to specific collateral types and at what cost. It tracks per-token quota limits, interest rates, and cumulative indices. Unlike the base debt which compounds, quota interest is additive (linear). --- ## Write Methods ### updateQuota Updates the quota for a specific token on a Credit Account. Called by the Credit Manager during multicall execution. ```solidity function updateQuota( address creditAccount, address token, int96 quotaChange, uint96 minQuota ) external returns (uint256 tokensToEnable, uint256 tokensToDisable); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditAccount` | `address` | Credit Account to update | | `token` | `address` | Token whose quota is being changed | | `quotaChange` | `int96` | Amount to increase (positive) or decrease (negative) | | `minQuota` | `uint96` | Minimum quota to maintain (reverts if result is below this and non-zero) | **Returns:** Token masks to enable/disable based on quota state. **Access:** Credit Manager only. Reverts with `QuotaIsOutOfBoundsException` if `totalQuoted` would exceed the token's limit. --- ### setTokenLimit Sets the maximum aggregate quota for a specific token across all Credit Accounts. ```solidity function setTokenLimit(address token, uint96 limit) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Collateral token | | `limit` | `uint96` | Maximum total quota allowed | **Access:** Configurator only. Prevents over-exposure to any single asset. --- ### setTokenQuotaIncreaseFee Sets the fee charged when a quota is increased. ```solidity function setTokenQuotaIncreaseFee(address token, uint16 fee) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Collateral token | | `fee` | `uint16` | Fee in basis points | **Access:** Configurator only. --- ### addQuotaToken Registers a new token for quota tracking. ```solidity function addQuotaToken(address token) external; ``` | Parameter | Type | Description | | --- | --- | --- | | `token` | `address` | Token to register | **Access:** Configurator only. --- ### updateRates Syncs quota interest rates from the Gauge/Tumbler (rate keeper). Must be called for rate changes to take effect. ```solidity function updateRates() external; ``` **Access:** Rate keeper (Gauge or Tumbler) only. --- ## View Methods ### getTokenQuotaParams Returns the global quota parameters for a token. ```solidity function getTokenQuotaParams(address token) external view returns ( uint16 rate, uint192 cumulativeIndex, uint16 quotaIncreaseFee, uint96 totalQuoted, uint96 limit, bool isActive ); ``` | Return Value | Type | Description | | --- | --- | --- | | `rate` | `uint16` | Annual interest rate in basis points | | `cumulativeIndex` | `uint192` | Cumulative index for interest calculation | | `quotaIncreaseFee` | `uint16` | Fee charged on quota increases (bps) | | `totalQuoted` | `uint96` | Total quota across all Credit Accounts | | `limit` | `uint96` | Maximum allowed total quota | | `isActive` | `bool` | Whether the token is currently active | --- ### getQuotaAndOutstandingInterest Returns the quota and accrued interest for a specific Credit Account and token. ```solidity function getQuotaAndOutstandingInterest( address creditAccount, address token ) external view returns (uint96 quoted, uint128 outstandingInterest); ``` | Return Value | Type | Description | | --- | --- | --- | | `quoted` | `uint96` | Amount of quota held by the account | | `outstandingInterest` | `uint128` | Accrued interest not yet added to debt | --- ### quotedTokens Returns the list of all tokens registered for quota tracking. ```solidity function quotedTokens() external view returns (address[] memory); ``` --- ### pool Returns the associated pool address. ```solidity function pool() external view returns (address); ``` --- ### gauge Returns the rate keeper (Gauge or Tumbler) address. ```solidity function gauge() external view returns (address); ``` --- ## Events ### UpdateQuota ```solidity event UpdateQuota( address indexed creditAccount, address indexed token, int96 quotaChange ); ``` Emitted when a Credit Account's quota is changed. ### SetTokenLimit ```solidity event SetTokenLimit(address indexed token, uint96 limit); ``` Emitted when a token's quota limit is updated. ### SetTokenQuotaIncreaseFee ```solidity event SetTokenQuotaIncreaseFee(address indexed token, uint16 fee); ``` Emitted when a quota increase fee is set. ### AddQuotaToken ```solidity event AddQuotaToken(address indexed token); ``` Emitted when a new token is registered for quota tracking. ### UpdateRates ```solidity event UpdateRates(); ``` Emitted when quota rates are synced from the rate keeper. --- ## Rate Keeper Models The QuotaKeeper relies on an external rate keeper to provide interest rates: | Model | Description | | --- | --- | | **GaugeV3** | Decentralized voting model where GEAR stakers vote to set rates between `minRate` and `maxRate` | | **TumblerV3** | Curator-controlled model with direct rate setting via `setRate(token, rate)` and epoch-based updates | --- ## Related Pages - [Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool) -- The vault that QuotaKeeper is associated with - [Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm) -- Calls `updateQuota` during multicall execution - [Credit Configurator](https://docs.gearbox.finance/developers/gm-ref-cc) -- Manages quota parameters through the configurator role - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Compressors Source: https://docs.gearbox.finance/developers/gm-ref-compressors File: content/developers/gm-ref-compressors.mdx Compressor contracts are read-only data aggregation contracts. Instead of dozens of individual calls, a single compressor call returns complete protocol state. The SDK uses compressors internally. ## Discovering Compressor Addresses Use `AddressProvider` to find compressor addresses: ```solidity address compressor = addressProvider.getLatestAddressByContractType( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` ```typescript const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` --- ## MarketCompressor Aggregates complete market state including pools, credit managers, and price oracles. ### View Methods #### getMarkets Returns market data for all markets matching the filter. ```solidity function getMarkets(MarketFilter memory filter) external view returns (MarketData[] memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `filter` | `MarketFilter` | Filter criteria (see below) | **Returns:** Array of `MarketData` structs. --- #### getMarketData Returns complete data for a single market identified by its pool address. ```solidity function getMarketData(address pool) external view returns (MarketData memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `pool` | `address` | Pool address identifying the market | --- #### getPoolState Returns the pool state for a given pool. ```solidity function getPoolState(address pool) external view returns (PoolState memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `pool` | `address` | Pool address | --- ### Data Structures #### MarketFilter ```solidity struct MarketFilter { address[] configurators; // Filter by Risk Curator addresses address[] pools; // Filter by specific pool addresses address underlying; // Filter by underlying token (e.g., USDC) } ``` Pass empty arrays and `address(0)` for no filtering. #### MarketData ```solidity struct MarketData { PoolState pool; QuotaKeeperState quotaKeeper; CreditSuiteData[] creditManagers; PriceOracleState priceOracle; TokenData[] tokens; } ``` #### PoolState (key fields) | Field | Type | Description | | --- | --- | --- | | `baseParams.addr` | `address` | Pool contract address | | `availableLiquidity` | `uint256` | Borrowable liquidity | | `dieselRate` | `uint256` | Share price (RAY) | | `supplyRate` | `uint256` | Lender APY (RAY) | | `baseInterestRate` | `uint256` | Borrower APR (RAY) | | `totalAssets` | `uint256` | Total pool value | #### CreditSuiteData (key fields) | Field | Type | Description | | --- | --- | --- | | `creditManager` | `address` | Credit Manager address | | `creditFacade` | `address` | Credit Facade address | | `creditConfigurator` | `address` | Configurator address | | `debtLimits` | `DebtLimits` | Min/max debt per account | | `collateralTokens` | `CollateralToken[]` | Allowed tokens + LTs | --- ## CreditAccountCompressor Fetches credit account data with filtering and pagination. ### View Methods #### getCreditAccounts Returns credit accounts matching the filter with pagination support. ```solidity function getCreditAccounts( address creditManager, CreditAccountFilter memory filter, uint256 offset ) external view returns (CreditAccountData[] memory accounts, uint256 total); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditManager` | `address` | Credit Manager to query | | `filter` | `CreditAccountFilter` | Filter criteria (see below) | | `offset` | `uint256` | Pagination offset | **Returns:** Matching accounts and total count. --- #### countCreditAccounts Returns the count of credit accounts matching the filter. ```solidity function countCreditAccounts( address creditManager, CreditAccountFilter memory filter ) external view returns (uint256); ``` --- #### getCreditAccountData Returns complete data for a single credit account. ```solidity function getCreditAccountData( address creditManager, address creditAccount ) external view returns (CreditAccountData memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `creditManager` | `address` | Parent Credit Manager | | `creditAccount` | `address` | Account to query | --- ### Data Structures #### CreditAccountFilter ```solidity struct CreditAccountFilter { address owner; // Filter by owner (address(0) = any) uint256 minHealthFactor; // Minimum HF (0 = no min) uint256 maxHealthFactor; // Maximum HF (type(uint256).max = no max) bool includeZeroDebt; // Include accounts with no debt bool reverting; // Include reverting accounts } ``` #### CreditAccountData (key fields) | Field | Type | Description | | --- | --- | --- | | `addr` | `address` | Credit Account address | | `owner` | `address` | Account owner | | `creditManager` | `address` | Parent Credit Manager | | `debt` | `uint256` | Total debt (principal + interest) | | `enabledTokensMask` | `uint256` | Bitmask of enabled tokens | | `healthFactor` | `uint256` | Current HF (10000 = 1.0) | | `tokens` | `TokenInfo[]` | Token balances and values | | `isLiquidatable` | `bool` | Whether account can be liquidated | --- ## PriceFeedCompressor Aggregates price feed state for oracle monitoring and updates. ### View Methods #### getUpdatablePriceFeeds Returns all price feeds that may need updating for a given price oracle. ```solidity function getUpdatablePriceFeeds(address priceOracle) external view returns (PriceFeedData[] memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `priceOracle` | `address` | PriceOracle contract to query | **Returns:** Array of price feed data including staleness information. --- #### loadPriceFeedTree Returns the full price feed dependency tree for a specific token. ```solidity function loadPriceFeedTree(address priceOracle, address token) external view returns (PriceFeedTreeNode memory); ``` | Parameter | Type | Description | | --- | --- | --- | | `priceOracle` | `address` | PriceOracle contract | | `token` | `address` | Token to query | --- ## When to Use Compressors vs SDK | Scenario | Approach | | --- | --- | | General market data | SDK `marketRegister` | | Credit account queries | SDK services | | Custom filtering logic | Direct compressor calls | | Liquidation bots | Direct compressor (gas-optimized) | | On-chain integration | Direct compressor (no SDK in contracts) | | Real-time monitoring | Direct compressor with specific filters | --- ## Related Pages - [Pool (PoolV3)](https://docs.gearbox.finance/developers/gm-ref-pool) -- Pool state returned by MarketCompressor - [Credit Manager](https://docs.gearbox.finance/developers/gm-ref-cm) -- Credit accounts queried by CreditAccountCompressor - [Quota Keeper](https://docs.gearbox.finance/developers/gm-ref-qk) -- Quota data included in MarketData - [Smart Contracts Overview](https://docs.gearbox.finance/developers/gm-contracts) -- Full contract architecture ## Core Extension Source: https://docs.gearbox.finance/developers/core-extension File: content/developers/core-extension.mdx Advanced patterns for extending Gearbox protocol contracts beyond standard adapter integration. > This is advanced material. For standard integrations, see [Adapter Development](https://docs.gearbox.finance/developers/adapter-development) or [Protocol Integration](https://docs.gearbox.finance/developers/protocol-integration). ## When to Extend vs Integrate Different levels of integration require different approaches: | Approach | Complexity | When to Use | | ------------------------ | ---------- | -------------------------------------------------------------------- | | **Adapter** | Low | Wrap existing DeFi protocols for Credit Account access | | **Protocol Integration** | Medium | Build contracts that compose with Credit Accounts externally | | **Core Extension** | High | Modify Credit Manager/Pool behavior, create protocol-specific spokes | Examples by approach: **Adapter:** Wrapping Uniswap, Curve, Yearn for Credit Account users **Protocol Integration:** Strategy vaults that open/manage Credit Accounts **Core Extension:** Custom Credit Manager for protocol-specific accounting, custom interest rate models, pool hooks for fee distribution Build a core extension when you need to: * Modify how debt/collateral calculations work * Change liquidity flow patterns between pools and Credit Managers * Implement protocol-specific Credit Manager variants ("spokes") * Create custom interest rate logic beyond linear models * Add lifecycle hooks to pool operations ## Understanding Money Flows Core extensions require deep understanding of how liquidity moves through the protocol. ### The Fundamental Flow ``` Lenders → Pool → Credit Manager → Credit Account → DeFi Protocols (ERC-4626) | | | +-> Holds collateral +-> Tracks debt ``` **Key invariants:** 1. Pool lends only to whitelisted Credit Managers 2. Credit Managers borrow on behalf of Credit Accounts 3. Credit Accounts hold all collateral and debt 4. All value flows are tracked via indexed interest and quota systems ### Detailed Liquidity Flow **When borrowing:** ```solidity // 1. User opens Credit Account via CreditFacade CreditFacade.openCreditAccount(owner, calls, referralCode) | v // 2. Facade instructs Manager to borrow CreditManager.openCreditAccount(debt, onBehalfOf) | v // 3. Manager requests liquidity from Pool Pool.lendCreditAccount(borrowedAmount, creditAccount) | v // 4. Pool transfers underlying directly to Credit Account IERC20(underlying).transfer(creditAccount, borrowedAmount) ``` **When repaying:** ```solidity // 1. User closes account via CreditFacade CreditFacade.closeCreditAccount(creditAccount, calls) | v // 2. Manager calculates total debt totalDebt = principal + accruedInterest + quotaInterest + fees | v // 3. Underlying transfers from Credit Account to Pool IERC20(underlying).transferFrom(creditAccount, pool, totalDebt) | v // 4. Manager reports repayment to Pool Pool.repayCreditAccount(repaidAmount, profit, loss) ``` **Critical detail:** The Credit Manager never holds funds. It orchestrates transfers between Pool and Credit Account while maintaining accounting state. ## Pool Extension Patterns ### Custom Interest Rate Models The pool queries an external IRM contract for interest rates. You can implement custom logic by deploying a new IRM. **IInterestRateModel interface:** ```solidity interface ILinearInterestRateModelV3 { function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate); function getModelParameters() external view returns ( uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3 ); } ``` **Example: Time-weighted interest model** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {ILinearInterestRateModelV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ILinearInterestRateModelV3.sol"; contract TimeWeightedIRM is ILinearInterestRateModelV3 { uint16 public immutable U_1 = 7000; // 70% uint16 public immutable U_2 = 9000; // 90% uint16 public immutable R_base = 100; // 1% uint16 public immutable R_slope1 = 200; // 2% uint16 public immutable R_slope2 = 500; // 5% // Time-based slope adjustment uint16 public immutable peakHourMultiplier = 150; // 1.5x during peak hours function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate) { uint256 utilizationBps = (expectedLiquidity - availableLiquidity) * 10_000 / expectedLiquidity; // Apply time-based multiplier during peak hours (12-18 UTC) uint256 hour = (block.timestamp / 3600) % 24; uint16 multiplier = (hour >= 12 && hour < 18) ? peakHourMultiplier : 100; // Standard linear calculation with time adjustment uint256 baseRate = R_base; if (utilizationBps <= U_1) { borrowRate = baseRate + (R_slope1 * utilizationBps * multiplier) / 10_000 / 100; } else if (utilizationBps <= U_2) { borrowRate = baseRate + (R_slope2 * utilizationBps * multiplier) / 10_000 / 100; } else { borrowRate = baseRate + (R_slope3 * utilizationBps * multiplier) / 10_000 / 100; } // Convert to RAY (27 decimals) borrowRate = borrowRate * 10**27 / 10_000; } function getModelParameters() external view returns ( uint16, uint16, uint16, uint16, uint16, uint16 ) { // Return adjusted slope based on current time uint256 hour = (block.timestamp / 3600) % 24; uint16 adjustedSlope1 = (hour >= 12 && hour < 18) ? R_slope1 * peakHourMultiplier / 100 : R_slope1; return (U_1, U_2, R_base, adjustedSlope1, R_slope2, R_slope3); } } ``` **Deploying custom IRM:** Custom IRMs can be set via governance: ```solidity // Only CONFIGURATOR can update IRM IPoolV3(pool).setInterestRateModel(newIRMAddress); ``` ### Pool Withdrawal Hooks Pools in V3 support withdrawal fees. These are not "hooks" in the callback sense, but configurable parameters: ```solidity // Read current withdrawal fee uint16 withdrawFee = IPoolV3(pool).withdrawFee(); // basis points // Fee is applied during withdraw/redeem uint256 assets = pool.withdraw(amount, receiver, owner); // Actual received = amount - (amount * withdrawFee / 10000) ``` For custom fee distribution logic, you would typically: 1. Deploy a fee collector contract 2. Configure the pool's treasury address to your collector 3. Implement distribution logic in your collector ## Credit Manager Extension Patterns ### Spoke Development Concept A "spoke" is a specialized Credit Manager variant designed for protocol-specific use cases. Unlike standard Credit Managers, spokes extend core functionality for unique accounting or liquidity patterns. **When to build a spoke:** * Your protocol needs custom collateral valuation logic * You're integrating Credit Accounts as a core protocol primitive * You need specialized debt/liquidation mechanics * You want to modify how positions are opened/closed **Example use case:** A derivatives protocol where Credit Accounts are used as margin accounts with custom position tracking. ### Spoke Architecture Pattern ```solidity import {CreditManagerV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditManagerV3.sol"; contract DerivativesSpoke is CreditManagerV3 { // Custom state for protocol-specific tracking mapping(address => PositionData) public positions; struct PositionData { uint256 longExposure; uint256 shortExposure; int256 unrealizedPnL; } constructor( address _pool, address _addressProvider ) CreditManagerV3(_pool, _addressProvider) {} // Override collateral calculation to include unrealized PnL function calcDebtAndCollateral( address creditAccount, CollateralCalcTask task ) public view override returns ( CollateralDebtData memory cdd ) { // Call parent implementation cdd = super.calcDebtAndCollateral(creditAccount, task); // Adjust TWV based on unrealized PnL PositionData memory pos = positions[creditAccount]; if (pos.unrealizedPnL > 0) { // Add unrealized profit to collateral cdd.twvUSD += uint256(pos.unrealizedPnL); } else { // Subtract unrealized loss from collateral cdd.twvUSD -= uint256(-pos.unrealizedPnL); } } // Protocol-specific function to update position state function updatePosition( address creditAccount, uint256 longExposure, uint256 shortExposure, int256 pnl ) external { // Only allow calls from authorized adapters require( adapterToContract[msg.sender] != address(0), "Not authorized adapter" ); positions[creditAccount] = PositionData({ longExposure: longExposure, shortExposure: shortExposure, unrealizedPnL: pnl }); } } ``` ### Custom Collateral Valuation Extend collateral calculations for protocol-specific assets: ```solidity // Override token price resolution function priceOracle() public view override returns (IPriceOracleV3) { return IPriceOracleV3(customOracleAddress); } // Implement custom oracle with protocol-specific pricing contract CustomOracle is IPriceOracleV3 { function convertToUSD( uint256 amount, address token ) external view override returns (uint256) { if (token == protocolSpecificToken) { // Custom valuation logic return amount * getCustomPrice() / 10**tokenDecimals; } return defaultOracle.convertToUSD(amount, token); } } ``` ### Extending Liquidation Logic Custom liquidation premiums based on collateral type: ```solidity contract CustomLiquidationCM is CreditManagerV3 { mapping(address => uint16) public tokenLiquidationPremiums; function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls ) external override { // Calculate custom premium based on collateral composition uint256 enabledMask = enabledTokensMaskOf(creditAccount); uint16 premium = _calculatePremium(enabledMask); // Set premium temporarily uint16 oldPremium = liquidationPremium; liquidationPremium = premium; // Execute standard liquidation super.liquidateCreditAccount(creditAccount, to, calls); // Restore liquidationPremium = oldPremium; } function _calculatePremium(uint256 mask) internal view returns (uint16) { uint16 maxPremium = 0; for (uint256 i = 0; i < 256; i++) { if (mask & (1 << i) != 0) { address token = getTokenByMask(1 << i); if (tokenLiquidationPremiums[token] > maxPremium) { maxPremium = tokenLiquidationPremiums[token]; } } } return maxPremium; } } ``` ## Working with Configurators The CreditConfigurator provides governance interface for Credit Manager parameters. When building spokes, you typically extend the configurator as well. ### Standard Configurator Usage ```solidity import {ICreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditConfiguratorV3.sol"; ICreditConfiguratorV3 configurator = ICreditConfiguratorV3(configuratorAddress); // Add new collateral token configurator.addCollateralToken( tokenAddress, 8500 // liquidationThreshold (85% = 8500 basis points) ); // Add adapter configurator.allowAdapter(adapterAddress); // Set fees configurator.setFees( 500, // feeInterest (5%) 150, // feeLiquidation (1.5%) 500, // liquidationPremium (5%) 100, // feeLiquidationExpired (1%) 500 // liquidationPremiumExpired (5%) ); ``` ### Custom Configurator for Spokes ```solidity import {CreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditConfiguratorV3.sol"; contract DerivativesSpokeConfigurator is CreditConfiguratorV3 { DerivativesSpoke public immutable spoke; constructor( address _creditManager, address _creditFacade ) CreditConfiguratorV3(_creditManager, _creditFacade) { spoke = DerivativesSpoke(_creditManager); } // Protocol-specific configuration function setPositionLimits( uint256 maxLongExposure, uint256 maxShortExposure ) external configuratorOnly { spoke.setPositionLimits(maxLongExposure, maxShortExposure); } function setCustomLiquidationPremium( address token, uint16 premium ) external configuratorOnly { spoke.setTokenLiquidationPremium(token, premium); } } ``` ### Governance Integration For production deployments, configurator changes should go through governance: ```solidity // Propose change via governance forum // After approval, execute via timelock // Example: Adding new collateral token bytes memory data = abi.encodeCall( ICreditConfiguratorV3.addCollateralToken, (newTokenAddress, 8000) ); // Submit to governance contract governance.propose( configuratorAddress, 0, // value data, "Add NEWTOKEN as collateral with 80% LT" ); ``` **Key patterns demonstrated:** 1. **State extension**: Added `Position` tracking on top of base Credit Manager 2. **Collateral override**: Modified TWV calculation to include unrealized PnL 3. **Protocol-specific logic**: Market management and position tracking 4. **Governance integration**: `configuratorOnly` modifier for admin functions For architectural background, see [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite) and [Pool Architecture](https://docs.gearbox.finance/developers/pools). ## Security Considerations When extending core contracts: 1. **Preserve invariants** - Never break core protocol assumptions (debt tracking, collateral checks) 2. **Test extensively** - Fork mainnet and test against real pools/Credit Managers 3. **Audit thoroughly** - Core extensions require professional audits 4. **Consider upgrade paths** - Plan for parameter changes and emergency controls 5. **Monitor gas costs** - Overrides affect every user operation 6. **Validate inputs** - Custom logic must not allow manipulation of debt/collateral calculations ## Deployment Checklist * [ ] Core contracts audited by reputable firm * [ ] Fork tests against production Gearbox contracts * [ ] Governance proposal prepared with detailed specification * [ ] Documentation for users and integrators * [ ] Emergency pause mechanisms tested * [ ] Oracle manipulation scenarios analyzed * [ ] Gas profiling completed for all overridden functions * [ ] Upgradeability plan documented ## Related * [Adapter Development](https://docs.gearbox.finance/developers/adapter-development) - Standard integration path * [Protocol Integration](https://docs.gearbox.finance/developers/protocol-integration) - Building on top of Credit Accounts * [Credit Suite Architecture](https://docs.gearbox.finance/developers/credit-suite) - Core architecture reference * [Pool Architecture](https://docs.gearbox.finance/developers/pools) - Pool mechanics reference ## Liquidation Bots Source: https://docs.gearbox.finance/developers/liquidation-bots-2 File: content/developers/liquidation-bots-2.mdx Build on-chain liquidation contracts that can be triggered by keepers or automation services. > For SDK-based liquidation bots (recommended for most use cases), see [Liquidation Bots (SDK)](https://docs.gearbox.finance/developers/liquidation-bots). ## Overview On-chain liquidation contracts are useful when you need: * Atomicity with flash loans or other on-chain operations * Integration with existing keeper infrastructure (Gelato, Chainlink Automation) * Custom liquidation logic that must execute trustlessly * Protocol-owned liquidation capability Most liquidation bots use the SDK for monitoring and multicall building, then submit transactions off-chain. This guide covers the less common but important pattern of on-chain liquidation contracts. *** ## Understanding On-Chain Liquidation **WHY:** Know when to build a contract vs. use the SDK. ### When to Use On-Chain Contracts | Approach | Best For | | --------------------- | -------------------------------------------------------------------------- | | **SDK bot** | Most liquidators - flexible routing, off-chain simulation, rapid iteration | | **On-chain contract** | Flash loan liquidations, keeper automation, protocol-owned backstop | ### The Liquidation Entry Point ```solidity function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls, bytes memory lossPolicyData ) external; ``` The liquidator provides: * `creditAccount` - the account to liquidate * `to` - where remaining funds go after debt repayment * `calls` - multicall array that converts collateral to underlying * `lossPolicyData` - custom data for loss handling *** ## Checking Liquidatability **WHY:** Don't waste gas on accounts that can't be liquidated. ### Health Factor Check ```solidity import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {CollateralDebtData, CollateralCalcTask} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; function isLiquidatable( address creditManager, address creditAccount ) public view returns (bool, uint256 healthFactor) { CollateralDebtData memory cdd = ICreditManagerV3(creditManager) .calcDebtAndCollateral( creditAccount, CollateralCalcTask.DEBT_COLLATERAL ); healthFactor = (cdd.twvUSD * 10000) / cdd.totalDebtUSD; return (healthFactor < 10000, healthFactor); } ``` ### Expiration Check ```solidity import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; function isExpired(address creditFacade) public view returns (bool) { uint40 expirationDate = ICreditFacadeV3(creditFacade).expirationDate(); return expirationDate != 0 && block.timestamp > expirationDate; } ``` *** ## Building Liquidation Multicalls **WHY:** The multicall converts collateral tokens to underlying. Efficient routing means higher profit. ### Basic Swap Pattern For a single collateral token, swap it to underlying via the adapter: ```solidity import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; function buildSwapCalls( address creditManager, address creditFacade, address tokenIn, address tokenOut, address dexRouter ) internal view returns (MultiCall[] memory calls) { // Get adapter for DEX address adapter = ICreditManagerV3(creditManager).contractToAdapter(dexRouter); require(adapter != address(0), "No adapter"); calls = new MultiCall[](1); // Use diff function to swap entire balance minus 1 wei calls[0] = MultiCall({ target: adapter, callData: abi.encodeCall( ISwapAdapter.exactAllInputSingle, ISwapAdapter.ExactAllInputSingleParams({ tokenIn: tokenIn, tokenOut: tokenOut, fee: 3000, deadline: block.timestamp, rateMinRAY: 0, // Simplified: no slippage protection sqrtPriceLimitX96: 0 }) ) }); } ``` ### Multi-Collateral Pattern When an account has multiple collateral tokens: ```solidity function buildLiquidationCalls( address creditManager, address creditFacade, address underlying, address[] memory collateralTokens, address dexRouter ) internal view returns (MultiCall[] memory calls) { address adapter = ICreditManagerV3(creditManager).contractToAdapter(dexRouter); require(adapter != address(0), "No adapter"); // One swap per non-underlying collateral token uint256 swapCount; for (uint256 i = 0; i < collateralTokens.length; i++) { if (collateralTokens[i] != underlying) swapCount++; } calls = new MultiCall[](swapCount); uint256 callIdx; for (uint256 i = 0; i < collateralTokens.length; i++) { if (collateralTokens[i] == underlying) continue; calls[callIdx] = MultiCall({ target: adapter, callData: abi.encodeCall( ISwapAdapter.exactAllInputSingle, ISwapAdapter.ExactAllInputSingleParams({ tokenIn: collateralTokens[i], tokenOut: underlying, fee: 3000, deadline: block.timestamp, rateMinRAY: 0, sqrtPriceLimitX96: 0 }) ) }); callIdx++; } } ``` *** ## Simple Liquidation Contract **WHY:** A complete working example you can deploy and test. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {CollateralDebtData, CollateralCalcTask} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract SimpleLiquidator { address public owner; address public immutable creditFacade; address public immutable creditManager; address public immutable underlying; address public immutable dexRouter; constructor( address _creditFacade, address _dexRouter ) { owner = msg.sender; creditFacade = _creditFacade; creditManager = ICreditFacadeV3(_creditFacade).creditManager(); underlying = ICreditManagerV3(creditManager).underlying(); dexRouter = _dexRouter; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } /// @notice Liquidate an account, swapping specified tokens to underlying /// @param creditAccount The account to liquidate /// @param tokensToSwap Collateral tokens to swap (excluding underlying) function liquidate( address creditAccount, address[] calldata tokensToSwap ) external onlyOwner { // Build multicall: swap each collateral token to underlying address adapter = ICreditManagerV3(creditManager) .contractToAdapter(dexRouter); require(adapter != address(0), "No adapter for DEX"); MultiCall[] memory calls = new MultiCall[](tokensToSwap.length); for (uint256 i = 0; i < tokensToSwap.length; i++) { calls[i] = MultiCall({ target: adapter, callData: abi.encodeCall( ISwapAdapter.exactAllInputSingle, ISwapAdapter.ExactAllInputSingleParams({ tokenIn: tokensToSwap[i], tokenOut: underlying, fee: 3000, deadline: block.timestamp, rateMinRAY: 0, sqrtPriceLimitX96: 0 }) ) }); } // Execute liquidation - remaining funds sent to this contract ICreditFacadeV3(creditFacade).liquidateCreditAccount( creditAccount, address(this), // Receive remaining funds here calls, "" // lossPolicyData ); } /// @notice Check if liquidation would be profitable function estimateProfit( address creditAccount ) external view returns (bool profitable, uint256 healthFactor) { CollateralDebtData memory cdd = ICreditManagerV3(creditManager) .calcDebtAndCollateral( creditAccount, CollateralCalcTask.DEBT_COLLATERAL ); healthFactor = (cdd.twvUSD * 10000) / cdd.totalDebtUSD; // Profitable if account is liquidatable and has excess value profitable = healthFactor < 10000 && cdd.twvUSD > cdd.totalDebtUSD; } /// @notice Withdraw profits function withdraw(address token) external onlyOwner { uint256 balance = IERC20(token).balanceOf(address(this)); if (balance > 0) { IERC20(token).transfer(owner, balance); } } } ``` *** ## Flash Loan Liquidation **WHY:** Flash loans let you liquidate without upfront capital. For partial liquidations, the liquidator must provide underlying tokens. Flash loans make this capital-free: ```solidity import {IFlashLoanReceiver} from "@aave/v3-core/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol"; contract FlashLiquidator is IFlashLoanReceiver { address public immutable creditFacade; address public immutable creditManager; address public immutable underlying; address public immutable aavePool; function flashLiquidate( address creditAccount, address token, uint256 repaidAmount ) external { // Initiate flash loan for repaidAmount of underlying bytes memory params = abi.encode(creditAccount, token, repaidAmount); IPool(aavePool).flashLoanSimple( address(this), underlying, repaidAmount, params, 0 // referralCode ); } function executeOperation( address asset, uint256 amount, uint256 premium, address initiator, bytes calldata params ) external returns (bool) { require(msg.sender == aavePool, "Not pool"); (address creditAccount, address token, uint256 repaidAmount) = abi.decode(params, (address, address, uint256)); // Approve underlying to credit manager IERC20(underlying).approve(creditManager, repaidAmount); // Execute partial liquidation uint256 seized = ICreditFacadeV3(creditFacade) .partiallyLiquidateCreditAccount( creditAccount, token, repaidAmount, 0, // minSeizedAmount (simplified) address(this), new PriceUpdate[](0) ); // Repay flash loan (amount + premium) uint256 amountOwed = amount + premium; IERC20(asset).approve(aavePool, amountOwed); // Profit = seized token value - flash loan cost return true; } } ``` *** ## Gotchas ### Approve to Credit Manager, Not Facade For partial liquidations where you provide underlying: ```solidity // WRONG IERC20(underlying).approve(creditFacade, amount); // CORRECT IERC20(underlying).approve(creditManager, amount); ``` ### Gas Costs Scale with Token Count Liquidation gas depends on: * Number of collateral tokens to swap * Complexity of DEX routes * Price feed updates needed Estimate gas before submitting to ensure profitability. ### Race Conditions Multiple liquidators compete for the same accounts. On-chain contracts are at a disadvantage vs. off-chain bots that can use Flashbots/MEV protection. Consider: * Using higher priority fees for competitive scenarios * Targeting accounts that off-chain bots may skip (complex collateral compositions) * Bundling with Flashbots Protect for MEV protection ### exactAllInputSingle vs exactInputSingle Use `exactAllInputSingle` (the "diff" pattern) for liquidation swaps. It swaps the entire balance minus dust, which is what you want when converting all collateral: ```solidity // WRONG: Requires knowing exact balance abi.encodeCall(ISwapAdapter.exactInputSingle, (...)) // CORRECT: Swaps entire balance automatically abi.encodeCall(ISwapAdapter.exactAllInputSingle, (...)) ``` *** ## Next Steps * [Liquidation Bots (SDK)](https://docs.gearbox.finance/developers/liquidation-bots) - SDK-based approach (recommended for most cases) * [Liquidations Reference](https://docs.gearbox.finance/developers/liquidations) - Full liquidation mechanics * [Bot System Reference](https://docs.gearbox.finance/developers/bot-system) - Permission system for authorized bots * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Adapter patterns for swaps * [Protocol Integration](https://docs.gearbox.finance/developers/protocol-integration) - General patterns for building on Gearbox ## Credit Suite Source: https://docs.gearbox.finance/developers/credit-suite File: content/developers/credit-suite.mdx The Credit Suite is the foundational unit that enables leverage in Gearbox Protocol. It acts as an isolated environment where borrowers interact with DeFi protocols using borrowed funds while ensuring lender safety. ## Component Overview A Credit Suite consists of three tightly coupled smart contracts deployed together: ## Credit Manager (Logic Layer) The Credit Manager is the "brain" of the suite. It maintains the registry of Credit Accounts, connects to the underlying Pool, and calculates account solvency via the Price Oracle. **Key Responsibilities:** | Function | Purpose | | ------------------ | ------------------------------------------------------ | | Debt tracking | Maintains total debt and collateral tokens per account | | Pool interaction | Borrows and repays via PoolV3 | | Quota management | Coordinates with PoolQuotaKeeper for token limits | | Health calculation | Computes Health Factors via `calcDebtAndCollateral` | The Credit Manager is generally not accessed directly by users but by the Facade or Adapters. ## Credit Facade (Access Layer) The Credit Facade is the "face" of the suite. It serves as the primary entry point for users and implements the multicall logic, allowing complex DeFi operations to happen in a single transaction. **Key Responsibilities:** | Function | Purpose | | --------------------- | ---------------------------------------------------------------- | | Multicall execution | Iterates through user-provided calls, routing to Credit Account | | Security checks | Performs collateral check (Health Factor > 1) at transaction end | | Permission management | Manages BotList permissions for approved bots | | Access control | Enforces minDebt, maxDebt, and forbiddenTokenMask limits | ## Credit Configurator (Governance Layer) The Credit Configurator provides a secure interface for Risk Curators (or the DAO) to manage the suite without direct contract upgrades. **Key Responsibilities:** | Function | Purpose | | ---------------------- | --------------------------------- | | Collateral tokens | Adding/removing allowed tokens | | Liquidation thresholds | Setting LT per token | | Adapters | Configuring protocol integrations | | Fees and limits | Adjusting fee parameters | ## Configuration Parameters Risk Curators utilize the Credit Configurator to define the risk profile: | Parameter | Description | | ------------------------------ | ----------------------------------------------------------------------- | | **Liquidation Threshold (LT)** | Maximum leverage for a token. LT of 8500 (85%) implies \~6.6x leverage. | | **Collateral Tokens** | Allowed tokens in Credit Accounts. Unlisted tokens value at 0. | | **Adapters** | Whitelisted protocol integrations (e.g., Uniswap, Curve). | | **Debt Limits** | minDebt and maxDebt per account to prevent dust or concentration risk. | | **Fees** | feeLiquidation (to protocol) and liquidationPremium (to liquidator). | ## Borrowing and Repayment Flow ### Borrowing 1. User calls CreditFacade (open account or increase debt) 2. Facade validates request against debt limits 3. Facade instructs Manager to borrow 4. Manager calls Pool.lendCreditAccount(amount, creditAccount) 5. Pool transfers underlying directly to Credit Account 6. Pool updates interest rate based on new utilization ### Repayment 1. User calls CreditFacade (close account or decrease debt) 2. Manager calculates principal + interest + quota fees 3. Underlying transfers to Pool (from user or Credit Account) 4. Manager calls Pool.repayCreditAccount 5. Pool burns treasury shares (if loss) or mints treasury shares (if profit) ## Health Factor The Health Factor determines account solvency: **Health Factor = Total Weighted Value / Total Debt** * Total Weighted Value: Sum of (asset value \* liquidation threshold) for all collateral * Total Debt: Principal + accrued interest + quota fees When Health Factor drops below 1.0, the account becomes liquidatable. ## Implementation For implementation details, see: * **TypeScript/SDK:** [SDK Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) * **Solidity:** [Credit Accounts](https://docs.gearbox.finance/developers/credit-accounts) ## Pools Source: https://docs.gearbox.finance/developers/pools File: content/developers/pools.mdx PoolV3 is the central vault for a specific underlying asset (e.g., USDC, WETH). It follows the ERC-4626 tokenized vault standard, allowing users to deposit assets and receive Diesel Tokens (LP tokens) representing their share. ## Core Design Principles Unlike standard lending protocols, users do not borrow directly from the pool. Instead, whitelisted Credit Managers borrow liquidity on behalf of Credit Accounts to execute leveraged strategies. This separation ensures: * All borrowed funds flow through Credit Accounts with proper collateral checks * Lenders earn passive yield without exposure to leverage decisions * Risk is isolated at the Credit Manager level ## ERC-4626 Compliance PoolV3 implements the full ERC-4626 tokenized vault standard. Any tooling built for ERC-4626 vaults works with Gearbox pools. **Standard Functions:** | Function | Purpose | | ----------------------------------- | ------------------------------------------ | | `deposit(assets, receiver)` | Deposit underlying, receive shares | | `mint(shares, receiver)` | Mint exact shares, deposit required assets | | `withdraw(assets, receiver, owner)` | Withdraw exact assets, burn shares | | `redeem(shares, receiver, owner)` | Burn exact shares, receive assets | | `convertToShares(assets)` | Preview shares for asset amount | | `convertToAssets(shares)` | Preview assets for share amount | **Gearbox Extensions:** | Function | Purpose | | --------------------- | -------------------------------- | | `depositWithReferral` | On-chain referral tracking | | `lendCreditAccount` | Credit Manager-only borrowing | | `repayCreditAccount` | Credit Manager-only repayment | | `dieselRate()` | Share price in RAY (27 decimals) | ## Diesel Rate (Share Price) The diesel rate represents how many underlying tokens each diesel token (share) is worth. It starts at 1 RAY (10^27) and increases as interest accrues. **Calculation:** * 1 diesel token = dieselRate / 10^27 underlying tokens * The rate grows over time as borrowers pay interest * Lenders profit as their shares become worth more underlying ## Yield Sources Lenders provide liquidity to earn passive yield from two sources: 1. **Base Interest:** Paid by borrowers on the principal debt 2. **Quota Revenue:** Paid by borrowers for the right to hold specific collateral tokens The combined yield is reflected in the `supplyRate()` function. ## Withdrawal Mechanics Withdrawals in Gearbox V3 are subject to a withdrawal fee. This fee is taken from the interest earned, keeping the capital principal intact when possible. **Key considerations:** * Withdrawals revert if the pool is paused * Available liquidity limits maximum withdrawal * Fee calculation happens automatically during redeem/withdraw ## Credit Manager Interaction Only whitelisted Credit Managers can borrow from the pool: | Function | Access | Purpose | | ------------------------------------------ | ------- | ---------------- | | `lendCreditAccount(amount, creditAccount)` | CM only | Borrow from pool | | `repayCreditAccount(repaid, profit, loss)` | CM only | Repay to pool | Regular users cannot call these functions. All borrowing flows through the Credit Suite. ## Pool State Key state variables for monitoring pool health: | Field | Description | | -------------------- | ------------------------- | | `totalAssets` | Total value held by pool | | `availableLiquidity` | Borrowable amount | | `dieselRate` | Current share price (RAY) | | `supplyRate` | Lender APY (RAY) | | `baseInterestRate` | Borrower APR (RAY) | ## Interest Rate Determination The pool does not store interest rate logic. It queries the Interest Rate Model (IRM) whenever state changes. See the Interest Rate Model reference for the utilization curve mechanics. ## Implementation For implementation details, see: * **TypeScript/SDK:** [Reading Data](https://docs.gearbox.finance/developers/reading-data) * **Solidity:** [Pool Operations](https://docs.gearbox.finance/developers/pool-operations) ## Guides Source: https://docs.gearbox.finance/developers/gm-guides File: content/developers/gm-guides.mdx End-to-end tutorials for common Gearbox integration scenarios. | Guide | Best For | What You'll Build | | --- | --- | --- | | [**Frontend Applications**](https://docs.gearbox.finance/developers/gm-guide-frontend) | UI developers | Dashboard displaying pool data, account positions, real-time updates | | [**Backend Services**](https://docs.gearbox.finance/developers/gm-guide-backend) | Backend engineers | Event indexer, historical data tracker, analytics API | | [**Liquidation Bots**](https://docs.gearbox.finance/developers/gm-guide-bots) | Bot builders | Monitoring service that detects and executes liquidations | Each guide includes a complete working example with the Gearbox SDK. ## Multicall System Source: https://docs.gearbox.finance/developers/multicall-system File: content/developers/multicall-system.mdx The multicall system allows complex DeFi operations to execute in a single transaction. It is orchestrated by the CreditFacadeV3, with a mandatory collateral check at the end to ensure account solvency. ## The MultiCall Structure A multicall is an array of operations, each specifying a target contract and encoded function call: | Field | Type | Description | | ---------- | --------- | --------------------------------------- | | `target` | `address` | CreditFacade or allowed Adapter address | | `callData` | `bytes` | Encoded function call | ## Execution Flow ``` ### Step-by-Step Process 1. **Start MultiCall:** Facade emits StartMultiCall event 2. **Execution Loop:** Facade iterates through calls array * If target is Facade: executes internal protocol logic * If target is Adapter: routes call to whitelisted adapter 3. **End MultiCall:** Facade unsets active account status 4. **Security Checks:** * Slippage verification against stored expected balances * Forbidden token balance checks * Full collateral check (Health Factor > 1) ## Available Operations Operations available within a multicall fall into categories: ### Protocol Logic | Operation | Purpose | | -------------------- | ----------------------------------------- | | `addCollateral` | Move tokens from wallet to Credit Account | | `increaseDebt` | Borrow underlying from Pool | | `decreaseDebt` | Repay debt to Pool | | `updateQuota` | Purchase quota for collateral token | | `withdrawCollateral` | Remove assets from account | ### Safety Controls | Operation | Purpose | | ----------------------- | ------------------------------------------ | | `onDemandPriceUpdates` | Push fresh price data (must be first call) | | `storeExpectedBalances` | Record balances for slippage check | | `compareBalances` | Trigger slippage verification | | `setFullCheckParams` | Optimize collateral check with hints | ### External Calls External calls go through Adapters - whitelisted contracts that translate Gearbox calls to target protocol calls. Each Credit Manager has its own set of allowed adapters. ## Multicall-Supporting Functions All CreditFacade functions that modify state accept a multicall array: | Function | Purpose | | ------------------------ | ------------------------------------------- | | `openCreditAccount` | Create account with initial operations | | `closeCreditAccount` | Close account, return remaining funds | | `multicall` | Execute operations on existing account | | `botMulticall` | Bot-initiated operations (with permissions) | | `liquidateCreditAccount` | Liquidate unhealthy account | This allows all account management to happen in one transaction, minimizing gas overhead by batching under a single collateral check. ## Security Mechanisms ### Slippage Protection Use `storeExpectedBalances` before swaps and `compareBalances` after to prevent sandwich attacks. The protocol compares actual balances against expected minimums. ### Forbidden Token Handling Some tokens may be marked "forbidden" - their balances cannot increase during a multicall. The Facade checks forbidden token balances before and after execution. ### Collateral Check Every multicall (except close/liquidate) ends with a collateral check: * Total Weighted Value must exceed Total Debt * Health Factor = TWV / Debt must be > 1.0 * Failed check reverts the entire transaction ## The "Diff" Pattern Adapters implement `*_diff` functions (e.g., `swapDiff`, `depositDiff`) for handling unknown amounts. Instead of specifying exact input amounts: * Standard: needs exact `amountIn` * Diff: calculates `amountIn = currentBalance - leftoverAmount` This is essential when the exact result of a previous operation is unknown. ## Bot Permissions Multicalls enforce granular permissions via bitmask: | Permission | Purpose | | --------------------- | ------------------------- | | `ADD_COLLATERAL` | Move funds into account | | `INCREASE_DEBT` | Borrow more from pool | | `UPDATE_QUOTA` | Modify quota settings | | `EXTERNAL_CALLS` | Call external adapters | | `WITHDRAW_COLLATERAL` | Remove funds from account | A user can delegate specific permissions to bots while protecting against unauthorized withdrawals. ## Best Practices 1. **Price updates first:** Always put `onDemandPriceUpdates` at the start if using pull-based oracles 2. **Slippage protection:** Always use `storeExpectedBalances` when performing swaps 3. **Dust management:** Use `type(uint256).max` in `withdrawCollateral` to empty balances (subtracts 1 wei automatically) 4. **Gas optimization:** Use `setFullCheckParams` with hints for accounts with many tokens ## Implementation For implementation details, see: * **TypeScript/SDK:** [SDK Multicalls](https://docs.gearbox.finance/developers/multicalls) * **Solidity:** [Solidity Multicalls](https://docs.gearbox.finance/developers/multicalls) ## Credit Manager Source: https://docs.gearbox.finance/developers/credit-manager File: content/developers/credit-manager.mdx The **CreditManagerV3** is the core accounting engine of the Gearbox Protocol. It manages Credit Account lifecycle, tracks debt and collateral, calculates health factors, and enforces risk parameters. All Credit Account state is stored and managed through this contract. ## Core State: CreditAccountInfo Every Credit Account's state is tracked in a `CreditAccountInfo` struct: | Field | Type | Description | | --------------------------- | --------- | ------------------------------------------------------ | | `debt` | `uint256` | Principal amount borrowed from the pool | | `cumulativeIndexLastUpdate` | `uint256` | Pool's interest index at last debt update | | `cumulativeQuotaInterest` | `uint128` | Accrued quota interest not yet added to debt | | `quotaFees` | `uint128` | Quota fees owed | | `enabledTokensMask` | `uint256` | Bitmask of currently enabled collateral tokens | | `flags` | `uint16` | Account state flags (e.g., `BOT_PERMISSIONS_SET_FLAG`) | | `lastDebtUpdate` | `uint64` | Timestamp of last debt change (flash-loan protection) | | `borrower` | `address` | Current account owner | *** ## Debt Tracking Architecture ### Indexed Interest Accrual The Credit Manager uses an indexed interest model rather than storing accrued interest directly. This approach is gas-efficient because it doesn't require per-block updates. **Formula:** ``` accruedInterest = debt * (currentIndex - lastIndex) / lastIndex totalDebt = principal + accruedInterest + quotaInterest + fees ``` The `cumulativeIndexLastUpdate` is snapshotted whenever debt changes, allowing the contract to calculate exact interest at any time by comparing against the Pool's current `baseInterestIndex()`. ### Flash-Loan Protection The `lastDebtUpdate` timestamp prevents multiple debt changes in the same block. This protects against flash-loan attacks where an attacker could: 1. Borrow heavily 2. Manipulate prices 3. Close position profitably Any second debt change in the same block will revert. *** ## Collateral Management ### Token Masking System Every collateral token has a unique `uint256` mask (power of 2). An account's `enabledTokensMask` is the bitwise OR of all enabled token masks. **Benefits:** * **O(enabled\_tokens) iteration**: Health checks only iterate over enabled tokens, not all possible collateral * **Gas efficiency**: Single storage slot tracks all enabled/disabled states * **Atomic updates**: Enable/disable multiple tokens in one operation ```typescript // TypeScript: Checking enabled tokens const enabledMask = await creditManager.read.enabledTokensMaskOf([creditAccount]); // Get all enabled token addresses const collateralTokens = []; for (let i = 0; i < 256; i++) { if (enabledMask & (1n << BigInt(i))) { const tokenData = await creditManager.read.getTokenByMask([1n << BigInt(i)]); collateralTokens.push(tokenData); } } ``` ### Liquidation Thresholds (LT) Each collateral token has a Liquidation Threshold determining how much of its value counts toward collateralization: | Parameter | Description | | -------------------- | ------------------------------------ | | `ltInitial` | Starting LT value (e.g., 8500 = 85%) | | `ltFinal` | Final LT after ramping | | `timestampRampStart` | When LT ramping begins | | `rampDuration` | Duration of linear interpolation | **LT Ramping** allows gradual changes to risk parameters without sudden liquidation cascades. The current LT is linearly interpolated between `ltInitial` and `ltFinal` over the ramp period. ### Phantom Tokens Phantom tokens are virtual representations of staked/LP positions (e.g., staked Curve LP tokens). They implement `IPhantomToken.getPhantomTokenInfo()` which returns the underlying deposited token. During health factor calculations, phantom tokens are resolved to their underlying value, ensuring proper collateral accounting for wrapped positions. *** ## Health Factor Calculation ### TWV Formula The Health Factor determines whether an account is solvent: ``` TWV (Threshold Weighted Value) = Sum(Balance_i * Price_i * LT_i) TotalDebt = principal + baseInterest + quotaInterest + fees HealthFactor = TWV / TotalDebt ``` An account is: * **Healthy**: HF >= 1.0 (10000 in basis points) * **Liquidatable**: HF < 1.0 ### Collateral Calculation Modes The Credit Manager supports different calculation modes for gas optimization: | Mode | Use Case | | ---------------------------- | ------------------------------------------------ | | `DEBT_ONLY` | Calculate debt + interest without collateral | | `DEBT_COLLATERAL` | Full TWV + HF calculation | | `FULL_COLLATERAL_CHECK_LAZY` | Optimized: stops early when HF exceeds threshold | The lazy mode is used during multicalls where we only need to verify HF > 1, not calculate the exact value. ```typescript // TypeScript: Reading account health import { getContract } from 'viem'; const creditManager = getContract({ address: creditManagerAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get full debt breakdown const debtData = await creditManager.read.calcDebtAndCollateral([ creditAccount, 2 // CollateralCalcTask.DEBT_COLLATERAL ]); // debtData returns: { debt, accruedInterest, accruedFees, totalDebtUSD, // totalValue, twvUSD, enabledTokensMask, ... } const healthFactor = debtData.twvUSD * 10000n / debtData.totalDebtUSD; console.log(`Health Factor: ${Number(healthFactor) / 100}%`); ``` *** ## Pool and Oracle Coordination ### Pool Interaction The Credit Manager coordinates with PoolV3 for all debt operations: | Operation | Pool Function | | ----------------- | --------------------------------------------- | | Borrow | `pool.lendCreditAccount(debt, creditAccount)` | | Repay | `pool.repayCreditAccount(debt, profit, loss)` | | Interest tracking | `pool.baseInterestIndex()` | On liquidation with loss, the Credit Manager reports the loss to the Pool, which may burn Treasury shares or incur "uncovered loss" that's socialized across LPs. ### Oracle Interaction Price data comes from `PriceOracleV3`: | Function | Description | | ----------------------------- | ------------------------------------------------- | | `convertToUSD(amount, token)` | Convert token amount to USD value | | `getPrice(token)` | Get token price (respects `USE_SAFE_PRICES_FLAG`) | **Dual Price Safety**: When `USE_SAFE_PRICES_FLAG` is set (for forbidden tokens), the oracle returns `min(primaryPrice, reservePrice)` to protect against price manipulation. *** ## Access Control The Credit Manager restricts sensitive operations: | Caller | Allowed Operations | | ---------------------------- | ----------------------------------------------- | | `CreditFacadeV3` | All account operations (open, close, multicall) | | `CreditConfiguratorV3` | Parameter updates (tokens, adapters, fees) | | Credit Account (via execute) | Adapter calls during multicall | External contracts cannot directly manipulate Credit Account state - all operations must flow through the Facade.
Sources * [contracts/credit/CreditManagerV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditManagerV3.sol) * [contracts/interfaces/ICreditManagerV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/ICreditManagerV3.sol) * [contracts/libraries/CreditLogic.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/libraries/CreditLogic.sol) * [contracts/libraries/CollateralLogic.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/libraries/CollateralLogic.sol)
## Frontend Applications Source: https://docs.gearbox.finance/developers/gm-guide-frontend File: content/developers/gm-guide-frontend.mdx Build dashboards, portfolio trackers, and trading UIs that display Gearbox protocol data and let users manage positions. ## Overview Frontend applications typically need to: 1. Display pool and market data 2. Show collateral exposure and limits 3. Monitor credit account health 4. Enable position management This guide maps each requirement to specific SDK methods. *** ## Pool Display **WHY:** Users want to see pool health, yields, and utilization before depositing or borrowing. ### Data Requirements | Display | SDK Source | Field | | ------------------- | ------------- | -------------------------------------------------- | | Underlying token | `market.pool` | `underlying.symbol`, `underlying.address` | | Total supplied | `market.pool` | `totalAssets` | | Available liquidity | `market.pool` | `availableLiquidity` | | Utilization | Calculated | `(totalAssets - availableLiquidity) / totalAssets` | | Supply APY | `market.pool` | `supplyRate` (RAY scaled) | | Borrow APR | `market.pool` | `baseInterestRate` (RAY scaled) | | Share price | `market.pool` | `dieselRate` (RAY scaled) | ### How to Fetch ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Find market by pool address const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; // Display data const RAY = 10n ** 27n; console.log(`Underlying: ${pool.underlying.symbol}`); console.log(`Total Supplied: ${pool.totalAssets}`); console.log(`Available: ${pool.availableLiquidity}`); // Calculate utilization const borrowed = pool.totalAssets - pool.availableLiquidity; const utilization = Number(borrowed * 10000n / pool.totalAssets) / 100; console.log(`Utilization: ${utilization.toFixed(2)}%`); // Convert RAY rates to percentages const supplyAPY = Number(pool.supplyRate * 10000n / RAY) / 100; const borrowAPR = Number(pool.baseInterestRate * 10000n / RAY) / 100; console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); console.log(`Borrow APR: ${borrowAPR.toFixed(2)}%`); ``` ### Where Data Comes From The SDK caches market data on initialization via `GearboxSDK.attach()`. This data comes from the `MarketCompressor` contract. For real-time updates, either: * Re-initialize the SDK periodically * Call compressors directly (see [Real-Time Updates](#real-time-updates)) *** ## Collateral Exposure **WHY:** Users want to see what collaterals the pool is exposed to and current utilization against limits. ### Data Requirements | Display | SDK Source | Field | | --------------------- | ------------------------ | ------------------- | | Quoted tokens | `MarketData.quotaKeeper` | `tokens[]` | | Token quota limit | `quotaKeeper.tokens[]` | `limit` | | Current quoted amount | `quotaKeeper.tokens[]` | `totalQuoted` | | Quota rate | `quotaKeeper.tokens[]` | `rate` (RAY scaled) | ### How to Fetch For quota data, use the `MarketCompressor` directly: ```typescript import { marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; // Get compressor address const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Fetch market data with quota keeper const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); // QuotaKeeper token data for (const token of marketData.quotaKeeper.tokens) { const utilizationPct = Number(token.totalQuoted * 10000n / token.limit) / 100; console.log(`Token: ${token.token}`); console.log(` Limit: ${token.limit}`); console.log(` Quoted: ${token.totalQuoted}`); console.log(` Utilization: ${utilizationPct.toFixed(2)}%`); console.log(` Rate: ${token.rate}`); // RAY scaled } ``` ### Gotcha: Quota Limits Before letting users open positions with a specific collateral, check that `totalQuoted < limit`. If the limit is reached, new positions with that collateral will fail. *** ## Credit Manager Configuration **WHY:** Users need to know debt limits, collateral requirements, and fees before opening positions. ### Data Requirements | Display | SDK Source | Field | | --------------------- | -------------------- | ------------------------------------- | | Min debt | `creditManager` | `minDebt` | | Max debt | `creditManager` | `maxDebt` | | Collateral tokens | `creditManager` | `collateralTokens[]` | | Liquidation threshold | `collateralTokens[]` | `liquidationThreshold` (basis points) | | Fees | `creditManager` | `fees` | | Liquidation premium | `creditManager` | `liquidationPremium` | ### How to Fetch ```typescript const market = sdk.marketRegister.findByCreditManager(cmAddress); for (const cm of market.creditManagers) { console.log(`Credit Manager: ${cm.address}`); console.log(`Credit Facade: ${cm.creditFacade}`); // Debt limits console.log(`Min Debt: ${cm.minDebt}`); console.log(`Max Debt: ${cm.maxDebt}`); // Collateral configuration console.log('Allowed Collaterals:'); for (const token of cm.collateralTokens) { // LT is in basis points (10000 = 100%) const ltPct = Number(token.liquidationThreshold) / 100; console.log(` ${token.symbol}: LT ${ltPct.toFixed(1)}%`); } } ``` ### Understanding Liquidation Threshold The liquidation threshold (LT) determines how much each collateral contributes to the weighted collateral value: ``` Weighted Value = Token Balance * Token Price * LT Health Factor = Total Weighted Value / Total Debt ``` A lower LT means the protocol values that collateral more conservatively. *** ## Credit Account Monitoring **WHY:** Users need to track their position health, collateral values, and accrued interest. ### Data Requirements | Display | SDK Source | Field | | ---------------- | ------------------- | ---------------------------- | | Account address | `CreditAccountData` | `addr` | | Owner | `CreditAccountData` | `owner` | | Total debt | `CreditAccountData` | `debt` | | Health factor | `CreditAccountData` | `healthFactor` (10000 = 1.0) | | Is liquidatable | `CreditAccountData` | `isLiquidatable` | | Token balances | `CreditAccountData` | `tokens[]` | | Token values | `tokens[]` | `balanceInUnderlying` | | Accrued interest | `CreditAccountData` | `cumulativeQuotaInterest` | | Quota fees | `CreditAccountData` | `quotaFees` | ### How to Fetch ```typescript import { createCreditAccountService } from '@gearbox-protocol/sdk'; const service = createCreditAccountService(sdk, 310); // Get user's accounts const accounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: userAddress, }, sdk.currentBlock ); for (const account of accounts) { // Health factor: 10000 = 1.0 const hf = Number(account.healthFactor) / 10000; console.log(`Account: ${account.addr}`); console.log(`Health Factor: ${hf.toFixed(4)}`); console.log(`Liquidatable: ${account.isLiquidatable}`); // Debt breakdown console.log(`Total Debt: ${account.debt}`); console.log(`Accrued Interest: ${account.cumulativeQuotaInterest}`); console.log(`Quota Fees: ${account.quotaFees}`); // Token positions console.log('Positions:'); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}:`); console.log(` Balance: ${token.balance}`); console.log(` Value (underlying): ${token.balanceInUnderlying}`); console.log(` LT: ${token.lt / 100}%`); } } } ``` ### Interest Breakdown Credit account debt consists of: * **Principal:** Original borrowed amount * **Base interest:** Accrues on principal based on pool utilization * **Quota interest:** Per-collateral rate for non-underlying tokens * **Quota fees:** Fixed fee component for quotas ```typescript // Total debt = principal + base interest + quota interest + quota fees const principal = account.borrowedAmount; const quotaInterest = account.cumulativeQuotaInterest; const quotaFees = account.quotaFees; const baseInterest = account.debt - principal - quotaInterest - quotaFees; ``` ### Health Factor Thresholds Display appropriate warnings based on health factor: ```typescript function getHealthStatus(hf: number): string { if (hf < 1.0) return 'LIQUIDATABLE'; if (hf < 1.05) return 'CRITICAL'; if (hf < 1.1) return 'WARNING'; return 'HEALTHY'; } const status = getHealthStatus(Number(account.healthFactor) / 10000); ``` *** ## Price Feed Information **WHY:** Users want to understand which oracles determine their collateral values. ### How to Fetch ```typescript import { priceFeedCompressorAbi, AP_PRICE_FEED_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; // Get price feed compressor const [priceFeedCompressor] = sdk.addressProvider.mustGetLatest( AP_PRICE_FEED_COMPRESSOR, VERSION_RANGE_310 ); // Get all price feeds for a price oracle const feeds = await client.readContract({ address: priceFeedCompressor, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [priceOracleAddress], }); for (const feed of feeds) { console.log(`Token: ${feed.token}`); console.log(` Feed: ${feed.priceFeed}`); console.log(` Needs Update: ${feed.needsUpdate}`); } ``` *** ## Position Management **WHY:** Users take actions on their positions (add collateral, borrow, repay, etc.). ### Linking to Operations Position management uses multicalls. Link to the appropriate operation guide: | User Action | Operation Guide | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | Deposit collateral | [Adding Collateral](https://docs.gearbox.finance/developers/adding-collateral) | | Borrow more | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Repay debt | [Debt Management](https://docs.gearbox.finance/developers/debt-management) | | Update quota | [Updating Quotas](https://docs.gearbox.finance/developers/updating-quotas) | | Withdraw | [Withdrawing Collateral](https://docs.gearbox.finance/developers/withdrawing-collateral) | | Swap collateral | [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) | ### Pre-Operation Data Checks Before letting users perform operations, validate: ```typescript // Before addCollateral: Check token is allowed const isAllowed = market.creditManagers[0].collateralTokens .some(t => t.address === tokenAddress); // Before increaseDebt: Check against max debt const newDebt = account.debt + borrowAmount; const isWithinLimit = newDebt <= cm.maxDebt; // Before updateQuota: Check quota capacity const quotaToken = marketData.quotaKeeper.tokens .find(t => t.token === tokenAddress); const hasCapacity = quotaToken && quotaToken.totalQuoted < quotaToken.limit; // Before any operation: Estimate health factor impact // (Use simulation or calculate locally) ``` *** ## Real-Time Updates **WHY:** UI needs to stay current as blockchain state changes. ### Option 1: Poll Compressors For most dashboards, polling every few seconds is sufficient: ```typescript const POLL_INTERVAL = 5000; // 5 seconds async function pollMarketData() { const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); // Update UI state setPoolData(marketData.pool); setQuotaData(marketData.quotaKeeper); } // Start polling setInterval(pollMarketData, POLL_INTERVAL); ``` ### Option 2: Watch Events For specific state changes, watch contract events: ```typescript import { parseAbiItem } from 'viem'; // Watch for credit account state changes const unwatch = client.watchContractEvent({ address: creditManagerAddress, abi: creditManagerAbi, eventName: 'ExecuteOrder', onLogs: (logs) => { // Refresh account data for affected accounts for (const log of logs) { refreshAccountData(log.args.creditAccount); } }, }); // Cleanup on unmount return () => unwatch(); ``` ### Recommendation Use **polling** for: * General market data * Pool state (rates, liquidity) * Quota utilization Use **events** for: * User's own account changes * Critical health factor alerts *** ## Complete Example: Dashboard Component ```typescript import { GearboxSDK, createCreditAccountService, marketCompressorAbi } from '@gearbox-protocol/sdk'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; interface DashboardData { pool: { underlying: string; totalAssets: bigint; availableLiquidity: bigint; supplyAPY: number; borrowAPR: number; }; userAccounts: Array<{ address: string; healthFactor: number; debt: bigint; isLiquidatable: boolean; }>; } async function fetchDashboardData( poolAddress: `0x${string}`, cmAddress: `0x${string}`, userAddress: `0x${string}` ): Promise { const client = createPublicClient({ chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const RAY = 10n ** 27n; // Pool data const market = sdk.marketRegister.findByPool(poolAddress); const pool = market.pool; // User accounts const service = createCreditAccountService(sdk, 310); const accounts = await service.getCreditAccounts( { creditManager: cmAddress, owner: userAddress }, sdk.currentBlock ); return { pool: { underlying: pool.underlying.symbol, totalAssets: pool.totalAssets, availableLiquidity: pool.availableLiquidity, supplyAPY: Number(pool.supplyRate * 10000n / RAY) / 100, borrowAPR: Number(pool.baseInterestRate * 10000n / RAY) / 100, }, userAccounts: accounts.map(a => ({ address: a.addr, healthFactor: Number(a.healthFactor) / 10000, debt: a.debt, isLiquidatable: a.isLiquidatable, })), }; } ``` *** ## Next Steps * [Multicall Operations](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Implement position management * [Backend Services](https://docs.gearbox.finance/developers/gm-guide-backend) - If you also need historical data * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete compressor API ## Backend Services Source: https://docs.gearbox.finance/developers/gm-guide-backend File: content/developers/gm-guide-backend.mdx Build indexers, analytics pipelines, and data warehouses that track Gearbox protocol state over time. ## Overview Backend services typically need to: 1. Capture historical snapshots at specific blocks 2. Index events for efficient state reconstruction 3. Track rates, values, and utilization over time 4. Store and query historical data This guide shows patterns for each requirement. *** ## Historical Snapshots **WHY:** Track how protocol state changes over time for analytics, reporting, and historical queries. ### What to Snapshot | Data | Source | Change Frequency | | -------------------- | ------------------- | ------------------------- | | Pool rates | `PoolState` | Every block with activity | | Pool liquidity | `PoolState` | Every deposit/borrow | | Quota utilization | `QuotaKeeperState` | Every position change | | Credit account state | `CreditAccountData` | Every account operation | | Token prices | `PriceOracle` | External feed updates | ### How to Query at Specific Blocks Compressors support querying at historical blocks using viem's `blockTag` or `blockNumber`: ```typescript import { marketCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); // Query at specific block const historicalData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber: 19000000n, // Specific block }); console.log(`Pool state at block 19000000:`); console.log(` Available liquidity: ${historicalData.pool.availableLiquidity}`); console.log(` Supply rate: ${historicalData.pool.supplyRate}`); ``` ### Archive Node Requirements Historical queries require an archive node. Standard nodes only keep recent state (~128 blocks). **RPC providers with archive access:** * Alchemy (archive add-on) * Infura (archive add-on) * QuickNode (archive plans) * Self-hosted Erigon/Reth ### Snapshot Pattern ```typescript interface PoolSnapshot { blockNumber: bigint; timestamp: number; availableLiquidity: bigint; totalAssets: bigint; supplyRate: bigint; borrowRate: bigint; } async function capturePoolSnapshot( blockNumber: bigint ): Promise { const block = await client.getBlock({ blockNumber }); const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber, }); return { blockNumber, timestamp: Number(block.timestamp), availableLiquidity: marketData.pool.availableLiquidity, totalAssets: marketData.pool.totalAssets, supplyRate: marketData.pool.supplyRate, borrowRate: marketData.pool.baseInterestRate, }; } // Capture hourly snapshots const BLOCKS_PER_HOUR = 300n; // ~12 second blocks let currentBlock = startBlock; while (currentBlock <= endBlock) { const snapshot = await capturePoolSnapshot(currentBlock); await saveToDatabase(snapshot); currentBlock += BLOCKS_PER_HOUR; } ``` *** ## Event Indexing **WHY:** Events provide efficient tracking of specific state changes without polling. ### Key Events Credit Facade emits events for all account operations: | Event | When Emitted | Key Data | | ------------------------ | ------------------ | ----------------------------------------- | | `OpenCreditAccount` | Account opened | owner, creditAccount, borrowAmount | | `CloseCreditAccount` | Account closed | creditAccount | | `LiquidateCreditAccount` | Account liquidated | creditAccount, liquidator, remainingFunds | | `StartMultiCall` | Multicall begins | creditAccount | | `FinishMultiCall` | Multicall ends | creditAccount | Pool emits events for liquidity changes: | Event | When Emitted | Key Data | | ---------- | ---------------------- | ----------------------------------- | | `Deposit` | LP deposits | sender, owner, assets, shares | | `Withdraw` | LP withdraws | sender, receiver, assets, shares | | `Borrow` | Credit Manager borrows | creditAccount, amount | | `Repay` | Debt repaid | creditAccount, amount, profit, loss | ### Watching Events with viem ```typescript import { parseAbiItem } from 'viem'; // Watch for new credit accounts const unwatchOpen = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', onLogs: async (logs) => { for (const log of logs) { console.log(`New account: ${log.args.creditAccount}`); console.log(` Owner: ${log.args.owner}`); console.log(` Initial debt: ${log.args.borrowAmount}`); await indexCreditAccount(log); } }, }); // Watch for liquidations const unwatchLiquidate = client.watchContractEvent({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'LiquidateCreditAccount', onLogs: async (logs) => { for (const log of logs) { console.log(`Liquidated: ${log.args.creditAccount}`); console.log(` Liquidator: ${log.args.liquidator}`); await recordLiquidation(log); } }, }); ``` ### Fetching Historical Events For backfilling, fetch events in block ranges: ```typescript async function fetchHistoricalEvents( fromBlock: bigint, toBlock: bigint ) { // Fetch in chunks to avoid RPC limits const CHUNK_SIZE = 10000n; let current = fromBlock; while (current <= toBlock) { const chunkEnd = current + CHUNK_SIZE > toBlock ? toBlock : current + CHUNK_SIZE; const logs = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', fromBlock: current, toBlock: chunkEnd, }); for (const log of logs) { await processEvent(log); } current = chunkEnd + 1n; } } ``` *** ## State Tracking **WHY:** Build complete account or pool history over time by combining events and snapshots. ### Credit Account Lifecycle Track an account from open to close: ```typescript interface AccountHistory { creditAccount: string; owner: string; openBlock: bigint; closeBlock: bigint | null; operations: AccountOperation[]; } interface AccountOperation { blockNumber: bigint; txHash: string; type: 'open' | 'multicall' | 'liquidate' | 'close'; healthFactorAfter?: bigint; } async function trackAccountLifecycle(creditAccount: string): Promise { // Find open event const openEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'OpenCreditAccount', args: { creditAccount }, fromBlock: 0n, toBlock: 'latest', }); const openEvent = openEvents[0]; // Find all multicall events const multicallEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'FinishMultiCall', args: { creditAccount }, fromBlock: openEvent.blockNumber, toBlock: 'latest', }); // Find close event (if any) const closeEvents = await client.getContractEvents({ address: creditFacadeAddress, abi: creditFacadeAbi, eventName: 'CloseCreditAccount', args: { creditAccount }, fromBlock: openEvent.blockNumber, toBlock: 'latest', }); return { creditAccount, owner: openEvent.args.owner, openBlock: openEvent.blockNumber, closeBlock: closeEvents[0]?.blockNumber ?? null, operations: [ { blockNumber: openEvent.blockNumber, txHash: openEvent.transactionHash, type: 'open' }, ...multicallEvents.map(e => ({ blockNumber: e.blockNumber, txHash: e.transactionHash, type: 'multicall' as const, })), ...(closeEvents[0] ? [{ blockNumber: closeEvents[0].blockNumber, txHash: closeEvents[0].transactionHash, type: 'close' as const, }] : []), ].sort((a, b) => Number(a.blockNumber - b.blockNumber)), }; } ``` ### Combining Events and Snapshots For complete state reconstruction: ```typescript async function reconstructAccountStateAtBlock( creditAccount: string, targetBlock: bigint ): Promise { // Check if account existed at this block const history = await trackAccountLifecycle(creditAccount); if (history.openBlock > targetBlock) { return null; // Account didn't exist yet } if (history.closeBlock && history.closeBlock <= targetBlock) { return null; // Account was closed } // Query compressor at target block const [accountData] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccountData', args: [creditManagerAddress, creditAccount], blockNumber: targetBlock, }); return accountData; } ``` *** ## Rate History **WHY:** Analytics on yield, utilization trends, and rate changes over time. ### Rates to Track | Rate | Source | Notes | | --------------- | --------------------------- | -------------------------------------------------- | | Supply APY | `pool.supplyRate` | RAY scaled (10^27) | | Base borrow APR | `pool.baseInterestRate` | RAY scaled | | Quota rates | `quotaKeeper.tokens[].rate` | Per-token, RAY scaled | | Utilization | Calculated | `(totalAssets - availableLiquidity) / totalAssets` | ### Polling Pattern ```typescript interface RateSnapshot { blockNumber: bigint; timestamp: number; supplyRate: bigint; borrowRate: bigint; utilization: number; quotaRates: Map; } async function pollRates(): Promise { const block = await client.getBlock({ blockTag: 'latest' }); const marketData = await client.readContract({ address: compressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); const pool = marketData.pool; const borrowed = pool.totalAssets - pool.availableLiquidity; const utilization = pool.totalAssets > 0n ? Number(borrowed * 10000n / pool.totalAssets) / 100 : 0; const quotaRates = new Map(); for (const token of marketData.quotaKeeper.tokens) { quotaRates.set(token.token, token.rate); } return { blockNumber: block.number, timestamp: Number(block.timestamp), supplyRate: pool.supplyRate, borrowRate: pool.baseInterestRate, utilization, quotaRates, }; } // Poll every minute setInterval(async () => { const snapshot = await pollRates(); await saveRateSnapshot(snapshot); }, 60_000); ``` ### Rate Conversion Convert RAY-scaled rates to annual percentages: ```typescript const RAY = 10n ** 27n; function rayToAnnualPercent(rayRate: bigint): number { // rate is per-second, annualize it const SECONDS_PER_YEAR = 365n * 24n * 60n * 60n; const annualRate = rayRate * SECONDS_PER_YEAR; return Number(annualRate * 10000n / RAY) / 100; } const supplyAPY = rayToAnnualPercent(pool.supplyRate); console.log(`Supply APY: ${supplyAPY.toFixed(2)}%`); ``` *** ## Complete Example: Simple Indexer ```typescript import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; import { GearboxSDK, marketCompressorAbi, creditAccountCompressorAbi, AP_MARKET_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; interface IndexerState { lastIndexedBlock: bigint; pools: Map; accounts: Map; } async function runIndexer( startBlock: bigint, poolAddress: `0x${string}`, creditManagerAddress: `0x${string}` ) { const client = createPublicClient({ chain: mainnet, transport: http(process.env.ARCHIVE_RPC_URL), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const [marketCompressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); let currentBlock = startBlock; while (true) { const latestBlock = await client.getBlockNumber(); while (currentBlock <= latestBlock) { // Snapshot pool state const marketData = await client.readContract({ address: marketCompressor, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], blockNumber: currentBlock, }); await savePoolSnapshot(currentBlock, marketData.pool); // Index events in this block range const events = await client.getContractEvents({ address: creditManagerAddress, abi: creditFacadeAbi, fromBlock: currentBlock, toBlock: currentBlock + 100n, }); for (const event of events) { await processEvent(event); } currentBlock += 100n; } // Wait for new blocks await sleep(12_000); } } function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } ``` *** ## Next Steps * [Liquidation Bots](https://docs.gearbox.finance/developers/gm-guide-bots) - If you need to act on indexed data * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete compressor API * [Frontend Applications](https://docs.gearbox.finance/developers/gm-guide-frontend) - If you also need real-time display ## Credit Facade Source: https://docs.gearbox.finance/developers/credit-facade File: content/developers/credit-facade.mdx The **CreditFacadeV3** is the primary user-facing interface for Credit Account operations. It implements atomic multicall batching, enforces debt limits, manages bot permissions, and ensures all operations complete with a healthy account state. ## Multicall Execution Flow All Credit Account operations are executed through multicalls - batched transactions that execute atomically. ### Entry Points | Function | Caller | Description | | -------------------------------------------------- | -------------- | ------------------------------------------ | | `openCreditAccount(calls, onBehalfOf)` | Anyone | Create new account with initial operations | | `closeCreditAccount(creditAccount, calls)` | Owner | Close account, repay all debt | | `multicall(creditAccount, calls)` | Owner | Execute operations on existing account | | `botMulticall(creditAccount, calls)` | Authorized bot | Bot-initiated operations | | `liquidateCreditAccount(creditAccount, to, calls)` | Anyone | Liquidate unhealthy account | ### MultiCall Structure ```solidity struct MultiCall { address target; // Facade itself or whitelisted adapter bytes callData; // Function selector + encoded arguments } ``` ### Four Execution Phases **Phase 1: Initialization** * Emit `StartMultiCall` event * Snapshot forbidden token balances * Set flags (e.g., `REVERT_ON_FORBIDDEN_TOKENS_FLAG`) **Phase 2: Iterative Dispatch** ```solidity for (uint256 i = 0; i < calls.length; ++i) { if (target == address(this)) { _processFacadeCall(callData); // Internal operations } else { creditManager.setActiveCreditAccount(creditAccount); creditAccount.execute(target, callData); // External adapter } } ``` **Phase 3: Balance Tracking (Optional)** * `storeExpectedBalances(balances[])`: Store expected amounts * `compareBalances()`: Verify slippage protection **Phase 4: Final Validation** * Unset active account * Full collateral check (HF must be >= 1) * Verify forbidden token balances didn't increase *** ## Security Checks ### Access Control | Modifier | Effect | | ------------------------ | --------------------------------------- | | `creditAccountOwnerOnly` | Only account owner can call | | `nonReentrant` | Prevents reentrancy attacks | | `whenNotPaused` | Blocks operations when paused | | `whenNotExpired` | After expiration, only closures allowed | ### Bot Permission System Bots operate with granular permissions stored as a `uint192` bitmask: | Permission | Value | Operation | | -------------------------------- | --------- | --------------------- | | `ADD_COLLATERAL_PERMISSION` | `1 << 0` | Add funds to account | | `INCREASE_DEBT_PERMISSION` | `1 << 1` | Borrow more from pool | | `DECREASE_DEBT_PERMISSION` | `1 << 2` | Repay debt | | `WITHDRAW_COLLATERAL_PERMISSION` | `1 << 5` | Withdraw assets | | `UPDATE_QUOTA_PERMISSION` | `1 << 6` | Change token quotas | | `EXTERNAL_CALLS_PERMISSION` | `1 << 16` | Execute adapter calls | Each multicall operation checks against the caller's permission mask: ```solidity function _revertIfNoPermission(uint256 flags, uint256 permission) internal pure { if (flags & permission == 0) { revert NoPermissionException(permission); } } ``` *** ## Debt Limits Enforcement ### Global Limits Every Credit Facade enforces min/max debt bounds: ```solidity struct DebtLimits { uint128 minDebt; // Minimum principal (except 0) uint128 maxDebt; // Maximum principal } ``` **Validation:** ```solidity require(newDebt == 0 || (newDebt >= minDebt && newDebt <= maxDebt)); ``` Zero debt is always allowed (closing accounts), but any non-zero debt must fall within bounds. ### Per-Block Limit To prevent flash-loan exploits and rate manipulation: ```solidity uint8 maxDebtPerBlockMultiplier; uint256 limit = maxDebt * maxDebtPerBlockMultiplier; require(totalBorrowedInBlock[block.number] + amount <= limit); ``` This caps the total new debt that can be created in a single block across all Credit Accounts. ### Loss Policy When bad debt occurs during liquidation: 1. `maxDebtPerBlockMultiplier` is set to 0 2. All new borrowing is halted 3. Governance must intervene to restore normal operation This circuit breaker protects the protocol from cascading losses. ```typescript // TypeScript: Checking debt limits const facade = getContract({ address: facadeAddress, abi: creditFacadeV3Abi, client: publicClient, }); const [minDebt, maxDebt] = await facade.read.debtLimits(); const multiplier = await facade.read.maxDebtPerBlockMultiplier(); // Check if borrowing is allowed if (multiplier === 0) { console.log('Borrowing is currently disabled'); } console.log(`Debt range: ${minDebt} - ${maxDebt}`); console.log(`Per-block limit: ${maxDebt * BigInt(multiplier)}`); ``` *** ## Forbidden Tokens Logic Forbidden tokens are high-risk assets that require special handling. They still count toward collateral value but have restrictions. ### Protection Flags | Flag | Effect | | --------------------------------- | ----------------------------------------------- | | `REVERT_ON_FORBIDDEN_TOKENS_FLAG` | Revert if forbidden tokens are enabled | | `USE_SAFE_PRICES_FLAG` | Use `min(primary, reserve)` price for valuation | ### Rules 1. **Cannot increase quota** for forbidden tokens 2. **Balance must NOT increase** during multicall 3. **Safe pricing** is used during collateral checks This incentivizes users to reduce exposure to forbidden tokens while protecting the protocol from manipulation. ```typescript // TypeScript: Checking for forbidden tokens const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); const forbiddenMask = await creditManager.read.forbiddenTokenMask(); const enabledMask = await creditManager.read.enabledTokensMaskOf([creditAccount]); const hasForbidden = (enabledMask & forbiddenMask) !== 0n; if (hasForbidden) { console.log('Account has forbidden tokens - consider reducing exposure'); } ``` *** ## Account Lifecycle ### Opening an Account ``` User -> CreditFacade.openCreditAccount(calls, onBehalfOf) -> CreditManager.openCreditAccount(borrower, onBehalfOf) -> AccountFactory.takeCreditAccount(debt) -> Pool.lendCreditAccount(debt, account) -> _multicall(account, calls) -> CreditManager.fullCollateralCheck() ``` ### Multicall with Adapter ``` User -> CreditFacade.multicall([{target: adapter, callData}]) -> CreditManager.setActiveCreditAccount(account) -> CreditAccount.execute(adapter, callData) -> Adapter.someFunction(params) -> _execute(protocolCallData) -> CreditAccount -> DeFiProtocol.targetFunction() -> CreditManager.fullCollateralCheck() ``` ### Closing an Account ``` User -> CreditFacade.closeCreditAccount(account, calls) -> _multicall(account, calls) // Convert to underlying -> CreditManager.closeCreditAccount(account) -> Pool.repayCreditAccount(debt, profit, 0) -> Transfer remaining funds to user ```
Sources * [contracts/credit/CreditFacadeV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditFacadeV3.sol) * [contracts/interfaces/ICreditFacadeV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/ICreditFacadeV3.sol) * [contracts/interfaces/ICreditFacadeV3Multicall.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/ICreditFacadeV3Multicall.sol)
## Credit Configurator Source: https://docs.gearbox.finance/developers/credit-configurator File: content/developers/credit-configurator.mdx The **CreditConfiguratorV3** is the administrative gateway for the Credit Suite. It validates parameter changes and propagates them to the Credit Manager and Credit Facade. Importantly, it does not store configuration state itself - it acts as a validation layer. ## Architecture ``` User/DAO -> CreditConfiguratorV3 (validation) -> CreditManagerV3/CreditFacadeV3 (state) ``` The Configurator ensures all changes are valid before forwarding them to the appropriate contract. This separation allows for: * Centralized validation logic * Consistent access control * Audit trail through events *** ## Token & Risk Management ### Adding Collateral Tokens ```solidity function addCollateralToken(address token, uint16 liquidationThreshold); ``` **Validation:** * Token must be valid ERC-20 * Must have price feed in PriceOracle * Must be quoted in PoolQuotaKeeper * LT cannot exceed underlying's LT **For Phantom Tokens:** * The deposited (underlying) token must already exist as collateral * Phantom token represents staked/wrapped position ### Adjusting Liquidation Thresholds ```solidity // Immediate change function setLiquidationThreshold(address token, uint16 liquidationThreshold); // Gradual change (ramping) function rampLiquidationThreshold( address token, uint16 ltFinal, uint40 rampStart, uint24 rampDuration ); ``` **Ramping** allows gradual LT changes over time, preventing sudden liquidation cascades when risk parameters are adjusted. ### Forbidding/Allowing Tokens | Function | Access | Use Case | | ---------------------------- | --------------- | ------------------------------ | | `forbidToken(address token)` | Pausable Admins | Emergency: mark token as risky | | `allowToken(address token)` | Configurator | Restore normal token status | Forbidden tokens still count toward collateral (with safe pricing) but have restrictions on quota increases and balance changes. ```typescript // TypeScript: Reading token configuration const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get collateral token data const tokenMask = await creditManager.read.getTokenMaskOrRevert([tokenAddress]); const tokenData = await creditManager.read.collateralTokenByMask([tokenMask]); // Returns: { token, ltInitial, ltFinal, timestampRampStart, rampDuration } // Check if token is forbidden const forbiddenMask = await creditManager.read.forbiddenTokenMask(); const isForbidden = (tokenMask & forbiddenMask) !== 0n; ``` *** ## Fee Management ### Configurable Fees ```solidity function setFees( uint16 feeLiquidation, uint16 liquidationPremium, uint16 feeLiquidationExpired, uint16 liquidationPremiumExpired ); ``` | Parameter | Description | | --------------------------- | -------------------------------------- | | `feeLiquidation` | DAO fee on standard liquidations | | `liquidationPremium` | Reward for liquidators | | `feeLiquidationExpired` | Higher DAO fee for expired accounts | | `liquidationPremiumExpired` | Higher reward for expired liquidations | **Constraints:** * `feeLiquidation <= liquidationPremium` * `feeLiquidationExpired <= feeLiquidation` * `liquidationPremium + feeLiquidation < 100%` * Fee sum must remain constant (prevents sudden changes) The relationship ensures liquidators are always incentivized and the protocol takes a smaller cut than the liquidator reward. *** ## Borrowing Limits ### Debt Bounds ```solidity function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt); ``` **Validation:** * `minDebt <= maxDebt` * `maxDebt * maxEnabledTokens <= minDebt * 100` (safety ratio) * USD value of minDebt must be non-zero The safety ratio ensures accounts aren't opened with tiny debt that would be uneconomical to liquidate. ### Per-Block Multiplier ```solidity function setMaxDebtPerBlockMultiplier(uint8 multiplier); function forbidBorrowing(); // Sets multiplier to 0 ``` **`forbidBorrowing()`** is an emergency action available to Pausable Admins. It immediately halts all new borrowing without requiring a DAO vote. *** ## Adapter Management ### Allowing Adapters ```solidity function allowAdapter(address adapter); function forbidAdapter(address adapter); ``` **Validation:** * Adapter must implement `creditManager()` returning this Credit Manager * Adapter must implement `targetContract()` returning the DeFi protocol * Cannot target the Facade or Manager itself **Registration:** * Creates bidirectional mapping: `adapter <-> targetContract` * Credit Account can only call whitelisted adapters * Each target protocol has exactly one adapter ```typescript // TypeScript: Checking adapter status const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get adapter for a target protocol const adapterAddress = await creditManager.read.contractToAdapter([uniswapRouterAddress]); if (adapterAddress === '0x0000000000000000000000000000000000000000') { console.log('No adapter registered for this protocol'); } else { console.log(`Adapter: ${adapterAddress}`); } // Get all adapters const adaptersData = await creditManager.read.adapters(); ``` *** ## System Upgrades ### Oracle Updates ```solidity function setPriceOracle(address newPriceOracle); ``` Allows switching to a new price oracle implementation. The new oracle must support all currently configured collateral tokens. ### Facade Migration ```solidity function setCreditFacade(address newCreditFacade, bool migrateParams); ``` When `migrateParams` is true, debt limits and other Facade parameters are copied to the new contract. This enables upgrading the user-facing interface while preserving configuration. ### Configurator Upgrade ```solidity function upgradeCreditConfigurator(address newCreditConfigurator); ``` Transfers configurator role to a new contract. Used when the validation logic itself needs updating. *** ## Access Control Model ### Role Hierarchy | Role | Capabilities | | ------------------ | -------------------------------------------------------------------------- | | **Configurator** | All structural changes: tokens, fees, adapters, debt limits, upgrades | | **Pausable Admin** | Emergency actions: `forbidToken`, `forbidBorrowing` (no DAO vote required) | ### Cross-Contract Verification The Credit Manager and Facade verify that configuration calls come from the registered Configurator: ```solidity modifier creditConfiguratorOnly() { require(msg.sender == creditConfigurator); _; } ``` This prevents unauthorized parameter changes even if an attacker gains access to admin keys for other contracts. ```typescript // TypeScript: Reading configurator address const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); const configuratorAddress = await creditManager.read.creditConfigurator(); console.log(`Configurator: ${configuratorAddress}`); // Check access control roles (from ACL contract) const acl = getContract({ address: aclAddress, abi: aclAbi, client: publicClient, }); const isPausableAdmin = await acl.read.isPausableAdmin([someAddress]); const isConfigurator = await acl.read.isConfigurator([someAddress]); ```
Sources * [contracts/credit/CreditConfiguratorV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditConfiguratorV3.sol) * [contracts/interfaces/ICreditConfiguratorV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/ICreditConfiguratorV3.sol) * [contracts/core/ACL.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/core/ACL.sol)
## Liquidation Bots Source: https://docs.gearbox.finance/developers/gm-guide-bots File: content/developers/gm-guide-bots.mdx Build bots that monitor credit accounts and execute profitable liquidations. ## Overview Liquidation bots need to: 1. Find accounts with low health factors 2. Filter for liquidatable accounts 3. Compute optimal liquidation paths 4. Execute liquidations profitably This guide covers each step with verified SDK patterns. *** ## Understanding Liquidation **WHY:** Know what you're building before writing code. ### When Accounts Become Liquidatable An account becomes liquidatable when its health factor drops below 1.0: ``` Health Factor = Total Weighted Collateral Value / Total Debt Where: - Weighted Value = Sum of (Token Balance * Price * Liquidation Threshold) - Total Debt = Principal + Accrued Interest + Quota Fees ``` Health factor is scaled by 10000, so `healthFactor < 10000` means liquidatable. ### The Liquidation Process 1. **Liquidator calls** `creditFacade.liquidateCreditAccount()` 2. **Protocol converts** collateral to underlying token 3. **Debt is repaid** from converted collateral 4. **Liquidator receives** premium (configured per Credit Manager) 5. **Remaining funds** go to account owner (if any) The liquidator provides the multicall that handles collateral conversion. This is where profit comes from - efficient routing means better conversion rates. *** ## Finding Liquidatable Accounts **WHY:** Efficiently scan all accounts to find opportunities. ### Using CreditAccountCompressor The `CreditAccountCompressor` has built-in health factor filtering: ```typescript import { creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); // Find accounts with HF < 1.0 (10000 in basis points) const [accounts, total] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', // Any owner minHealthFactor: 0n, maxHealthFactor: 10000n, // HF < 1.0 includeZeroDebt: false, reverting: false, }, 0n, // offset ], }); console.log(`Found ${accounts.length} accounts with HF < 1.0`); ``` ### Filter by isLiquidatable The `isLiquidatable` field accounts for additional protocol checks: ```typescript const liquidatable = accounts.filter(a => a.isLiquidatable); console.log(`${liquidatable.length} are actually liquidatable`); for (const account of liquidatable) { console.log(`Account: ${account.addr}`); console.log(` Health Factor: ${Number(account.healthFactor) / 10000}`); console.log(` Debt: ${account.debt}`); console.log(` Collaterals:`); for (const token of account.tokens) { if (token.balance > 0n) { console.log(` ${token.symbol}: ${token.balance}`); } } } ``` ### Pagination for Large Result Sets The compressor returns paginated results. Iterate through all pages: ```typescript async function getAllLiquidatableAccounts( creditManager: `0x${string}` ): Promise { const filter = { owner: '0x0000000000000000000000000000000000000000' as const, minHealthFactor: 0n, maxHealthFactor: 10000n, includeZeroDebt: false, reverting: false, }; let offset = 0n; let allAccounts: CreditAccountData[] = []; while (true) { const [accounts, total] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [creditManager, filter, offset], }); const liquidatable = accounts.filter(a => a.isLiquidatable); allAccounts.push(...liquidatable); offset += BigInt(accounts.length); if (offset >= total) break; } return allAccounts; } ``` *** ## Account Analysis **WHY:** Understand an account's composition before liquidating. ### Collateral Breakdown ```typescript interface CollateralPosition { token: string; symbol: string; balance: bigint; valueInUnderlying: bigint; liquidationThreshold: number; } function analyzeCollateral(account: CreditAccountData): CollateralPosition[] { return account.tokens .filter(t => t.balance > 0n) .map(t => ({ token: t.token, symbol: t.symbol, balance: t.balance, valueInUnderlying: t.balanceInUnderlying, liquidationThreshold: Number(t.lt) / 100, })) .sort((a, b) => Number(b.valueInUnderlying - a.valueInUnderlying)); } const positions = analyzeCollateral(account); console.log('Collateral by value:'); for (const pos of positions) { console.log(` ${pos.symbol}: ${pos.valueInUnderlying} (LT: ${pos.liquidationThreshold}%)`); } ``` ### Estimating Profit ```typescript interface LiquidationEstimate { totalCollateralValue: bigint; debt: bigint; liquidationPremium: bigint; estimatedProfit: bigint; } function estimateLiquidation( account: CreditAccountData, premiumBps: number // e.g., 400 = 4% ): LiquidationEstimate { const totalValue = account.tokens.reduce( (sum, t) => sum + t.balanceInUnderlying, 0n ); const premium = totalValue * BigInt(premiumBps) / 10000n; // Simplified: assumes perfect conversion const estimatedProfit = totalValue - account.debt; return { totalCollateralValue: totalValue, debt: account.debt, liquidationPremium: premium, estimatedProfit: estimatedProfit > 0n ? estimatedProfit : 0n, }; } ``` *** ## Building the Liquidation Multicall **WHY:** The multicall handles collateral conversion and determines profit. ### Basic Structure A liquidation multicall typically: 1. Updates stale price feeds (if needed) 2. Swaps collateral tokens to underlying 3. Repays debt (handled by protocol) ```typescript import { encodeFunctionData } from 'viem'; import { iCreditFacadeV300MulticallAbi } from '@gearbox-protocol/sdk'; // Build liquidation multicall const calls: Array<{ target: `0x${string}`; callData: `0x${string}` }> = []; // 1. Update any stale price feeds first for (const feed of stalePriceFeeds) { calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'onDemandPriceUpdate', args: [feed.token, feed.reserve, feed.data], }), }); } // 2. Swap collateral to underlying via adapters for (const collateral of collateralToSwap) { const adapter = await creditManager.read.contractToAdapter([ collateral.protocol, ]); calls.push({ target: adapter, callData: encodeFunctionData({ abi: adapterAbi, functionName: 'swap', args: [collateral.swapParams], }), }); } ``` ### Using Slippage Protection Always protect against sandwich attacks: ```typescript // Store expected minimum output calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'storeExpectedBalances', args: [[{ token: underlyingToken, amount: minExpectedOutput }]], }), }); // Perform swap calls.push({ target: adapter, callData: encodeFunctionData({ abi: adapterAbi, functionName: 'swap', args: [swapParams], }), }); // Verify slippage calls.push({ target: creditFacadeAddress, callData: encodeFunctionData({ abi: iCreditFacadeV300MulticallAbi, functionName: 'compareBalances', args: [], }), }); ``` See [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) for details. *** ## Executing Liquidation **WHY:** Actually perform the liquidation and capture profit. ### The liquidateCreditAccount Call ```typescript // Get credit facade for the account's credit manager const market = sdk.marketRegister.findByCreditManager(account.creditManager); // Execute liquidation const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [ account.addr, // Credit account to liquidate receiverAddress, // Where to send remaining funds calls, // Liquidation multicall ], }); console.log(`Liquidation submitted: ${hash}`); // Wait for confirmation const receipt = await client.waitForTransactionReceipt({ hash }); console.log(`Liquidation ${receipt.status === 'success' ? 'succeeded' : 'failed'}`); ``` ### Handling Partial Liquidation In some configurations, partial liquidation is possible. Check the Credit Manager configuration: ```typescript // Full liquidation only if account is deeply underwater // Partial liquidation may be allowed above certain HF threshold ``` *** ## Bot Architecture **WHY:** Production bots need proper design for reliability and competitiveness. ### Monitoring Loop ```typescript async function monitoringLoop() { const POLL_INTERVAL = 3000; // 3 seconds while (true) { try { // Scan all credit managers for (const cm of creditManagers) { const accounts = await getAllLiquidatableAccounts(cm); for (const account of accounts) { // Analyze opportunity const estimate = estimateLiquidation(account, liquidationPremiumBps); if (estimate.estimatedProfit > minProfitThreshold) { await attemptLiquidation(account); } } } } catch (error) { console.error('Monitoring error:', error); } await sleep(POLL_INTERVAL); } } ``` ### Simulation Before Execution Always simulate before sending transactions: ```typescript async function attemptLiquidation(account: CreditAccountData) { const calls = buildLiquidationMulticall(account); // Simulate first try { await client.simulateContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], account: liquidatorAddress, }); } catch (error) { console.log(`Simulation failed for ${account.addr}:`, error); return; } // Simulation passed, execute try { const hash = await walletClient.writeContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], }); console.log(`Liquidation tx: ${hash}`); } catch (error) { console.error(`Execution failed:`, error); } } ``` ### Competition Considerations Liquidation is competitive. Other bots are scanning the same accounts. **Strategies:** * **Speed:** Use faster RPC endpoints, optimize code paths * **Gas:** Pay higher gas for priority (use `maxPriorityFeePerGas`) * **Efficiency:** Better swap routing means higher profit, can afford more gas * **Flashbots:** Use MEV-protected submission to avoid frontrunning ```typescript // Higher priority fee for competitive liquidations const hash = await walletClient.writeContract({ address: creditFacadeAddress, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [account.addr, receiverAddress, calls], maxPriorityFeePerGas: parseGwei('3'), // Higher tip }); ``` *** ## Complete Example: Simple Liquidation Bot ```typescript import { createPublicClient, createWalletClient, http, parseGwei } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { mainnet } from 'viem/chains'; import { GearboxSDK, creditAccountCompressorAbi, AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310, } from '@gearbox-protocol/sdk'; const MIN_PROFIT_USD = 100n * 10n ** 6n; // \$100 minimum profit async function runLiquidationBot(creditManagerAddress: `0x${string}`) { const client = createPublicClient({ chain: mainnet, transport: http(), }); const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const walletClient = createWalletClient({ account, chain: mainnet, transport: http(), }); const sdk = await GearboxSDK.attach({ client, marketConfigurators: [], }); const [accountCompressor] = sdk.addressProvider.mustGetLatest( AP_CREDIT_ACCOUNT_COMPRESSOR, VERSION_RANGE_310 ); const market = sdk.marketRegister.findByCreditManager(creditManagerAddress); console.log(`Monitoring ${market.creditManagers[0].address}`); console.log(`Liquidator: ${account.address}`); while (true) { try { // Find liquidatable accounts const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', minHealthFactor: 0n, maxHealthFactor: 10000n, includeZeroDebt: false, reverting: false, }, 0n, ], }); const liquidatable = accounts.filter(a => a.isLiquidatable); if (liquidatable.length > 0) { console.log(`Found ${liquidatable.length} liquidatable accounts`); for (const target of liquidatable) { const totalValue = target.tokens.reduce( (sum, t) => sum + t.balanceInUnderlying, 0n ); const estimatedProfit = totalValue - target.debt; if (estimatedProfit > MIN_PROFIT_USD) { console.log(`Profitable opportunity: ${target.addr}`); console.log(` Debt: ${target.debt}`); console.log(` Value: ${totalValue}`); console.log(` Est. Profit: ${estimatedProfit}`); // Build and execute liquidation const calls = buildLiquidationCalls(target, market); try { // Simulate await client.simulateContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [target.addr, account.address, calls], account: account.address, }); // Execute const hash = await walletClient.writeContract({ address: market.creditFacade.address, abi: creditFacadeAbi, functionName: 'liquidateCreditAccount', args: [target.addr, account.address, calls], maxPriorityFeePerGas: parseGwei('2'), }); console.log(`Liquidation submitted: ${hash}`); } catch (error) { console.log(`Failed to liquidate ${target.addr}:`, error); } } } } } catch (error) { console.error('Loop error:', error); } // Poll every 3 seconds await new Promise(resolve => setTimeout(resolve, 3000)); } } function buildLiquidationCalls( account: CreditAccountData, market: MarketData ): Array<{ target: `0x${string}`; callData: `0x${string}` }> { const calls: Array<{ target: `0x${string}`; callData: `0x${string}` }> = []; const creditFacade = market.creditFacade.address; const underlying = market.pool.underlying.address; // Swap each non-underlying collateral token to underlying via adapter for (const token of account.tokens) { if (token.balance <= 1n) continue; // skip dust if (token.token === underlying) continue; // skip underlying itself // Use Uniswap V3 adapter for swaps (simplified: hardcoded router) const uniswapAdapter = market.adapters?.['UNISWAP_V3_ROUTER']; if (!uniswapAdapter) continue; // exactAllInputSingle swaps entire balance minus 1 wei calls.push({ target: uniswapAdapter, callData: encodeFunctionData({ abi: uniswapV3AdapterAbi, functionName: 'exactAllInputSingle', args: [{ tokenIn: token.token, tokenOut: underlying, fee: 3000, // 0.3% pool (use 500 for stablecoin pairs) deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), rateMinRAY: 0n, // No slippage protection (simplified) sqrtPriceLimitX96: 0n, }], }), }); } return calls; } ``` *** ## Gotchas ### Price Updates Must Come First If any price feeds are stale, update them at the start of your multicall: ```typescript // WRONG: Swap first, then update prices (will fail) // CORRECT: Update prices first, then swap const calls = [ ...priceUpdateCalls, ...swapCalls, ]; ``` See [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds). ### Account State Can Change Between scanning and executing, another bot may liquidate the account: ```typescript try { await walletClient.writeContract({ ... }); } catch (error) { if (error.message.includes('account not liquidatable')) { console.log('Account already liquidated by another bot'); } } ``` ### Gas Estimation Liquidation gas costs vary based on: * Number of collateral tokens * Complexity of swaps * Price feed updates needed Always estimate gas before calculating profitability. *** ## Next Steps * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Swap patterns via adapters * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protect against MEV * [Updating Price Feeds](https://docs.gearbox.finance/developers/updating-price-feeds) - Required for stale oracles * [Compressors Reference](https://docs.gearbox.finance/developers/compressors) - Complete filter options ## Liquidations Source: https://docs.gearbox.finance/developers/liquidations File: content/developers/liquidations.mdx Liquidations are the mechanism that ensures protocol solvency by closing undercollateralized positions. Gearbox V3 supports both **full liquidations** (complete account closure) and **partial liquidations** (debt reduction while keeping account open). ## Full Liquidation Flow ### When Liquidations Occur An account becomes liquidatable when: * **Health Factor < 1.0** (undercollateralized) * **Account has expired** (past the configured expiration timestamp) Anyone can liquidate an unhealthy account - there's no whitelist. ### Entry Point ```solidity function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls, bytes memory lossPolicyData ) ``` | Parameter | Description | | ---------------- | ------------------------------------------ | | `creditAccount` | The account to liquidate | | `to` | Where liquidator receives remaining assets | | `calls` | Multicall array for converting collateral | | `lossPolicyData` | Custom data for loss handling | *** ## Liquidation Math ### Core Parameters | Parameter | Description | | ------------------------ | ------------------------------------------------ | | **Liquidation Premium** | % of account value liquidator receives as reward | | **Liquidation Discount** | % used to cover debt and fees (100% - Premium) | ### Fund Distribution Formula ```solidity uint256 totalFunds = totalValue * liquidationDiscount / PERCENTAGE_FACTOR; ``` **Liabilities** = Total Debt (Principal + Interest + Quota Fees) + DAO Liquidation Fee ### Outcomes **1. Solvent Liquidation** (`totalFunds > liabilities`) * Pool repaid in full * DAO receives liquidation fee * Remaining funds go to original borrower **2. Bad Debt** (`totalFunds < liabilities`) * DAO profit reduced first * If insufficient, loss reported to Pool * Pool burns Treasury shares to cover * If Treasury empty: "uncovered loss" (socialized across LPs) * Emergency: `maxDebtPerBlockMultiplier` set to 0 to halt borrowing *** ## Step-by-Step Execution ### 1. Trigger and Multicall Liquidator identifies account with HF < 1 and constructs multicall: ```typescript // TypeScript: Liquidation bot example const calls = [ // Swap collateral tokens to underlying via adapters { target: uniswapAdapterAddress, callData: encodeFunctionData({ abi: uniswapAdapterAbi, functionName: 'exactAllInputSingle', args: [{ tokenIn: wbtcAddress, tokenOut: usdcAddress, ... }] }) }, // Additional swaps as needed... ]; await creditFacade.write.liquidateCreditAccount([ creditAccountAddress, liquidatorAddress, // receives remaining assets calls, '0x' // lossPolicyData ]); ``` ### 2. Internal Execution 1. Calculate payments via `CreditLogic.calcLiquidationPayments` 2. Execute multicall (convert collateral to underlying) 3. Transfer `amountToPool` to PoolV3 4. Remove active quotas via PoolQuotaKeeper ### 3. Pool Distribution **Profits:** * Pool mints shares to Treasury **Losses:** * Burns Treasury shares * If Treasury empty: emits `IncurUncoveredLoss` * Triggers emergency borrowing halt ### 4. Remaining Funds 1. **Borrower's Share**: `minRemainingFunds` (if any) 2. **Liquidator's Share**: Everything else (includes premium) *** ## Fee Distribution ```solidity function _calcPartialLiquidationPayments( uint256 amount, address token, bool isExpired ) returns ( uint256 repaidAmount, uint256 feeAmount, uint256 seizedAmount ) ``` | Fee Type | Description | | ---------------------------- | --------------------------------- | | `feeLiquidation` | Standard liquidation fee to DAO | | `feeLiquidationExpired` | Higher fee for expired accounts | | `liquidationDiscount` | Discount for healthy liquidations | | `liquidationDiscountExpired` | Discount for expired liquidations | Expired accounts have higher fees to incentivize timely liquidation. *** ## Partial Liquidation ### When Allowed Partial liquidation is useful when: * Market liquidity is insufficient for full conversion * "Deleverage" strategy is preferred * Account can remain healthy with reduced debt ### Constraints * Account must remain open after liquidation * Must pass collateral check post-liquidation (HF >= 1) * Cannot leave "dust" debt below `minDebt` ### Execution ```solidity function partiallyLiquidateCreditAccount( address creditAccount, address token, uint256 repaidAmount, uint256 minSeizedAmount, address to, PriceUpdate[] calldata priceUpdates ) external returns (uint256 seizedAmount) ``` **Steps:** 1. Update price feeds (if provided) 2. Verify account is liquidatable (HF < 1 or expired) 3. Liquidator provides underlying as collateral 4. Calculate payments (repaid, fee, seized) 5. Handle phantom token withdrawal if applicable 6. Decrease account debt 7. Withdraw fee to treasury 8. Transfer seized collateral to liquidator 9. Full collateral check (HF must be >= 1 after) ### Health Factor Thresholds **Protocol Level:** * **Liquidation Trigger**: HF < 1.0 * **Post-Liquidation**: HF >= 1.0 (enforced) **Bot-Specific** (configurable in `PartialLiquidationBotV3`): * `minHealthFactor`: HF threshold for intervention * `maxHealthFactor`: Maximum HF after partial liquidation * Prevents "over-liquidation" ```typescript // TypeScript: Partial liquidation const seizedAmount = await creditFacade.write.partiallyLiquidateCreditAccount([ creditAccountAddress, wbtcAddress, // token to seize parseUnits('1000', 6), // repaid USDC amount parseUnits('0.03', 8), // min BTC to receive liquidatorAddress, [] // price updates ]); ``` *** ## Emergency Liquidations ### Regular vs Emergency | Type | When | Who | | ------------- | ----------------------------- | -------------------------------- | | **Regular** | Protocol functioning normally | Anyone | | **Emergency** | Protocol/Facade paused | `EMERGENCY_LIQUIDATOR` role only | ### whenNotPausedOrEmergency Modifier ```solidity modifier whenNotPausedOrEmergency() { require( !paused() || _hasRole("EMERGENCY_LIQUIDATOR", msg.sender), "Pausable: paused" ); _; } ``` This ensures liquidations can continue even during pause, preventing bad debt accumulation. ### Treasury Backstop The `TreasuryLiquidator` contract allows the DAO treasury to provide emergency liquidity: * Provides underlying funds when external liquidators are absent * Acts as backstop during extreme market conditions * Protects protocol from cascading losses ```typescript // TypeScript: Checking if account can be liquidated const creditFacade = getContract({ address: facadeAddress, abi: creditFacadeV3Abi, client: publicClient, }); const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get health factor const debtData = await creditManager.read.calcDebtAndCollateral([ creditAccount, 2 // DEBT_COLLATERAL ]); const hf = debtData.twvUSD * 10000n / debtData.totalDebtUSD; // Check expiration const expirationDate = await creditFacade.read.expirationDate(); const isExpired = BigInt(Math.floor(Date.now() / 1000)) > expirationDate; const isLiquidatable = hf < 10000n || isExpired; console.log(`Liquidatable: ${isLiquidatable}, HF: ${Number(hf) / 100}%`); ```
Sources * [contracts/credit/CreditFacadeV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditFacadeV3.sol) (lines 277-437) * [contracts/credit/CreditManagerV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditManagerV3.sol) * [contracts/libraries/CreditLogic.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/libraries/CreditLogic.sol) * [contracts/emergency/TreasuryLiquidator.sol](https://github.com/Gearbox-protocol/periphery-v3/blob/main/contracts/emergency/TreasuryLiquidator.sol) * [contracts/bots/PartialLiquidationBotV3.sol](https://github.com/Gearbox-protocol/bots-v3/blob/main/contracts/bots/PartialLiquidationBotV3.sol)
## Bot System Source: https://docs.gearbox.finance/developers/bot-system File: content/developers/bot-system.mdx Gearbox V3 features a sophisticated bot permission system that allows automated management of Credit Accounts. Bots can execute operations on behalf of account owners with granular, revocable permissions. ## BotList Architecture ### Components | Component | Description | | ------------------ | --------------------------------------------------------------------- | | **BotListV3** | Registry storing `(bot, creditManager, creditAccount)` -> permissions | | **IBot interface** | Bots implement `requiredPermissions()` to declare needed permissions | | **CreditFacadeV3** | Entry point for bot execution via `botMulticall` | ### Permission Storage ```solidity // BotListV3 storage mapping(address bot => mapping(address creditManager => mapping(address creditAccount => uint192 permissions) ) ) internal _permissions; ``` *** ## Permission Model ### Permission Flags Permissions are stored as a `uint192` bitmask: | Permission | Value | Operation | | -------------------------------- | --------- | --------------------- | | `ADD_COLLATERAL_PERMISSION` | `1 << 0` | Add funds to account | | `INCREASE_DEBT_PERMISSION` | `1 << 1` | Borrow more from pool | | `DECREASE_DEBT_PERMISSION` | `1 << 2` | Repay debt | | `WITHDRAW_COLLATERAL_PERMISSION` | `1 << 5` | Withdraw assets | | `UPDATE_QUOTA_PERMISSION` | `1 << 6` | Change token quotas | | `SET_BOT_PERMISSIONS_PERMISSION` | `1 << 8` | Manage other bots | | `EXTERNAL_CALLS_PERMISSION` | `1 << 16` | Execute adapter calls | ### ALL\_PERMISSIONS Constant ```solidity uint192 ALL_PERMISSIONS = ADD_COLLATERAL_PERMISSION | INCREASE_DEBT_PERMISSION | DECREASE_DEBT_PERMISSION | WITHDRAW_COLLATERAL_PERMISSION | UPDATE_QUOTA_PERMISSION | EXTERNAL_CALLS_PERMISSION; ``` Note: `SET_BOT_PERMISSIONS_PERMISSION` is excluded from `ALL_PERMISSIONS` - bots cannot grant permissions to other bots. *** ## Granting Permissions ### Setting Bot Permissions Account owners grant permissions through a multicall: ```solidity function _setBotPermissions(address creditAccount, bytes calldata callData) internal { (address bot, uint192 permissions) = abi.decode(callData, (address, uint192)); // Cannot grant SET_BOT_PERMISSIONS_PERMISSION uint192 allowedPermissions = ALL_PERMISSIONS & ~SET_BOT_PERMISSIONS_PERMISSION; uint192 unexpectedPermissions = permissions & ~allowedPermissions; if (unexpectedPermissions != 0) revert UnexpectedPermissionsException(unexpectedPermissions); uint256 remainingBots = IBotListV3(botList).setBotPermissions({ bot: bot, creditAccount: creditAccount, permissions: permissions }); // Update BOT_PERMISSIONS_SET_FLAG // ... } ``` ### Process 1. Owner calls `setBotPermissions(botAddress, permissions)` in multicall 2. BotListV3 validates permissions match bot's `requiredPermissions()` 3. Max 5 active bots per account (`MAX_SANE_ACTIVE_BOTS`) 4. Revoke by setting `permissions = 0` 5. `eraseAllBotPermissions()` clears all (called automatically on account close) ### BOT\_PERMISSIONS\_SET\_FLAG This optimization flag indicates whether an account has any authorized bots: * Set `true` when first bot is added * Set `false` when last bot is removed * Checked in `botMulticall` before querying BotListV3 ```typescript // TypeScript: Setting bot permissions import { encodeFunctionData } from 'viem'; const ADD_COLLATERAL = 1n << 0n; const DECREASE_DEBT = 1n << 2n; const EXTERNAL_CALLS = 1n << 16n; // Grant bot permission to add collateral, repay debt, and make external calls const permissions = ADD_COLLATERAL | DECREASE_DEBT | EXTERNAL_CALLS; const calls = [ { target: facadeAddress, callData: encodeFunctionData({ abi: creditFacadeMulticallAbi, functionName: 'setBotPermissions', args: [botAddress, permissions] }) } ]; await creditFacade.write.multicall([creditAccount, calls]); ``` *** ## Bot Operations ### botMulticall Execution ```solidity function botMulticall(address creditAccount, MultiCall[] calldata calls) external whenNotPaused whenNotExpired nonReentrant { _getBorrowerOrRevert(creditAccount); // Check bot status (uint256 botPermissions, bool forbidden) = IBotListV3(botList).getBotStatus({bot: msg.sender, creditAccount: creditAccount}); if (forbidden || botPermissions == 0 || _flagsOf(creditAccount) & BOT_PERMISSIONS_SET_FLAG == 0) { revert NotApprovedBotException(msg.sender); } // Execute with permission checks _multicall(creditAccount, calls, _enabledTokensMaskOf(creditAccount), botPermissions); } ``` ### Execution Flow 1. **Status Check**: Query BotListV3 for permissions & forbidden status 2. **Flag Check**: Verify `BOT_PERMISSIONS_SET_FLAG` is set 3. **Granular Authorization**: Each call checked against permission bitmask 4. **Solvency Guard**: Always `fullCollateralCheck` after all calls 5. **Revert**: If HF < 1 after execution ### Permission Enforcement ```solidity function _revertIfNoPermission(uint256 flags, uint256 permission) internal pure { if (flags & permission == 0) { revert NoPermissionException(permission); } } ``` Used throughout multicall processing: ```solidity _revertIfNoPermission(flags, ADD_COLLATERAL_PERMISSION); _revertIfNoPermission(flags, UPDATE_QUOTA_PERMISSION); _revertIfNoPermission(flags, WITHDRAW_COLLATERAL_PERMISSION); _revertIfNoPermission(flags, INCREASE_DEBT_PERMISSION); _revertIfNoPermission(flags, EXTERNAL_CALLS_PERMISSION); ``` *** ## Safety Model ### Security Properties | Property | Mechanism | | ----------------- | --------------------------------------------------- | | **Isolation** | Permissions are account-specific (not global) | | **Immutability** | Bots are typically immutable contracts | | **DAO Override** | Global "forbid" list in BotListV3 | | **Atomic Safety** | Post-execution collateral check prevents fund theft | | **No Bad Debt** | Bots cannot leave protocol with losses | ### Forbidden Bot List The DAO can globally forbid malicious bots: ```solidity // In BotListV3 function setBotForbiddenStatus(address bot, bool forbidden) external; ``` A forbidden bot cannot execute on any account, regardless of individual permissions. ### Collateral Check Protection Even with all permissions, a bot cannot: * Leave account with HF < 1 * Increase forbidden token balances * Bypass debt limits The final collateral check after `botMulticall` ensures account remains healthy. ```typescript // TypeScript: Bot executing operations const botClient = createWalletClient({ account: botAccount, chain: mainnet, transport: http(), }); const creditFacade = getContract({ address: facadeAddress, abi: creditFacadeV3Abi, client: botClient, }); // Bot-initiated operations const calls = [ // Add collateral { target: facadeAddress, callData: encodeFunctionData({ abi: creditFacadeMulticallAbi, functionName: 'addCollateral', args: [usdcAddress, parseUnits('1000', 6)] }) }, // Execute swap via adapter { target: uniswapAdapterAddress, callData: encodeFunctionData({ abi: uniswapAdapterAbi, functionName: 'exactInputSingle', args: [swapParams] }) } ]; // Execute as bot await creditFacade.write.botMulticall([creditAccountAddress, calls]); ``` *** ## Bot Development ### Implementing IBot Interface ```solidity interface IBot { function requiredPermissions() external view returns (uint192); } ``` Bots should declare minimum permissions needed: ```solidity contract MyRebalanceBot is IBot { function requiredPermissions() external pure returns (uint192) { return UPDATE_QUOTA_PERMISSION | EXTERNAL_CALLS_PERMISSION; } function rebalance(address creditAccount, address creditFacade) external { // Build multicall MultiCall[] memory calls = _buildRebalanceCalls(creditAccount); // Execute ICreditFacadeV3(creditFacade).botMulticall(creditAccount, calls); } } ``` ### Common Bot Patterns | Bot Type | Typical Permissions | | --------------------------- | ------------------------------------------------------------- | | **Rebalance Bot** | `UPDATE_QUOTA_PERMISSION`, `EXTERNAL_CALLS_PERMISSION` | | **Collateral Manager** | `ADD_COLLATERAL_PERMISSION`, `WITHDRAW_COLLATERAL_PERMISSION` | | **Debt Manager** | `INCREASE_DEBT_PERMISSION`, `DECREASE_DEBT_PERMISSION` | | **Partial Liquidation Bot** | `DECREASE_DEBT_PERMISSION`, `EXTERNAL_CALLS_PERMISSION` | ```typescript // TypeScript: Checking bot permissions const botList = getContract({ address: botListAddress, abi: botListV3Abi, client: publicClient, }); // Get bot status for specific account const [permissions, forbidden] = await botList.read.getBotStatus([ botAddress, creditAccountAddress ]); console.log(`Permissions: ${permissions.toString(2)}`); // Binary representation console.log(`Forbidden: ${forbidden}`); // Check specific permission const hasExternalCalls = (permissions & (1n << 16n)) !== 0n; console.log(`Can make external calls: ${hasExternalCalls}`); ```
Sources * [contracts/core/BotListV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/core/BotListV3.sol) * [contracts/interfaces/IBotListV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/IBotListV3.sol) * [contracts/interfaces/base/IBot.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/base/IBot.sol) * [contracts/credit/CreditFacadeV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditFacadeV3.sol)
## Quota Keeper Source: https://docs.gearbox.finance/developers/quota-keeper File: content/developers/quota-keeper.mdx In Gearbox V3, "Quotas" are the primary mechanism for managing risk associated with collateral exposure. While a lending pool provides the capital (underlying), the **PoolQuotaKeeper** regulates how much of that capital can be exposed to specific collateral types and at what cost. ## Pool and PoolQuotaKeeper Interaction The **Pool** (`PoolV3`) and the **PoolQuotaKeeper** (`PoolQuotaKeeperV3`) form a tight loop for financial accounting and revenue distribution. > For the economic rationale behind quotas and how they affect borrowing strategy, see [Quota Controls](https://github.com/de-snake/docs-knowledge/blob/new-docs-dev-1/new-docs-about/economics-and-risk/quota-controls.md). **Architecture and Data Flow** The Pool manages the actual ERC-20 assets, while the QuotaKeeper tracks the borrowing capacity for the collaterals. * **Additive Interest**: Unlike the base debt which compounds, quota interest is additive (linear). The QuotaKeeper tracks a `cumulativeIndex` for each token that grows based on the rate set by the Gauge. *** ## Quota Limits and Risk Management Quota limits represent the protocol's "risk appetite" for specific assets. They are enforced globally per pool. **What are Quota Limits?** A **Quota Limit** is the maximum aggregate amount of debt backed a specific collateral token that all Credit Accounts combined can hold. * **Preventing Tail Risk**: Limits ensure that the protocol is never over-exposed to a single asset that might suffer from a liquidity crunch or oracle failure. * **Capacity Control**: If a token's `totalQuoted` reaches its `limit`, any transaction attempting to increase that quota in a Credit Account will revert with `QuotaIsOutOfBoundsException`. **Mechanism for Setting Limits** Limits are managed directly on the `PoolQuotaKeeperV3` contract: * **Function**: `setTokenLimit(address token, uint96 limit)` * **Access Control**: Only the `CONFIGURATOR` (typically a Risk Curator or the DAO) can call this. * **Validation**: In the **Permissionless** framework, the `PoolFactory` ensures that limits cannot be set for tokens that do not have a valid price feed. *** ## Gauge Model: Gauge vs. Tumbler Every QuotaKeeper relies on an external contract implementing `IRateKeeper` to provide interest rates. **GaugeV3 (Voting Model)** Used in the core protocol where GEAR stakers vote to move interest rates between a `minRate` and `maxRate`. This reflects decentralized market sentiment regarding the risk/reward of specific strategies. **TumblerV3 (Curator Model)** The **Tumbler** is a simplified, non-voting implementation of the Gauge. It is the standard choice for curators in the Gearbox Permissionless ecosystem. * **Direct Control**: Curators set exact rates using `setRate(address token, uint16 rate)`. * **Epoch-Based Updates**: Rates are set in basis points but only take effect when `updateRates()` is called. The Tumbler enforces an `epochLength` to prevent frequent rate manipulation and provide predictability for borrowers. *** ## Data Retrieval and Observability To monitor the state of quotas and limits, several view functions are provided. **Global Quota Data** To see the status of a specific token within a pool, call `PoolQuotaKeeperV3.getTokenQuotaParams(address token)`. This returns: * `rate`: The current annual interest rate in basis points. * `totalQuoted`: Total amount of this token currently held as quota across all accounts. * `limit`: The maximum allowed `totalQuoted`. * `isActive`: Whether the token is currently quoted in this keeper. **User-Specific Data** To see how much quota an individual Credit Account is consuming, call `PoolQuotaKeeperV3.getQuotaAndOutstandingInterest(address creditAccount, address token)`. * `quoted`: The amount of token quota held by the account. * `outstandingInterest`: The interest accrued by this quota that hasn't been added to the account's debt yet. ```typescript // TypeScript: Reading quota data import { getContract } from 'viem'; const quotaKeeper = getContract({ address: quotaKeeperAddress, abi: poolQuotaKeeperV3Abi, client: publicClient, }); // Get global quota parameters for a token const tokenParams = await quotaKeeper.read.getTokenQuotaParams([tokenAddress]); // Returns: { rate, cumulativeIndex, quotaIncreaseFee, totalQuoted, limit, isActive } // Get account-specific quota and accrued interest const [quoted, outstandingInterest] = await quotaKeeper.read.getQuotaAndOutstandingInterest([ creditAccountAddress, tokenAddress, ]); // Check if quota is available (not at limit) const availableQuota = tokenParams.limit - tokenParams.totalQuoted; ```
Sources * [contracts/pool/PoolQuotaKeeperV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/pool/PoolQuotaKeeperV3.sol) * [contracts/pool/PoolV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/pool/PoolV3.sol) * [contracts/pool/TumblerV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/pool/TumblerV3.sol) * [contracts/interfaces/IPoolQuotaKeeperV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/interfaces/IPoolQuotaKeeperV3.sol) * [contracts/factories/PoolFactory.sol](https://github.com/Gearbox-protocol/permissionless/blob/master/contracts/factories/PoolFactory.sol) * [contracts/types/MarketData.sol](https://github.com/Gearbox-protocol/periphery-v3/blob/main/contracts/types/MarketData.sol)
## Compressors Source: https://docs.gearbox.finance/developers/compressors File: content/developers/compressors.mdx Compressor contracts aggregate on-chain data efficiently. Instead of dozens of individual reads, a single compressor call returns complete protocol state. ## Discovering Compressor Addresses Use AddressProvider to find compressor addresses: ### Solidity Usage ```solidity [address compressor] = addressProvider.getLatestAddressByContractType( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` ### TypeScript Usage ```typescript import { AP_MARKET_COMPRESSOR, VERSION_RANGE_310 } from '@gearbox-protocol/sdk'; const [compressor] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` *** ## MarketCompressor The primary data aggregation contract. Returns complete market state including pools, credit managers, and price oracles. ### Interface ```solidity interface IMarketCompressor { function getMarkets(MarketFilter memory filter) external view returns (MarketData[] memory); function getMarketData(address pool) external view returns (MarketData memory); function getPoolState(address pool) external view returns (PoolState memory); } ``` ### MarketFilter ```solidity struct MarketFilter { address[] configurators; // Filter by Risk Curator addresses address[] pools; // Filter by specific pool addresses address underlying; // Filter by underlying token (e.g., USDC) } ``` Pass empty arrays and `address(0)` for no filtering. ### Solidity Usage ```solidity import {IMarketCompressor} from "@gearbox-protocol/periphery-v3/contracts/interfaces/IMarketCompressor.sol"; import {MarketData, MarketFilter} from "@gearbox-protocol/periphery-v3/contracts/types/MarketData.sol"; IMarketCompressor compressor = IMarketCompressor(MARKET_COMPRESSOR_ADDRESS); // Get single market MarketData memory data = compressor.getMarketData(poolAddress); // Get all USDC markets MarketFilter memory filter = MarketFilter({ underlying: USDC, configurators: new address[](0), pools: new address[](0) }); MarketData[] memory usdcMarkets = compressor.getMarkets(filter); ``` ### TypeScript Usage ```typescript import { marketCompressorAbi } from '@gearbox-protocol/sdk'; // Get all markets const markets = await client.readContract({ address: compressorAddress, abi: marketCompressorAbi, functionName: 'getMarkets', args: [{ configurators: [], pools: [], underlying: '0x0000000000000000000000000000000000000000' }], }); // Get specific pool data const marketData = await client.readContract({ address: compressorAddress, abi: marketCompressorAbi, functionName: 'getMarketData', args: [poolAddress], }); ``` ### MarketData Structure ```solidity struct MarketData { PoolState pool; // Pool state and rates QuotaKeeperState quotaKeeper; // Quota limits and rates CreditSuiteData[] creditManagers; // All CMs for this market PriceOracleState priceOracle; // Oracle configuration TokenData[] tokens; // Allowed collateral tokens } ``` **Key fields in PoolState:** | Field | Type | Description | | -------------------- | --------- | --------------------- | | `baseParams.addr` | `address` | Pool contract address | | `availableLiquidity` | `uint256` | Borrowable liquidity | | `dieselRate` | `uint256` | Share price (RAY) | | `supplyRate` | `uint256` | Lender APY (RAY) | | `baseInterestRate` | `uint256` | Borrower APR (RAY) | | `totalAssets` | `uint256` | Total pool value | **Key fields in CreditSuiteData:** | Field | Type | Description | | -------------------- | ------------------- | ------------------------ | | `creditManager` | `address` | Credit Manager address | | `creditFacade` | `address` | Credit Facade address | | `creditConfigurator` | `address` | Configurator address | | `debtLimits` | `DebtLimits` | min/max debt per account | | `collateralTokens` | `CollateralToken[]` | Allowed tokens + LTs | *** ## CreditAccountCompressor Fetches credit account data with filtering and pagination. ### Interface ```solidity interface ICreditAccountCompressor { function getCreditAccounts( address creditManager, CreditAccountFilter memory filter, uint256 offset ) external view returns (CreditAccountData[] memory accounts, uint256 total); function countCreditAccounts( address creditManager, CreditAccountFilter memory filter ) external view returns (uint256); function getCreditAccountData( address creditManager, address creditAccount ) external view returns (CreditAccountData memory); } ``` ### CreditAccountFilter ```solidity struct CreditAccountFilter { address owner; // Filter by owner (address(0) = any) uint256 minHealthFactor; // Minimum HF (0 = no min) uint256 maxHealthFactor; // Maximum HF (type(uint256).max = no max) bool includeZeroDebt; // Include accounts with no debt bool reverting; // Include reverting accounts } ``` ### Solidity Usage ```solidity import {ICreditAccountCompressor} from "@gearbox-protocol/periphery-v3/contracts/interfaces/ICreditAccountCompressor.sol"; ICreditAccountCompressor compressor = ICreditAccountCompressor(compressorAddress); // Get accounts with low health factor (for liquidation) CreditAccountFilter memory filter = CreditAccountFilter({ owner: address(0), minHealthFactor: 0, maxHealthFactor: 10000, // HF < 1.0 includeZeroDebt: false, reverting: false }); (CreditAccountData[] memory accounts, uint256 total) = compressor.getCreditAccounts(creditManager, filter, 0); ``` ### TypeScript Usage ```typescript import { creditAccountCompressorAbi } from '@gearbox-protocol/sdk'; // Get all accounts with debt const [accounts, total] = await client.readContract({ address: compressorAddress, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManagerAddress, { owner: '0x0000000000000000000000000000000000000000', minHealthFactor: 0n, maxHealthFactor: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), includeZeroDebt: false, reverting: false, }, 0n, // offset ], }); console.log(`Found ${total} accounts, fetched ${accounts.length}`); ``` ### CreditAccountData Structure ```solidity struct CreditAccountData { address addr; // Credit Account address address owner; // Account owner address creditManager; // Parent Credit Manager uint256 debt; // Total debt (principal + interest) uint256 cumulativeIndexLastUpdate; uint256 cumulativeQuotaInterest; uint128 quotaFees; uint256 enabledTokensMask; // Bitmask of enabled tokens uint256 healthFactor; // Current HF (10000 = 1.0) TokenInfo[] tokens; // Token balances and values bool isLiquidatable; } ``` ### Pagination Large result sets are paginated. Use `offset` to fetch subsequent pages: ```typescript const PAGE_SIZE = 100n; let offset = 0n; let allAccounts: CreditAccountData[] = []; while (true) { const [accounts, total] = await compressor.read.getCreditAccounts([ creditManager, filter, offset, ]); allAccounts.push(...accounts); offset += BigInt(accounts.length); if (offset >= total) break; } ``` *** ## PriceFeedCompressor Aggregates price feed state for oracle updates. ### Interface ```solidity interface IPriceFeedCompressor { function getUpdatablePriceFeeds(address priceOracle) external view returns (PriceFeedData[] memory); function loadPriceFeedTree(address priceOracle, address token) external view returns (PriceFeedTreeNode memory); } ``` ### TypeScript Usage ```typescript // Get all feeds that need updating const feeds = await client.readContract({ address: priceFeedCompressor, abi: priceFeedCompressorAbi, functionName: 'getUpdatablePriceFeeds', args: [priceOracleAddress], }); // Filter for stale feeds const staleFeeds = feeds.filter(f => f.needsUpdate); ``` *** ## When to Use Compressors vs SDK | Scenario | Approach | | ---------------------- | --------------------------------------- | | General market data | SDK `marketRegister` | | Credit account queries | SDK services | | Custom filtering logic | Direct compressor calls | | Liquidation bots | Direct compressor (gas-optimized) | | On-chain integration | Direct compressor (no SDK in contracts) | | Real-time monitoring | Direct compressor with specific filters | The SDK uses compressors internally. Use direct compressor calls when you need: * Custom filter combinations not exposed by SDK * Pagination control * Gas-optimized queries for bots * On-chain access (Solidity contracts) *** ## Complete Example ### TypeScript ```typescript import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; import { marketCompressorAbi, creditAccountCompressorAbi, } from '@gearbox-protocol/sdk'; const client = createPublicClient({ chain: mainnet, transport: http(), }); async function getMarketOverview() { const marketCompressor = '0x...'; // From AddressProvider const markets = await client.readContract({ address: marketCompressor, abi: marketCompressorAbi, functionName: 'getMarkets', args: [{ configurators: [], pools: [], underlying: '0x0000000000000000000000000000000000000000' }], }); for (const market of markets) { console.log(`Pool: ${market.pool.baseParams.addr}`); console.log(` Available: ${market.pool.availableLiquidity}`); console.log(` Supply Rate: ${market.pool.supplyRate}`); } } async function findLiquidatableAccounts(creditManager: `0x${string}`) { const accountCompressor = '0x...'; // From AddressProvider const [accounts] = await client.readContract({ address: accountCompressor, abi: creditAccountCompressorAbi, functionName: 'getCreditAccounts', args: [ creditManager, { owner: '0x0000000000000000000000000000000000000000', minHealthFactor: 0n, maxHealthFactor: 10000n, // HF < 1.0 includeZeroDebt: false, reverting: false, }, 0n, ], }); return accounts.filter(a => a.isLiquidatable); } ``` ### Solidity ```solidity function getMarketData(address pool) external view returns (MarketData memory) { return IMarketCompressor(MARKET_COMPRESSOR).getMarketData(pool); } function findLiquidatableAccounts(address creditManager) external view returns (CreditAccountData[] memory) { CreditAccountFilter memory filter = CreditAccountFilter({ owner: address(0), minHealthFactor: 0, maxHealthFactor: 10000, includeZeroDebt: false, reverting: false }); (CreditAccountData[] memory accounts,) = ICreditAccountCompressor(ACCOUNT_COMPRESSOR).getCreditAccounts( creditManager, filter, 0 ); return accounts; } ``` ## Automated Insurance Source: https://docs.gearbox.finance/developers/automated-insurance File: content/developers/automated-insurance.mdx The insurance mechanism in Gearbox V3 functions as a **liquidity buffer** held in the `TreasurySplitter` contract. It acts as a first-loss protection layer to maintain pool solvency during bad debt events. ## How the Insurance Mechanism Works The "Insurance" is essentially a **retention policy** on the fees collected by the protocol. Instead of immediately distributing all profits to the Curator and the DAO, the `TreasurySplitter` holds a specific amount of tokens back as a safety buffer. **The Buffer Logic (`TreasurySplitter`)** The `TreasurySplitter` collects fees (from interest and standard liquidations). Before any of these fees can be claimed (distributed) by the admins, the contract checks if the current balance exceeds the `tokenInsuranceAmount`. * **If Balance < Insurance Amount:** The distribution fails/stops. All funds remain in the contract. * **If Balance > Insurance Amount:** Only the surplus (`balance - insuranceAmount`) is distributed. The `insuranceAmount` remains locked in the contract. This ensures that there is always a pile of liquid assets available to the protocol to cover unexpected losses before any profit-taking occurs. *** #### Understanding the Flow (Architecture) The following diagram shows how the assets are held and how the distribution logic protects the insurance amount: *** ## Covering Bad Debt (Liquidation Logic) When a "Bad Debt" liquidation occurs (where the collateral value is less than the debt), the system must cover the deficit to keep the lenders whole. This happens in the `PoolV3` contract, interacting with the protocol's Treasury balance. 1. **Liquidation with Loss:** A liquidator calls `liquidateCreditAccount`. They sell the collateral, but the proceeds are insufficient to repay the full debt. 2. **Reporting the Loss:** The Credit Manager calls `pool.repayCreditAccount(..., profit, loss)`. 3. **Burning the Buffer:** The Pool burns **LP Shares** held by the Treasury address to cover the loss. **Critical Link:** The `TreasurySplitter` accumulates the *pool's LP tokens*. The assets held in the `TreasurySplitter` can be used to **re-deposit** into the Pool effectively using the insurance buffer to pay for the bad debt. *** > For the economic model behind insurance reserves and first-loss capital mechanics, see [Insurance and Solvency Reserves](https://github.com/de-snake/docs-knowledge/blob/new-docs-dev-1/new-docs-about/economics-and-risk/insurance-and-solvency-reserves.md). *** ## Reading Insurance State On-Chain Query the `TreasurySplitter` contract to verify insurance health for a specific pool. ### 1. Check Insurance Target (The Floor) The minimum amount retained before any profit distribution: #### Solidity ```solidity uint256 target = treasurySplitter.tokenInsuranceAmount(token); ``` #### TypeScript ```typescript import { getContract } from 'viem'; import { iTreasurySplitterAbi } from '@gearbox-protocol/sdk'; const treasurySplitter = getContract({ address: treasurySplitterAddress, abi: iTreasurySplitterAbi, client: publicClient }); const insuranceTarget = await treasurySplitter.read.tokenInsuranceAmount([tokenAddress]); ``` ### 2. Check Current Buffer Compare actual balance to target to determine insurance status: #### Solidity ```solidity uint256 target = treasurySplitter.tokenInsuranceAmount(token); uint256 balance = IERC20(token).balanceOf(address(treasurySplitter)); bool fullyInsured = balance >= target; uint256 surplus = balance > target ? balance - target : 0; ``` #### TypeScript ```typescript import { erc20Abi } from 'viem'; // Get target and current balance const target = await treasurySplitter.read.tokenInsuranceAmount([tokenAddress]); const balance = await publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [treasurySplitterAddress] }); const fullyInsured = balance >= target; const surplus = balance > target ? balance - target : 0n; ``` ### 3. Monitor Governance Changes Insurance parameter changes require dual signatures (Curator + DAO): #### TypeScript ```typescript // Get pending proposals that may affect insurance const proposals = await treasurySplitter.read.activeProposals(); // Each proposal contains: proposer, target, calldata, executed status for (const proposal of proposals) { console.log('Pending proposal:', proposal); } ``` ### 4. Query Fee Distribution Config Understand where surplus fees are distributed: #### TypeScript ```typescript // Get default split configuration const defaultSplit = await treasurySplitter.read.defaultSplit(); // Or token-specific split const tokenSplit = await treasurySplitter.read.tokenSplits([tokenAddress]); // Returns: { receivers: address[], proportions: uint256[] } ```
Sources * [contracts/market/TreasurySplitter.sol](https://github.com/Gearbox-protocol/permissionless/blob/master/contracts/market/TreasurySplitter.sol) * [contracts/interfaces/ITreasurySplitter.sol](https://github.com/Gearbox-protocol/permissionless/blob/master/contracts/interfaces/ITreasurySplitter.sol) * [contracts/credit/CreditManagerV3.sol](https://github.com/Gearbox-protocol/core-v3/blob/main/contracts/credit/CreditManagerV3.sol)
## Gearbox Permissionless Overview Source: https://docs.gearbox.finance/developers/gp-overview File: content/developers/gp-overview.mdx Gearbox Permissionless is the governance and infrastructure layer that makes the entire protocol trustless, verifiable, and permissionlessly extensible across chains. It is implemented as a collection of smart contracts that define roles, boundaries, and processes — ensuring no single entity can access user funds or override market parameters. The code is open source at [github.com/Gearbox-protocol/permissionless](https://github.com/Gearbox-protocol/permissionless). ## Design Principles - **Non-Interference with Decisions** — the DAO cannot influence or override decisions made by Market Curators or Instance Owners - **No Control Over Market Contracts** — market parameters allow flexible modification by Curators, but the DAO cannot alter them - **Exclusive Control Over System Contract Versions** — only the DAO can authorize new versions of system contracts (core protocol logic). Adding adapters, price feeds, bots, or other components remains permissionless. - **Chain Expansion Oversight** — only the DAO can activate Gearbox on new chains, ensuring the correct Treasury address and Instance Owner multisig are set ## Core Components | Component | Purpose | | --- | --- | | [**Contract Types & Versioning**](https://docs.gearbox.finance/developers/gp-contract-types) | Type system for all protocol contracts. Domain-based organization, semantic versioning, module replacement (not proxies) | | [**Cross-Chain Multisig (CCM)**](https://docs.gearbox.finance/developers/gp-ccm) | Governance synchronization across chains. Hash-linked batches, strict sequential ordering | | [**Bytecode Repository (BCR)**](https://docs.gearbox.finance/developers/gp-bcr) | On-chain registry of audited bytecode. System vs public domains, dual-signature verification | | [**Instances**](https://docs.gearbox.finance/developers/gp-instances) | Per-chain protocol replicas. DAO-activated, managed by Instance Owner | | [**PriceFeed Store**](https://docs.gearbox.finance/developers/gp-pricefeed-store) | Token-to-oracle marketplace. Instance Owner managed, curator-consumed | | [**Treasury & Insurance**](https://docs.gearbox.finance/developers/gp-treasury) | Fee distribution (curator/DAO split) and loss coverage fund | ## Roles The protocol defines a clear hierarchy of roles, each with strictly bounded authority enforced at the smart contract level: | Role | Scope | Can Affect Users | | --- | --- | --- | | DAO (token holders) | All chains | No | | Technical Multisig | All chains | No | | Auditors | All chains | No | | Instance Owners | One chain | No | | Financial Representatives | One chain | No | | Market Curators | Market | Yes (with timelock) | | Emergency Admin | Market | Yes (immediate) | | Pausable/Unpausable Admins | Market | Yes | | Emergency Liquidators | Market | Yes | See [Roles & Permissions](https://docs.gearbox.finance/developers/gp-roles) for detailed authority tables. ## What's in This Section - [**Concepts**](https://docs.gearbox.finance/developers/gp-concepts) — How each component works - [**Building**](https://docs.gearbox.finance/developers/gp-building) — Create adapters, integrate protocols, extend the core ## Concepts Source: https://docs.gearbox.finance/developers/gp-concepts File: content/developers/gp-concepts.mdx The Gearbox Permissionless infrastructure is built from a set of interlocking components. Each enforces specific boundaries at the smart contract level — no role can exceed its authority, even if compromised. - [**Contract Types & Versioning**](https://docs.gearbox.finance/developers/gp-contract-types) — The type system (`contractType`, domains, `::` notation) and semantic versioning that organize all protocol contracts. Upgrades via module replacement, not proxies. - [**Cross-Chain Multisig (CCM)**](https://docs.gearbox.finance/developers/gp-ccm) — The governance synchronization layer. Hash-linked batches originate on Mainnet and propagate to all chains in strict sequential order. - [**Bytecode Repository (BCR)**](https://docs.gearbox.finance/developers/gp-bcr) — On-chain registry of audited contract bytecode. System domains require DAO approval; public domains (adapters, price feeds, IRMs) are permissionless after audit. - [**Instances**](https://docs.gearbox.finance/developers/gp-instances) — Per-chain protocol replicas. DAO-activated, managed by an Instance Owner multisig. One instance per chain. - [**PriceFeed Store**](https://docs.gearbox.finance/developers/gp-pricefeed-store) — A marketplace linking tokens to approved price feeds. Managed by Instance Owners, consumed by Market Curators. - [**Treasury & Insurance**](https://docs.gearbox.finance/developers/gp-treasury) — Fee distribution between curators and the DAO via TreasurySplitter, plus an insurance fund for loss coverage. - [**Roles & Permissions**](https://docs.gearbox.finance/developers/gp-roles) — Complete role hierarchy: DAO, Technical Multisig, Auditors, Instance Owners, Financial Representatives, Market Curators, Emergency Admin, and more. Each with explicit authority boundaries. ## Contract Types & Versioning Source: https://docs.gearbox.finance/developers/gp-contract-types File: content/developers/gp-contract-types.mdx Every contract in Gearbox is identified by a `contractType` (bytes32) and a `version` (uint256), enabling precise classification and safe modular upgrades without proxies. ## Domains and Contract Types Contract types are organized into **domains** based on functionality. Domains and subtypes are separated by `::` notation. | Domain | Example Types | Description | | --- | --- | --- | | `IRM` | `IRM::FIXED`, `IRM::LINEAR_MODEL` | Interest rate models | | `PRICE_FEED` | `PRICE_FEED::CHAINLINK`, `PRICE_FEED::REDSTONE` | Price feed implementations | | `ADAPTER` | `ADAPTER::UNISWAP_V3`, `ADAPTER::CURVE` | Protocol integration adapters | | `PHANTOM_TOKEN` | `PHANTOM_TOKEN::STETH` | Phantom token wrappers | Contract types within the same domain share the same programming interface, but their configurations may differ. For example, `IRM::FIXED` and `IRM::LINEAR_MODEL` both implement the interest rate model interface, but accept different constructor parameters. ## Version Control Versions are encoded as `uint256` integers (e.g., `3_10` represents version 3.10). The versioning scheme uses three levels: | Level | When to Use | Compatibility | | --- | --- | --- | | **Major** | Significant interface changes | Breaking — not backward compatible | | **Minor** | Minor interface changes | May require integration updates | | **Patch** | Same interface, no behavioral changes | Fully backward compatible | ## Module Replacement, Not Proxies Gearbox upgrades contracts through **module replacement** rather than proxy patterns. When a new version is available: - The new contract is deployed as a standalone module - The old module is swapped out for the new one - No proxy delegation or storage layout concerns This approach eliminates the risks associated with proxy-based upgrades (storage collisions, delegatecall vulnerabilities) while keeping the system modular. ## Upgrade Rules Two critical constraints govern upgrades: 1. **Patched versions only** — updates are only permissible via patched versions of an existing contract type, ensuring interface stability 2. **No forced updates** — system upgrades are managed exclusively by market curators. The DAO cannot force a curator to adopt a new contract version Even when a new version passes audit and DAO approval, it remains opt-in. Market curators deliberately choose whether and when to update their deployments. ## Learn More - [Bytecode Repository (BCR)](https://docs.gearbox.finance/developers/gp-bcr) — How contract bytecode is stored, audited, and approved - [Cross-Chain Multisig (CCM)](https://docs.gearbox.finance/developers/gp-ccm) — How governance decisions propagate across chains - [Instances](https://docs.gearbox.finance/developers/gp-instances) — How per-chain deployments operate ## Guardrails & Role Separation Source: https://docs.gearbox.finance/developers/gp-guardrails File: content/developers/gp-guardrails.mdx Most systems fail when one role has too much power. Gearbox achieves permissionless market operation by limiting power rather than adding trust. ## Three Governance Layers ### 1. Protocol DAO (Global) Manages the codebase and high-level protocol parameters across all chains. - Approves new contract versions for the [Bytecode Repository](https://docs.gearbox.finance/developers/gp-bcr) - Authorizes chain deployments - Manages GEAR token incentives - **Cannot** alter curator parameters or access user funds ### 2. Instance Owner (Per-Chain) A technical multisig on each chain that maintains infrastructure. - Manages the Price Feed Store (whitelists oracle feeds) - Executes protocol upgrades on the specific chain - Operates on a **neutral principle**: verifies technical correctness, not financial viability - Composed of diverse ecosystem participants (curators, chain foundation, auditors, core team) ### 3. Market Curators (Per-Market) Independent operators of specific lending markets. - Set risk parameters: liquidation thresholds, adapters, fees, debt limits - Non-custodial — manage *parameters*, not *funds* - Subject to hardcoded safety constraints (see below) ## The Ramping Mechanism Rather than allowing instant parameter changes, Gearbox enforces **linear ramping** for critical updates: - Liquidation threshold changes interpolate linearly over a **minimum 24-hour period** - This minimum is **hardcoded and immutable** — not even curators can bypass it - Prevents sudden liquidation cascades or market manipulation via parameter changes - Users have time to react and adjust positions before new parameters take full effect ## Emergency Toolset Curators have access to immediate intervention capabilities for genuine emergencies: | Emergency Action | Effect | Constraint | | --- | --- | --- | | Prohibit a token | Immediately blocks a risky collateral token | Instant | | Adjust interest rates | Change rate curves to manage liquidity | Instant | | Increase liquidation thresholds | Make accounts safer (never riskier) | Instant | | Switch oracle feeds | Switch to an alternative approved feed | Feed must have been in catalog for 2+ days | The oracle switching constraint is critical: even during emergencies, newly added oracle feeds must have existed in the on-chain catalog for at least 2 days before they can be deployed. This prevents an attacker from adding a malicious oracle and immediately switching to it. ## Key Design Principle Even if governance were compromised, it cannot directly change user positions or interfere with curated markets. Each layer's power is bounded by smart contract logic, not by trust in the operators. ## Learn More - [Bytecode Repository (BCR)](https://docs.gearbox.finance/developers/gp-bcr) — How code verification prevents unaudited deployments - [Cross-Chain Multisig (CCM)](https://docs.gearbox.finance/developers/gp-ccm) — How governance decisions synchronize across chains ## Bytecode Repository (BCR) Source: https://docs.gearbox.finance/developers/gp-bcr File: content/developers/gp-bcr.mdx The Bytecode Repository is an on-chain registry that stores the bytecode of all contracts used in the Gearbox Protocol, with the exception of external tokens, price feeds, and external protocol contracts. Every entry is identified by a `contractType` and `version`. ## System vs Public Domains Contract domains are classified into two categories that determine the approval flow required before a contract can be used in production. | Category | Domains | Approval Required | | --- | --- | --- | | **Public** (permissionless) | `PRICE_FEED::`, `ADAPTER::`, `IRM::`, `PHANTOM_TOKEN::` | Audit signature only | | **System** | All other domains | Audit signature + DAO vote | Public domains allow anyone to contribute new contract types after passing an audit. System domains require an additional DAO governance vote, reflecting their higher impact on protocol integrity. ## Adding Contracts to BCR Any EOA can upload contract bytecode to the BCR via an EIP-712 signature. However, uploading alone does not put a contract into production. The approval flow differs by domain: ### Public Domain Flow 1. Developer uploads contract bytecode to BCR 2. Auditor reviews and signs the bytecode hash 3. Contract becomes available for use by market curators ### System Domain Flow 1. Developer uploads contract bytecode to BCR 2. Auditor reviews and signs the bytecode hash 3. DAO votes to approve the contract 4. Contract becomes available for use by market curators In both cases, the final step is always curator adoption — even with full approval, market curators must deliberately choose to deploy or update to the new contract. ## ContractType Cybersquatting Protection Once a `contractType` has been audited, it is permanently assigned to the original author. This prevents malicious actors from registering a contract under someone else's type identifier. ## Version Mechanics The BCR enforces a strict separation between approval and deployment: - Audit + DAO approval (for system domains) makes a contract **available** - Market curators must **deliberately choose** to update to the new version - No contract is automatically pushed into production This ensures curators retain full control over which contract versions run in their markets. ## Bytecode Requirements All contracts submitted to the BCR must be compiled with the following settings: | Parameter | Required Value | | --- | --- | | Solidity version | `0.8.23` | | EVM target | `shanghai` | | Optimizer runs | `1000` | | Bytecode hash | `none` | These requirements ensure deterministic compilation and consistent bytecode verification across environments. ## Learn More - [Contract Types & Versioning](https://docs.gearbox.finance/developers/gp-contract-types) — How contract types and domains are structured - [Cross-Chain Multisig (CCM)](https://docs.gearbox.finance/developers/gp-ccm) — How governance decisions propagate across chains - [Instances](https://docs.gearbox.finance/developers/gp-instances) — How per-chain deployments operate ## Cross-Chain Multisig (CCM) Source: https://docs.gearbox.finance/developers/gp-ccm File: content/developers/gp-ccm.mdx The Cross-Chain Multisig (CCM) is the core contract that manages DAO governance across all connected networks. It is deployed at the same address on every chain: `0xCCCCcCCc42B7DA9fdEc1761698Fb55fdD41CDF55`. ## Batch Voting System DAO decisions are grouped into **batches**. Token holders vote on each batch, and approved batches are executed across all chains. Batches are connected by hashes in a chain-like structure — each batch references its predecessor. This creates an immutable, ordered history of governance decisions with strict sequential ordering across all EVM chains. ``` Batch N-1 (hash: 0xabc...) └──▶ Batch N (prev: 0xabc..., hash: 0xdef...) └──▶ Batch N+1 (prev: 0xdef..., hash: 0x123...) ``` This sequential ordering guarantees that every chain processes governance decisions in the exact same order, preventing conflicts or state divergence. ## Propagation Mechanisms Once a batch is approved, it must reach every connected chain. CCM supports two transport mechanisms: | Mechanism | Description | | --- | --- | | **Cross-chain multisig signing** | Signers execute the batch directly on each target chain | | **Message bridging** | Batch is relayed through a cross-chain messaging bridge | Both mechanisms deliver the same deterministic batch payload, ensuring identical execution regardless of transport. ## Recovery Mode In edge cases where a batch exceeds the block size limit on a specific chain, CCM provides **rescue batches**. These allow the affected chain to process the oversized batch through an alternative execution path, preventing a single chain's constraints from blocking governance across the entire network. ## Current vs Future Governance | Aspect | Current Implementation | Future Implementation | | --- | --- | --- | | **Decision origin** | Technical multisig signs on Mainnet | Fully on-chain governance contract | | **Batch creation** | Multisig uploads batches to CCM | Governance contract creates batches directly | | **Trust model** | Trusted multisig signers | Trustless on-chain voting | The CCM contract itself does not change between phases — only the entity that submits batches transitions from a multisig to an on-chain governance contract. ## Learn More - [Bytecode Repository (BCR)](https://docs.gearbox.finance/developers/gp-bcr) — How contract bytecode is stored and approved - [Instances](https://docs.gearbox.finance/developers/gp-instances) — How per-chain deployments are activated and managed - [Contract Types & Versioning](https://docs.gearbox.finance/developers/gp-contract-types) — How contracts are classified and versioned ## Instances Source: https://docs.gearbox.finance/developers/gp-instances File: content/developers/gp-instances.mdx An Instance is a complete, functional replica of the Gearbox Protocol on a specific chain. Each chain supports a single Instance, which must be approved by DAO vote before activation. ## Instance Properties | Property | Description | | --- | --- | | **One per chain** | Each blockchain has exactly one Gearbox Instance | | **DAO-approved** | Activation requires a DAO vote that validates the native token address and assigns governance roles | | **Independent operation** | Core functions (lending, borrowing, liquidations) operate without cross-chain dependencies | | **Immutable ownership** | Once set, the Instance Owner cannot be changed — not even by the DAO | ## Permissionless Deployment Anyone can deploy a new Instance via the Gearbox Permissionless Interface. However, deployment alone does not make an Instance operational — it requires **activation** through the DAO. ## Activation Process During activation, the DAO performs the following steps: 1. **Validates the native token address** — confirms the wrapped native token for the target chain 2. **Assigns the Financial Multisig** — designates the multisig responsible for financial operations 3. **Designates the Instance Owner** — assigns the Instance Owner multisig that will manage chain-level infrastructure Once activation is complete, the Instance Owner multisig manages the Instance. This assignment is permanent and cannot be revoked or reassigned by any party, including the DAO. ## Instance Owner Role The Instance Owner is a multisig that manages chain-specific infrastructure for its Instance. Its responsibilities include maintaining price feed allowlists, managing technical chain parameters, and ensuring the Instance operates correctly within its local ecosystem. The Instance Owner does not control market-level parameters — those are managed by individual market curators. ## Learn More - [Cross-Chain Multisig (CCM)](https://docs.gearbox.finance/developers/gp-ccm) — How governance decisions reach each Instance - [Bytecode Repository (BCR)](https://docs.gearbox.finance/developers/gp-bcr) — How contract bytecode is verified before deployment - [Contract Types & Versioning](https://docs.gearbox.finance/developers/gp-contract-types) — How contracts are classified and versioned ## PriceFeed Store Source: https://docs.gearbox.finance/developers/gp-pricefeed-store File: content/developers/gp-pricefeed-store.mdx The PriceFeed Store (PFS) is a marketplace that links tokens with their approved price feeds, serving as the canonical registry that market curators draw from when configuring markets. ## Purpose Price feeds are critical infrastructure — a malicious or misconfigured feed can directly threaten user funds. The PFS exists to provide a vetted catalog of token-to-feed mappings, reducing the risk of both intentional attacks and accidental misconfiguration. | Goal | How PFS Achieves It | | --- | --- | | **Prevent malicious feeds** | Only Instance Owners can add or update feed entries | | **Mitigate misconfiguration** | Curators select from pre-approved options rather than supplying arbitrary addresses | | **Maintain neutrality** | Instance Owners must remain impartial regarding price feed brands | ## How It Works Market curators do not interact with the PFS directly at runtime. Instead, they select assets and feeds from the PFS when composing a market configuration transaction. Once that transaction executes, the market's oracle references are set independently of the PFS. ```mermaid flowchart LR IO[Instance Owner] -->|"approves feeds"| PFS[PriceFeed Store] PFS -->|"curators select from"| TX[Configuration Transaction] TX -->|"sets oracles in"| Market[Market] ``` ### Key Properties - **Snapshot, not live link** — after a market configurator transaction executes, the market no longer relies on the PFS. Removing an asset or feed from the PFS will not affect any existing market. - **Instance Owner controlled** — only the Instance Owner multisig can add, update, or remove entries. The DAO cannot modify PFS contents directly. - **Approval, not endorsement** — Instance Owners approve feeds as valid (not malicious), but are not responsible for the correctness of the underlying price data. ## Management | Action | Who Can Perform It | | --- | --- | | Add a token/feed mapping | Instance Owner | | Update an existing mapping | Instance Owner | | Remove a mapping | Instance Owner | | Use an approved feed in a market | Market Curator | ## Fixed Address The PFS has a deterministic, fixed address on every chain where Gearbox is deployed. The current registry of instances and their addresses is available at: [https://permissionless.gearbox.foundation/instances](https://permissionless.gearbox.foundation/instances) ## Learn More - [Roles & Permissions](https://docs.gearbox.finance/developers/gp-roles) — Full breakdown of Instance Owner authority and other governance roles - [Omni-EVM & Instances](https://docs.gearbox.finance/developers/gp-instances) — How per-chain instances operate independently - [Treasury & Insurance](https://docs.gearbox.finance/developers/gp-treasury) — Fee collection and distribution mechanics ## Building on Permissionless Source: https://docs.gearbox.finance/developers/gp-building File: content/developers/gp-building.mdx Gearbox's permissionless architecture is designed for extensibility. You can build new components that plug into the system without requiring governance approval for the integration itself. ## What You Can Build | Extension Type | Description | Guide | | --- | --- | --- | | **Adapter** | Add support for a new DeFi protocol (DEX, vault, staking) so Credit Accounts can interact with it | [Adapter Development](https://docs.gearbox.finance/developers/gp-adapters) | | **Protocol Integration** | Compose with Credit Accounts from your own smart contracts — build strategies, keepers, or automated managers | [Protocol Integration](https://docs.gearbox.finance/developers/gp-integration) | | **Core Extension** | Extend the protocol itself — custom interest rate models, pool hooks, Credit Manager spokes | [Core Extension](https://docs.gearbox.finance/developers/gp-extension) | ## How It Fits Together ```mermaid flowchart LR subgraph "Your Code" Adapter[New Adapter] Integration[Protocol Integration] Extension[Core Extension] end subgraph "Gearbox Protocol" CM[Credit Manager] Pool[Pool] Facade[Credit Facade] end subgraph "External DeFi" DEX[New DEX] Vault[New Vault] end Adapter -->|"wraps"| DEX Adapter -->|"wraps"| Vault CM -->|"calls via"| Adapter Integration -->|"multicall"| Facade Extension -->|"extends"| Pool Extension -->|"extends"| CM ``` ## Approval Flow While building is permissionless, deploying to production uses the BCR verification process: 1. **Build** your adapter/integration/extension 2. **Test** with Foundry or Hardhat against forked mainnet 3. **Submit** for audit 4. **Register** in the [Bytecode Repository](https://docs.gearbox.finance/developers/gp-bcr) with dual signatures 5. **Curators enable** your component in their Credit Suites Steps 1-2 are fully permissionless. Steps 3-5 ensure production safety. ## Treasury & Insurance Source: https://docs.gearbox.finance/developers/gp-treasury File: content/developers/gp-treasury.mdx The Treasury Splitter contract governs how protocol fees are divided between market curators and the Gearbox DAO, while the insurance fund provides a minimal reserve to cover potential losses. ## Treasury Splitter Every market generates fees from borrower interest. These fees are collected and split between two parties: the market curator who manages risk, and the DAO that maintains the protocol. | Property | Detail | | --- | --- | | **Default split** | 50/50 between market curator and DAO | | **Governance model** | Operates as a 2/2 multisig — changes require approval from both the Financial Representative and the market curator | | **Fee collection timing** | Fees are collected when debt is repaid, not continuously | | **Split change timing** | Changes to the treasury split take effect immediately, including for previously accrued but uncollected fees | ```mermaid flowchart LR Borrower[Borrower Repays] -->|"fees collected"| Splitter[Treasury Splitter] Splitter -->|"50%"| Curator[Market Curator] Splitter -->|"50%"| DAO[DAO Treasury] FR[Financial Representative] -->|"manages split"| Splitter ``` ### Delayed Fee Collection Fees are not streamed or collected in real time. They accumulate within the protocol and are only realized when a borrower repays their debt. This means: - The split ratio at the time of collection determines distribution, not the ratio at the time fees were accrued - Changing the split retroactively affects all uncollected fees - Both parties should be aware of this when negotiating split changes ## Financial Representative Each chain has a Financial Representative appointed by the DAO to manage fee-related operations on that specific chain. | Aspect | Detail | | --- | --- | | **Scope** | One chain | | **Appointment** | Set during instance activation | | **Replacement** | Changed via DAO vote | | **User impact** | Cannot affect end users directly | ### Financial Representative Authority | Action | Function | Description | | --- | --- | --- | | Set default split | `setDefaultSplit()` | Change the default fee division ratio | | Set token-specific split | `setTokenSplit()` | Override the split for a specific token | | Set insurance amount | `setTokenInsuranceAmount()` | Configure the undistributable reserve per token | | Withdraw tokens | `withdrawToken()` | Withdraw collected fees from the splitter | All of these actions require approval from **both** the Financial Representative and the relevant market curator (2/2 multisig). ## Insurance Fund The Treasury Splitter holds a minimal undistributable amount as an insurance reserve. This fund exists to cover potential losses and cannot be withdrawn through normal fee collection. | Property | Detail | | --- | --- | | **Purpose** | Cover protocol losses | | **Configuration** | Set per token via `setTokenInsuranceAmount()` | | **Accessibility** | Cannot be withdrawn through standard fee collection | | **Management** | Requires 2/2 approval (Financial Representative + market curator) to modify | ## Learn More - [Roles & Permissions](https://docs.gearbox.finance/developers/gp-roles) — Full breakdown of Financial Representative and other governance roles - [PriceFeed Store](https://docs.gearbox.finance/developers/gp-pricefeed-store) — How price feeds are approved and managed - [Omni-EVM & Instances](https://docs.gearbox.finance/developers/gp-instances) — How per-chain instances operate independently ## Adapter Development Source: https://docs.gearbox.finance/developers/gp-adapters File: content/developers/gp-adapters.mdx Build adapters to integrate new DeFi protocols with Gearbox Credit Accounts. ## When to Build an Adapter Build an adapter when you want to enable Credit Accounts to interact with a new DeFi protocol. Consider these options: | Approach | When to Use | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | | **Build an Adapter** | The protocol is mature, has significant TVL, and you want it available across all Gearbox Credit Managers | | **Protocol Integration** | You're building a protocol that wants to accept Credit Accounts as users (e.g., a DEX accepting leveraged trades) | | **Direct Contract** | Your use case doesn't need leverage or margin trading features | Adapters make sense for: * DEX protocols (Uniswap, Curve, Balancer) * Yield vaults (Yearn, ERC-4626 vaults) * Liquid staking protocols (Lido, Rocket Pool) * Lending protocols (Aave, Compound) Do not build adapters for: * Protocols with admin keys that can rug users * Protocols without audits or battle-testing * Highly experimental or unproven contracts ## Architecture Overview Adapters sit between Credit Accounts and external protocols: ``` User -> CreditFacade -> CreditAccount -> Adapter -> Target Protocol | | | +-> Security enforcement | +-> Token state updates | +-> Approval management | +-> Receives output tokens ``` The adapter's job is to: 1. Accept calls from the CreditFacade (not users directly) 2. Translate the call to the target protocol's interface 3. Override recipient addresses to always be the Credit Account 4. Manage token approvals safely 5. Update token states (enable outputs, optionally disable inputs) For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Building Your First Adapter ### Step 1: Inherit AbstractAdapter All adapters extend `AbstractAdapter`: ```solidity import {AbstractAdapter} from "@gearbox-protocol/core-v3/contracts/adapters/AbstractAdapter.sol"; contract MyVaultAdapter is AbstractAdapter { constructor( address _creditManager, address _targetContract ) AbstractAdapter(_creditManager, _targetContract) {} } ``` **Example:** ```solidity // Adapter for an ERC-4626 vault contract ERC4626Adapter is AbstractAdapter { constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) {} } ``` ### Step 2: Implement Core Functions Add wrapper functions that call the target protocol: ```solidity function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); // Get token addresses address asset = IERC4626(targetContract).asset(); address vault = targetContract; // Build the call bytes memory callData = abi.encodeCall( IERC4626.deposit, (assets, creditAccount) // Override recipient to creditAccount ); // Execute with safe approval pattern shares = abi.decode( _executeSwapSafeApprove(asset, vault, callData, false), (uint256) ); } ``` **Example:** ```solidity // Withdraw function - burn shares, receive assets function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); address vault = targetContract; address asset = IERC4626(vault).asset(); bytes memory callData = abi.encodeCall( IERC4626.redeem, (shares, creditAccount, creditAccount) ); assets = abi.decode( _executeSwapSafeApprove(vault, asset, callData, false), (uint256) ); } ``` ### Step 3: Implement Diff Functions Diff functions calculate the input amount from the current balance: ```solidity // Standard: requires exact amount function deposit(uint256 assets) external creditFacadeOnly returns (uint256); // Diff: calculates amount as (balance - leftoverAmount) function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256); ``` **Example:** ```solidity function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); // Calculate actual deposit amount uint256 balance = IERC20(asset).balanceOf(creditAccount); uint256 amount = balance - leftoverAmount; // Call the standard deposit function return deposit(amount); } ``` **Why diff functions exist:** When chaining operations, you often don't know exact amounts: * After a swap, you don't know exact output until execution * After partial withdrawals, remaining balance is variable * Diff functions say "use everything except X" instead of "use exactly Y" ### Step 4: Add Security Modifiers Every external function must use `creditFacadeOnly`: ```solidity // WRONG - allows anyone to call function deposit(uint256 amount) external returns (uint256) { ... } // CORRECT - only CreditFacade can call function deposit(uint256 amount) external creditFacadeOnly returns (uint256) { ... } ``` **Example:** ```solidity // All user-facing functions need the modifier function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { ... } function depositDiff(uint256 leftover) external creditFacadeOnly returns (uint256 shares) { ... } function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { ... } function redeemDiff(uint256 leftover) external creditFacadeOnly returns (uint256 assets) { ... } ``` ## Complete Example: ERC-4626 Vault Adapter Here's a complete adapter for an ERC-4626 vault with all essential patterns: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {AbstractAdapter} from "@gearbox-protocol/core-v3/contracts/adapters/AbstractAdapter.sol"; import {AdapterType} from "@gearbox-protocol/core-v3/contracts/interfaces/IAdapter.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ERC4626Adapter is AbstractAdapter { AdapterType public constant override _gearboxAdapterType = AdapterType.VAULT; constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) {} // Deposit assets, receive shares function deposit(uint256 assets) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); bytes memory callData = abi.encodeCall( IERC4626.deposit, (assets, creditAccount) ); shares = abi.decode( _executeSwapSafeApprove(asset, targetContract, callData, false), (uint256) ); } // Deposit all assets except leftoverAmount function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 shares) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); uint256 balance = IERC20(asset).balanceOf(creditAccount); return deposit(balance - leftoverAmount); } // Redeem shares, receive assets function redeem(uint256 shares) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); address asset = IERC4626(targetContract).asset(); bytes memory callData = abi.encodeCall( IERC4626.redeem, (shares, creditAccount, creditAccount) ); assets = abi.decode( _executeSwapSafeApprove(targetContract, asset, callData, false), (uint256) ); } // Redeem all shares except leftoverAmount function redeemDiff(uint256 leftoverAmount) external creditFacadeOnly returns (uint256 assets) { address creditAccount = _creditAccount(); uint256 balance = IERC20(targetContract).balanceOf(creditAccount); return redeem(balance - leftoverAmount); } } ``` ## Security Patterns Deep Dive ### Why creditFacadeOnly Matters Without this modifier, an attacker could call adapter functions directly: **Attack scenario without creditFacadeOnly:** 1. Attacker calls `adapter.deposit(1000)` directly 2. Adapter tries to pull tokens from "current" Credit Account 3. Without CreditFacade context, `_creditAccount()` returns address(0) or wrong account 4. Tokens could be pulled from wrong account or transaction reverts unpredictably **With creditFacadeOnly:** 1. Only CreditFacade can call the adapter 2. CreditFacade sets the active Credit Account before calling 3. Adapter correctly identifies which account to operate on 4. No unauthorized access possible ```solidity // The modifier checks that msg.sender is the CreditFacade modifier creditFacadeOnly() { if (msg.sender != creditFacade) revert CallerNotCreditFacadeException(); _; } ``` ### Recipient Override Pattern Always hardcode the recipient as the Credit Account. Never accept user-specified recipients. **Attack scenario without recipient override:** ```solidity // DANGEROUS - user controls recipient function withdraw(uint256 shares, address recipient) external creditFacadeOnly { IVault(targetContract).redeem(shares, recipient, _creditAccount()); } // Attacker multicall: // 1. Deposit 100 ETH to vault // 2. Call withdraw(shares, attackerAddress) // 3. Funds sent to attacker instead of Credit Account // 4. Credit Account has no collateral, but debt remains ``` **Safe pattern:** ```solidity // CORRECT - recipient is always the Credit Account function withdraw(uint256 shares) external creditFacadeOnly { address creditAccount = _creditAccount(); IVault(targetContract).redeem(shares, creditAccount, creditAccount); } ``` ### Safe Approval Pattern Approvals are reset to 1 (not 0) after each operation. This prevents: * Approval racing attacks * Gas waste from 0 -> N transitions * Front-running of approval transactions ```solidity // _executeSwapSafeApprove does this internally: // 1. Approve tokenIn to target contract // 2. Execute the call // 3. Set approval to 1 (not 0) // 4. Enable tokenOut // 5. Optionally disable tokenIn if balance is 1 wei _executeSwapSafeApprove( tokenIn, // Token being spent tokenOut, // Token being received callData, // The encoded call false // Don't disable tokenIn if non-zero balance remains ); ``` **Why reset to 1 instead of 0:** * ERC-20 standard: 0 -> N costs more gas than 1 -> N * Prevents approval race conditions * Future operations don't need expensive zero-to-nonzero transition ### Token State Management Adapters automatically update which tokens are "enabled" on the Credit Account: ```solidity // After a swap USDC -> WETH: // - WETH mask is enabled (counts toward collateral) // - USDC mask disabled if balance = 1 wei and disableTokenIn = true // If disableTokenIn = false: // - USDC remains enabled even with low balance // - Useful for partial swaps or when you'll receive more USDC later ``` ## Testing with Foundry Test adapters using Foundry fork tests: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {Test} from "forge-std/Test.sol"; import {ERC4626Adapter} from "../adapters/ERC4626Adapter.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; contract ERC4626AdapterTest is Test { ERC4626Adapter adapter; address creditManager = 0x...; // Existing CM on mainnet address vault = 0x...; // Target vault function setUp() public { // Fork mainnet vm.createSelectFork(vm.envString("ETH_RPC_URL")); // Deploy adapter adapter = new ERC4626Adapter(creditManager, vault); } function test_deposit() public { // Setup: get a credit account with assets address creditAccount = _openCreditAccount(); // Execute deposit via CreditFacade uint256 assets = 100e18; uint256 shares = adapter.deposit(assets); // Assertions assertGt(shares, 0, "Should receive shares"); assertEq(IERC20(vault).balanceOf(creditAccount), shares); } function test_depositDiff() public { address creditAccount = _openCreditAccount(); uint256 initialBalance = 100e18; uint256 leftover = 1; uint256 shares = adapter.depositDiff(leftover); assertEq(shares, adapter.previewDeposit(initialBalance - leftover)); } function test_cannotCallDirectly() public { vm.expectRevert(); // CallerNotCreditFacadeException adapter.deposit(100e18); } } ``` **Key test patterns:** 1. **Fork testing** - Test against real protocols on mainnet/testnet 2. **Access control** - Verify creditFacadeOnly works 3. **Balance checks** - Assert correct token transfers 4. **Diff functions** - Verify correct amount calculation 5. **Edge cases** - Test with zero amounts, max values, etc. ## Deployment and Whitelisting After writing and testing your adapter: 1. **Deploy the adapter** - Deploy to mainnet with CreditManager and target addresses 2. **Submit governance proposal** - Request adapter whitelisting via Gearbox governance 3. **Governance vote** - DAO votes on adding adapter to Credit Manager(s) 4. **Adapter registration** - If approved, adapter is added to allowedAdapters mapping **Governance process:** * Submit proposal on Gearbox governance forum * Include audit report (required for new adapters) * Specify which Credit Manager(s) should whitelist it * DAO votes on proposal * If passed, adapter becomes available for Credit Accounts **Note:** Each Credit Manager has its own adapter whitelist. An adapter approved for one Credit Manager isn't automatically available on others. For architectural background, see [Multicall System](https://docs.gearbox.finance/developers/multicall-system). ## Best Practices 1. **Keep it simple** - Adapters should be thin wrappers, not business logic 2. **No state** - Adapters should be stateless (except immutables) 3. **Comprehensive diff** - Implement diff functions for all variable-amount operations 4. **Always override recipients** - Never let users specify where funds go 5. **Test thoroughly** - Test both success and failure paths 6. **Document clearly** - Write NatSpec comments explaining each function 7. **Declare adapter type** - Set `_gearboxAdapterType` for proper categorization ## Key Inherited Functions AbstractAdapter provides these helper functions: | Function | Description | | ------------------------------ | --------------------------------------- | | `_creditAccount()` | Returns current Credit Account address | | `_creditManager()` | Returns Credit Manager address | | `_creditFacade()` | Returns Credit Facade address | | `_getMaskOrRevert(token)` | Gets token mask, reverts if not allowed | | `_execute(callData)` | Executes call on target contract | | `_executeSwapSafeApprove(...)` | Executes call with approval management | | `targetContract` | Immutable address of wrapped protocol | ## Related * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Using adapters in multicalls * [Multicall System](https://docs.gearbox.finance/developers/multicall-system) - Architectural overview ## Roles & Permissions Source: https://docs.gearbox.finance/developers/gp-roles File: content/developers/gp-roles.mdx Gearbox governance is divided into distinct roles with carefully scoped authority. No single role can unilaterally modify a live market — this separation of concerns is the foundation of the permissionless design. ## Role Summary | Role | Scope | Can Affect End Users? | Primary Responsibility | | --- | --- | --- | --- | | **DAO** | All chains | No | Protocol-level governance, code approval | | **Technical Multisig** | All chains | No | Execute DAO decisions (temporary) | | **Auditors** | All chains | No | Verify and sign contract bytecode | | **Instance Owners** | One chain | No | Chain-specific infrastructure and PFS | | **Financial Representatives** | One chain | No | Fee management per chain | | **Market Curators** | Specific markets | Yes (with timelock) | Create and manage markets | | **Emergency Admin** | Specific markets | Yes (immediate) | Emergency response | | **Pausable/Unpausable Admins** | Specific markets | Yes | Pause/unpause market components | | **Emergency Liquidators** | Specific markets | Yes | Special liquidation during emergencies | ## DAO (Token Holders) The DAO represents GEAR token holders and governs protocol-level decisions across all chains. Critically, none of the DAO's actions can modify existing markets. | Aspect | Detail | | --- | --- | | **Scope** | All chains | | **Voting power** | Proportional to token holdings | | **Current process** | Snapshot voting, then technical multisig executes | | **Future process** | On-chain voting directly on transaction batches | ### DAO Authority | Area | Actions | | --- | --- | | **CCM Management** | Manage the Cross-Chain Multisig configuration | | **BCR Management** | Add auditors, allow system contracts in the Bytecode Repository | | **Instance Management** | Activate instances, set global addresses | ### Key Constraint None of the DAO's actions can modify existing markets. The DAO governs the protocol infrastructure, not individual market parameters. ## Technical Multisig A temporary measure that exists solely to execute DAO decisions. The Technical Multisig has no autonomous decision-making authority. | Aspect | Detail | | --- | --- | | **Scope** | All chains | | **Autonomy** | None — executes DAO decisions only | | **Implementation** | Legacy Safe multisig + Cross-Chain Multisig contract | | **Interaction** | Strictly forced to interact with CCM on Ethereum Mainnet only | This role is designed to be eliminated once on-chain voting is fully implemented. ## Auditors External parties who audit smart contracts and sign verified bytecode. They are the gatekeepers for what code can be deployed. | Aspect | Detail | | --- | --- | | **Scope** | All chains | | **Interaction point** | BCR on Ethereum Mainnet exclusively | | **Management** | Added and removed by DAO | ### Auditor Authority | Action | Function | Description | | --- | --- | --- | | Submit audit report | `submitAuditReport()` | Add an audit report to the BCR | | Assign contract type | via `submitAuditReport()` | Assigns contractType ownership to the verified bytecode | ## Instance Owners Instance Owners manage chain-specific infrastructure through a soft power mechanism. Their authority is limited to preparing the environment — they cannot force changes onto existing markets. | Aspect | Detail | | --- | --- | | **Scope** | One chain | | **Current composition** | 3 core Gearbox members + 9 tech multisig members, threshold 4 | | **Neutrality requirement** | Must remain impartial regarding price feed brands | ### Instance Owner Authority | Area | Actions | | --- | --- | | **PriceFeed Store** | Add, update, and remove token/feed mappings | | **Local addresses** | Manage chain-specific address configuration | | **Bot management** | Forbid specific bots | | **Infrastructure** | Manage router and internal systems | ### Key Constraint None of their actions can modify existing market behavior. PFS changes are not auto-applied to existing markets — they only affect future configuration transactions. ## Financial Representatives Each chain has a Financial Representative who manages fee-related operations. This role requires cooperation with market curators through a 2/2 approval mechanism. | Aspect | Detail | | --- | --- | | **Scope** | One chain | | **Appointment** | Set during instance activation | | **Replacement** | Changed via DAO vote | ### Financial Representative Authority | Action | Function | Requires 2/2 Approval | | --- | --- | --- | | Set default fee split | `setDefaultSplit()` | Yes | | Set token-specific split | `setTokenSplit()` | Yes | | Set insurance amount | `setTokenInsuranceAmount()` | Yes | | Withdraw collected fees | `withdrawToken()` | Yes | ## Market Curators Market curators are the most powerful market-level role. Anyone can become a curator — the DAO cannot deny participation. The DAO has no responsibility for or control over curated markets. | Aspect | Detail | | --- | --- | | **Scope** | Specific markets | | **Entry barrier** | None — anyone can become a curator | | **Primary gateway** | MarketConfigurator contract (supports Ownable2Step) | | **Default timelock** | 24 hours (hardcoded minimum) | ### Governance Flow ```mermaid flowchart LR Gov[Governor] -->|"submits batch"| TL[Timelock - 24h min] TL -->|"executes after delay"| MC[Market Configurator] MC -->|"applies changes"| Market[Market] ``` ### Market Curator Authority — Configurator Level | Action | Description | | --- | --- | | Set emergency admin | Assign or change the emergency admin for the market | | Grant/revoke roles | Manage role assignments within the market | | Create markets | Deploy new markets | | Shutdown markets | Permanently close markets | | Add periphery contracts | Register additional contracts for the market | ### Market Curator Authority — Market Management | Action | Description | | --- | --- | | Add collateral tokens | Extend accepted collateral types | | Create/shutdown credit suites | Manage credit account configurations | | Upgrade oracles | Change price oracle implementations | | Upgrade IRM | Change interest rate model | | Upgrade rate keeper | Change rate keeper implementation | | Upgrade loss policy | Change loss handling policy | | Set fees | Configure fee parameters | | Set limits | Configure borrowing and position limits | | Ramp LTs | Gradually adjust liquidation thresholds | | Forbid/allow tokens | Restrict or permit specific tokens | | Pause/unpause | Halt or resume market operations | ## Emergency Admin The Emergency Admin can act immediately without timelock delays. This role exists for rapid response to security threats or market anomalies. | Aspect | Detail | | --- | --- | | **Scope** | Specific markets | | **Timelock** | None — effects are immediate | | **Appointment** | Set by market owner, can be changed anytime | ### Emergency Admin Authority | Area | Actions | | --- | --- | | **Timelock management** | Cancel pending batches in the timelock | | **Role management** | Revoke roles | | **Oracle management** | Configure PriceOracle | | **Pool emergency** | Set credit manager debt limit to zero, set quota to zero, pause pool | | **Credit suite emergency** | Emergency credit suite management | ## Pausable/Unpausable Admins Specialized roles that can pause or unpause specific market components. These provide granular control over market operations during incidents. | Aspect | Detail | | --- | --- | | **Scope** | Specific markets | | **Authority** | Pause or unpause individual market components | | **User impact** | Can affect end users | ## Emergency Liquidators Accounts with special liquidation rights that are only active during emergency conditions. | Aspect | Detail | | --- | --- | | **Scope** | Specific markets | | **Activation** | Only during emergencies | | **Authority** | Special liquidation permissions beyond normal liquidator access | ## Learn More - [PriceFeed Store](https://docs.gearbox.finance/developers/gp-pricefeed-store) — How price feeds are approved and managed - [Treasury & Insurance](https://docs.gearbox.finance/developers/gp-treasury) — Fee collection and distribution mechanics - [Guardrails & Role Separation](https://docs.gearbox.finance/developers/gp-guardrails) — The three-layer governance model - [Cross-Chain Multisig (CCM)](https://docs.gearbox.finance/developers/gp-ccm) — How governance synchronizes across chains - [Bytecode Repository (BCR)](https://docs.gearbox.finance/developers/gp-bcr) — How code is verified before deployment ## Protocol Integration Source: https://docs.gearbox.finance/developers/gp-integration File: content/developers/gp-integration.mdx Build smart contracts that compose with Gearbox Credit Accounts. ## Overview Protocol integration means building contracts that: * Call Gearbox Credit Accounts from external contracts * Create automated strategies using multicalls * Compose Gearbox with your own protocol logic Unlike adapters (which are called BY Credit Accounts), protocol integrations CALL Credit Accounts from outside. ## Architecture ``` Your Contract -> CreditFacade -> Credit Account -> Adapters -> DeFi Protocols | +-> Multicall execution +-> Collateral checks +-> Access control ``` ## Basic Integration Pattern ### 1. Find the Right Market ```solidity import {IAddressProviderV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol"; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; contract MyStrategy { IAddressProviderV3 public constant ADDRESS_PROVIDER = IAddressProviderV3(0x9ea7b04Da02a5373317D745c1571c84aaD03321D); ICreditFacadeV3 public creditFacade; address public creditManager; function initialize(address _creditManager) external { creditManager = _creditManager; // Get CreditFacade from CreditManager creditFacade = ICreditFacadeV3( ICreditManagerV3(_creditManager).creditFacade() ); } } ``` ### 2. Build and Execute Multicalls ```solidity import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; function depositToStrategy( address creditAccount, address token, uint256 amount ) external { MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (token, amount) ) }); // Caller must approve creditManager first creditFacade.multicall(creditAccount, calls); } ``` ## Common Integration Patterns ### Automated Strategy Contract A contract that executes predefined strategies: ```solidity // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.17; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {ICreditFacadeV3Multicall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3Multicall.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; import {IUniswapV3Adapter} from "@gearbox-protocol/integrations-v3/contracts/interfaces/uniswap/IUniswapV3Adapter.sol"; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract LeveragedYieldStrategy { ICreditFacadeV3 public immutable creditFacade; ICreditManagerV3 public immutable creditManager; address public immutable underlying; address public immutable targetAsset; address public immutable uniswapAdapter; address public immutable yieldAdapter; constructor( address _creditManager, address _targetAsset, address _yieldVault ) { creditManager = ICreditManagerV3(_creditManager); creditFacade = ICreditFacadeV3(creditManager.creditFacade()); underlying = creditManager.underlying(); targetAsset = _targetAsset; // Find adapters uniswapAdapter = creditManager.contractToAdapter( 0xE592427A0AEce92De3Edee1F18E0157C05861564 // Uniswap V3 Router ); yieldAdapter = creditManager.contractToAdapter(_yieldVault); require(uniswapAdapter != address(0), "Uniswap adapter not found"); require(yieldAdapter != address(0), "Yield adapter not found"); } /// @notice Open leveraged yield position /// @param collateralAmount Initial collateral /// @param leverage Leverage multiplier (e.g., 4 for 4x) /// @param minYieldTokens Minimum yield tokens to receive function openPosition( uint256 collateralAmount, uint256 leverage, uint256 minYieldTokens ) external returns (address creditAccount) { require(leverage >= 1 && leverage <= 10, "Invalid leverage"); uint256 borrowAmount = collateralAmount * (leverage - 1); MultiCall[] memory calls = new MultiCall[](4); // 1. Add collateral calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.addCollateral, (underlying, collateralAmount) ) }); // 2. Borrow calls[1] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.increaseDebt, (borrowAmount) ) }); // 3. Swap to target asset uint256 totalAmount = collateralAmount + borrowAmount; ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({ tokenIn: underlying, tokenOut: targetAsset, fee: 500, recipient: address(0), deadline: block.timestamp + 3600, amountIn: totalAmount, amountOutMinimum: 0, // Using yield token check instead sqrtPriceLimitX96: 0 }); calls[2] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); // 4. Deposit to yield vault using diff pattern calls[3] = MultiCall({ target: yieldAdapter, callData: abi.encodeCall( IYearnV2Adapter.depositDiff, (1) // Leave 1 wei ) }); // Approve and open IERC20(underlying).transferFrom(msg.sender, address(this), collateralAmount); IERC20(underlying).approve(address(creditManager), collateralAmount); creditAccount = creditFacade.openCreditAccount(msg.sender, calls, 0); } } ``` ### Strategy with Price Updates When using Pyth or Redstone oracles, your contract must accept and forward price data: ```solidity /// @notice Execute strategy with price updates /// @param creditAccount Target Credit Account /// @param priceUpdates Array of (token, reserve, data) for on-demand oracles /// @param strategyParams Your strategy-specific parameters function executeWithPriceUpdates( address creditAccount, bytes[] calldata priceUpdates, StrategyParams calldata strategyParams ) external { uint256 numUpdates = priceUpdates.length; // Build calls with price updates first MultiCall[] memory calls = new MultiCall[](numUpdates + strategyParams.numCalls); // Add all price updates at the beginning for (uint256 i = 0; i < numUpdates; i++) { (address token, bool reserve, bytes memory data) = abi.decode(priceUpdates[i], (address, bool, bytes)); calls[i] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.onDemandPriceUpdate, (token, reserve, data) ) }); } // Add strategy calls after price updates _buildStrategyCalls(calls, numUpdates, strategyParams); creditFacade.multicall(creditAccount, calls); } ``` ### Keeper/Bot Integration For automated position management: ```solidity contract PositionKeeper { ICreditFacadeV3 public immutable creditFacade; address public keeper; mapping(address => bool) public managedAccounts; modifier onlyKeeper() { require(msg.sender == keeper, "Not keeper"); _; } /// @notice Rebalance position when health factor is low function rebalance( address creditAccount, address tokenToSell, uint256 sellAmount, uint256 minRepay ) external onlyKeeper { require(managedAccounts[creditAccount], "Not managed"); MultiCall[] memory calls = new MultiCall[](2); // 1. Swap collateral for underlying calls[0] = MultiCall({ target: uniswapAdapter, callData: abi.encodeCall( IUniswapV3Adapter.exactInputSingle, (swapParams) ) }); // 2. Repay debt using diff pattern calls[1] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.decreaseDebt, (minRepay) ) }); // Execute as bot (requires bot permissions on account) creditFacade.botMulticall(creditAccount, calls); } } ``` ## Access Control Considerations ### Account Ownership Only the account owner (or approved bots) can execute multicalls: ```solidity // This will revert if msg.sender is not account owner creditFacade.multicall(creditAccount, calls); // For keeper/bot access, the owner must grant permissions first // This is done via setBotPermissions in a multicall ``` ### Bot Permissions To allow external contracts to manage accounts: ```solidity // Owner grants bot permissions MultiCall[] memory calls = new MultiCall[](1); calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.setBotPermissions, ( botAddress, uint192(1 << 0 | 1 << 1) // Permission flags for allowed operations ) ) }); creditFacade.multicall(creditAccount, calls); ``` Then the bot can use `botMulticall`: ```solidity // Bot executes with limited permissions creditFacade.botMulticall(creditAccount, calls); ``` ## Error Handling ### Reading Account State Before Operations ```solidity function getAccountHealth(address creditAccount) external view returns (uint256) { ( uint256 debt, uint256 cumulativeIndexLastUpdate, uint128 cumulativeQuotaInterest, uint128 quotaFees, uint256 enabledTokensMask, uint16 flags, uint64 lastDebtUpdate, address borrower ) = creditManager.creditAccountInfo(creditAccount); // Calculate health factor using collateral values // ... } ``` ### Handling Reverts ```solidity function safeExecute( address creditAccount, MultiCall[] memory calls ) external returns (bool success) { try creditFacade.multicall(creditAccount, calls) { success = true; } catch Error(string memory reason) { emit ExecutionFailed(creditAccount, reason); success = false; } } ``` ## Gas Optimization ### Use Collateral Hints For accounts with many tokens, provide hints to reduce oracle calls: ```solidity // Get token masks for primary collateral uint256 usdcMask = creditManager.getTokenMaskOrRevert(usdc); uint256 wethMask = creditManager.getTokenMaskOrRevert(weth); uint256[] memory hints = new uint256[](2); hints[0] = usdcMask; hints[1] = wethMask; // Add setFullCheckParams as first non-price-update call calls[0] = MultiCall({ target: address(creditFacade), callData: abi.encodeCall( ICreditFacadeV3Multicall.setFullCheckParams, (hints, 10000) // 1.0 min health factor ) }); ``` ### Batch Operations Combine related operations in single multicalls rather than multiple transactions: ```solidity // GOOD - single multicall MultiCall[] memory calls = new MultiCall[](4); calls[0] = addCollateralCall; calls[1] = borrowCall; calls[2] = swapCall; calls[3] = depositCall; creditFacade.multicall(account, calls); // BAD - multiple transactions (more gas, atomicity issues) creditFacade.multicall(account, [addCollateralCall]); creditFacade.multicall(account, [borrowCall]); creditFacade.multicall(account, [swapCall]); ``` ## Testing ### Foundry Setup ```solidity import {Test} from "forge-std/Test.sol"; contract StrategyTest is Test { LeveragedYieldStrategy strategy; address user = address(0x1); function setUp() public { // Fork mainnet vm.createSelectFork("mainnet"); // Deploy strategy strategy = new LeveragedYieldStrategy( CREDIT_MANAGER, TARGET_ASSET, YIELD_VAULT ); } function test_openPosition() public { // Setup deal(USDC, user, 10_000e6); vm.startPrank(user); IERC20(USDC).approve(address(strategy), 10_000e6); // Execute address account = strategy.openPosition(10_000e6, 4, 0); // Verify assertTrue(account != address(0)); assertGt(IERC20(YIELD_TOKEN).balanceOf(account), 0); vm.stopPrank(); } } ``` ## Security Considerations 1. **Reentrancy** - Multicalls are atomic, but be careful with callbacks 2. **Slippage** - Always use `storeExpectedBalances`/`compareBalances` for swaps 3. **Access control** - Verify account ownership before operations 4. **Oracle manipulation** - Use safe pricing for withdrawals 5. **Flash loan attacks** - Consider same-block restrictions on debt changes ## Related * [Multicalls](https://docs.gearbox.finance/developers/gm-accounts-multicalls) - Core encoding patterns * [Making External Calls](https://docs.gearbox.finance/developers/making-external-calls) - Using adapters * [Controlling Slippage](https://docs.gearbox.finance/developers/controlling-slippage) - Protecting operations * [Adapter Development](https://docs.gearbox.finance/developers/gp-adapters) - Building adapters ## Core Extension Source: https://docs.gearbox.finance/developers/gp-extension File: content/developers/gp-extension.mdx Advanced patterns for extending Gearbox protocol contracts beyond standard adapter integration. > This is advanced material. For standard integrations, see [Adapter Development](https://docs.gearbox.finance/developers/gp-adapters) or [Protocol Integration](https://docs.gearbox.finance/developers/gp-integration). ## When to Extend vs Integrate Different levels of integration require different approaches: | Approach | Complexity | When to Use | | ------------------------ | ---------- | -------------------------------------------------------------------- | | **Adapter** | Low | Wrap existing DeFi protocols for Credit Account access | | **Protocol Integration** | Medium | Build contracts that compose with Credit Accounts externally | | **Core Extension** | High | Modify Credit Manager/Pool behavior, create protocol-specific spokes | Examples by approach: **Adapter:** Wrapping Uniswap, Curve, Yearn for Credit Account users **Protocol Integration:** Strategy vaults that open/manage Credit Accounts **Core Extension:** Custom Credit Manager for protocol-specific accounting, custom interest rate models, pool hooks for fee distribution Build a core extension when you need to: * Modify how debt/collateral calculations work * Change liquidity flow patterns between pools and Credit Managers * Implement protocol-specific Credit Manager variants ("spokes") * Create custom interest rate logic beyond linear models * Add lifecycle hooks to pool operations ## Understanding Money Flows Core extensions require deep understanding of how liquidity moves through the protocol. ### The Fundamental Flow ``` Lenders → Pool → Credit Manager → Credit Account → DeFi Protocols (ERC-4626) | | | +-> Holds collateral +-> Tracks debt ``` **Key invariants:** 1. Pool lends only to whitelisted Credit Managers 2. Credit Managers borrow on behalf of Credit Accounts 3. Credit Accounts hold all collateral and debt 4. All value flows are tracked via indexed interest and quota systems ### Detailed Liquidity Flow **When borrowing:** ```solidity // 1. User opens Credit Account via CreditFacade CreditFacade.openCreditAccount(owner, calls, referralCode) | v // 2. Facade instructs Manager to borrow CreditManager.openCreditAccount(debt, onBehalfOf) | v // 3. Manager requests liquidity from Pool Pool.lendCreditAccount(borrowedAmount, creditAccount) | v // 4. Pool transfers underlying directly to Credit Account IERC20(underlying).transfer(creditAccount, borrowedAmount) ``` **When repaying:** ```solidity // 1. User closes account via CreditFacade CreditFacade.closeCreditAccount(creditAccount, calls) | v // 2. Manager calculates total debt totalDebt = principal + accruedInterest + quotaInterest + fees | v // 3. Underlying transfers from Credit Account to Pool IERC20(underlying).transferFrom(creditAccount, pool, totalDebt) | v // 4. Manager reports repayment to Pool Pool.repayCreditAccount(repaidAmount, profit, loss) ``` **Critical detail:** The Credit Manager never holds funds. It orchestrates transfers between Pool and Credit Account while maintaining accounting state. ## Pool Extension Patterns ### Custom Interest Rate Models The pool queries an external IRM contract for interest rates. You can implement custom logic by deploying a new IRM. **IInterestRateModel interface:** ```solidity interface ILinearInterestRateModelV3 { function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate); function getModelParameters() external view returns ( uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3 ); } ``` **Example: Time-weighted interest model** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {ILinearInterestRateModelV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ILinearInterestRateModelV3.sol"; contract TimeWeightedIRM is ILinearInterestRateModelV3 { uint16 public immutable U_1 = 7000; // 70% uint16 public immutable U_2 = 9000; // 90% uint16 public immutable R_base = 100; // 1% uint16 public immutable R_slope1 = 200; // 2% uint16 public immutable R_slope2 = 500; // 5% // Time-based slope adjustment uint16 public immutable peakHourMultiplier = 150; // 1.5x during peak hours function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate) { uint256 utilizationBps = (expectedLiquidity - availableLiquidity) * 10_000 / expectedLiquidity; // Apply time-based multiplier during peak hours (12-18 UTC) uint256 hour = (block.timestamp / 3600) % 24; uint16 multiplier = (hour >= 12 && hour < 18) ? peakHourMultiplier : 100; // Standard linear calculation with time adjustment uint256 baseRate = R_base; if (utilizationBps <= U_1) { borrowRate = baseRate + (R_slope1 * utilizationBps * multiplier) / 10_000 / 100; } else if (utilizationBps <= U_2) { borrowRate = baseRate + (R_slope2 * utilizationBps * multiplier) / 10_000 / 100; } else { borrowRate = baseRate + (R_slope3 * utilizationBps * multiplier) / 10_000 / 100; } // Convert to RAY (27 decimals) borrowRate = borrowRate * 10**27 / 10_000; } function getModelParameters() external view returns ( uint16, uint16, uint16, uint16, uint16, uint16 ) { // Return adjusted slope based on current time uint256 hour = (block.timestamp / 3600) % 24; uint16 adjustedSlope1 = (hour >= 12 && hour < 18) ? R_slope1 * peakHourMultiplier / 100 : R_slope1; return (U_1, U_2, R_base, adjustedSlope1, R_slope2, R_slope3); } } ``` **Deploying custom IRM:** Custom IRMs can be set via governance: ```solidity // Only CONFIGURATOR can update IRM IPoolV3(pool).setInterestRateModel(newIRMAddress); ``` ### Pool Withdrawal Hooks Pools in V3 support withdrawal fees. These are not "hooks" in the callback sense, but configurable parameters: ```solidity // Read current withdrawal fee uint16 withdrawFee = IPoolV3(pool).withdrawFee(); // basis points // Fee is applied during withdraw/redeem uint256 assets = pool.withdraw(amount, receiver, owner); // Actual received = amount - (amount * withdrawFee / 10000) ``` For custom fee distribution logic, you would typically: 1. Deploy a fee collector contract 2. Configure the pool's treasury address to your collector 3. Implement distribution logic in your collector ## Credit Manager Extension Patterns ### Spoke Development Concept A "spoke" is a specialized Credit Manager variant designed for protocol-specific use cases. Unlike standard Credit Managers, spokes extend core functionality for unique accounting or liquidity patterns. **When to build a spoke:** * Your protocol needs custom collateral valuation logic * You're integrating Credit Accounts as a core protocol primitive * You need specialized debt/liquidation mechanics * You want to modify how positions are opened/closed **Example use case:** A derivatives protocol where Credit Accounts are used as margin accounts with custom position tracking. ### Spoke Architecture Pattern ```solidity import {CreditManagerV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditManagerV3.sol"; contract DerivativesSpoke is CreditManagerV3 { // Custom state for protocol-specific tracking mapping(address => PositionData) public positions; struct PositionData { uint256 longExposure; uint256 shortExposure; int256 unrealizedPnL; } constructor( address _pool, address _addressProvider ) CreditManagerV3(_pool, _addressProvider) {} // Override collateral calculation to include unrealized PnL function calcDebtAndCollateral( address creditAccount, CollateralCalcTask task ) public view override returns ( CollateralDebtData memory cdd ) { // Call parent implementation cdd = super.calcDebtAndCollateral(creditAccount, task); // Adjust TWV based on unrealized PnL PositionData memory pos = positions[creditAccount]; if (pos.unrealizedPnL > 0) { // Add unrealized profit to collateral cdd.twvUSD += uint256(pos.unrealizedPnL); } else { // Subtract unrealized loss from collateral cdd.twvUSD -= uint256(-pos.unrealizedPnL); } } // Protocol-specific function to update position state function updatePosition( address creditAccount, uint256 longExposure, uint256 shortExposure, int256 pnl ) external { // Only allow calls from authorized adapters require( adapterToContract[msg.sender] != address(0), "Not authorized adapter" ); positions[creditAccount] = PositionData({ longExposure: longExposure, shortExposure: shortExposure, unrealizedPnL: pnl }); } } ``` ### Custom Collateral Valuation Extend collateral calculations for protocol-specific assets: ```solidity // Override token price resolution function priceOracle() public view override returns (IPriceOracleV3) { return IPriceOracleV3(customOracleAddress); } // Implement custom oracle with protocol-specific pricing contract CustomOracle is IPriceOracleV3 { function convertToUSD( uint256 amount, address token ) external view override returns (uint256) { if (token == protocolSpecificToken) { // Custom valuation logic return amount * getCustomPrice() / 10**tokenDecimals; } return defaultOracle.convertToUSD(amount, token); } } ``` ### Extending Liquidation Logic Custom liquidation premiums based on collateral type: ```solidity contract CustomLiquidationCM is CreditManagerV3 { mapping(address => uint16) public tokenLiquidationPremiums; function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls ) external override { // Calculate custom premium based on collateral composition uint256 enabledMask = enabledTokensMaskOf(creditAccount); uint16 premium = _calculatePremium(enabledMask); // Set premium temporarily uint16 oldPremium = liquidationPremium; liquidationPremium = premium; // Execute standard liquidation super.liquidateCreditAccount(creditAccount, to, calls); // Restore liquidationPremium = oldPremium; } function _calculatePremium(uint256 mask) internal view returns (uint16) { uint16 maxPremium = 0; for (uint256 i = 0; i < 256; i++) { if (mask & (1 << i) != 0) { address token = getTokenByMask(1 << i); if (tokenLiquidationPremiums[token] > maxPremium) { maxPremium = tokenLiquidationPremiums[token]; } } } return maxPremium; } } ``` ## Working with Configurators The CreditConfigurator provides governance interface for Credit Manager parameters. When building spokes, you typically extend the configurator as well. ### Standard Configurator Usage ```solidity import {ICreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditConfiguratorV3.sol"; ICreditConfiguratorV3 configurator = ICreditConfiguratorV3(configuratorAddress); // Add new collateral token configurator.addCollateralToken( tokenAddress, 8500 // liquidationThreshold (85% = 8500 basis points) ); // Add adapter configurator.allowAdapter(adapterAddress); // Set fees configurator.setFees( 500, // feeInterest (5%) 150, // feeLiquidation (1.5%) 500, // liquidationPremium (5%) 100, // feeLiquidationExpired (1%) 500 // liquidationPremiumExpired (5%) ); ``` ### Custom Configurator for Spokes ```solidity import {CreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditConfiguratorV3.sol"; contract DerivativesSpokeConfigurator is CreditConfiguratorV3 { DerivativesSpoke public immutable spoke; constructor( address _creditManager, address _creditFacade ) CreditConfiguratorV3(_creditManager, _creditFacade) { spoke = DerivativesSpoke(_creditManager); } // Protocol-specific configuration function setPositionLimits( uint256 maxLongExposure, uint256 maxShortExposure ) external configuratorOnly { spoke.setPositionLimits(maxLongExposure, maxShortExposure); } function setCustomLiquidationPremium( address token, uint16 premium ) external configuratorOnly { spoke.setTokenLiquidationPremium(token, premium); } } ``` ### Governance Integration For production deployments, configurator changes should go through governance: ```solidity // Propose change via governance forum // After approval, execute via timelock // Example: Adding new collateral token bytes memory data = abi.encodeCall( ICreditConfiguratorV3.addCollateralToken, (newTokenAddress, 8000) ); // Submit to governance contract governance.propose( configuratorAddress, 0, // value data, "Add NEWTOKEN as collateral with 80% LT" ); ``` **Key patterns demonstrated:** 1. **State extension**: Added `Position` tracking on top of base Credit Manager 2. **Collateral override**: Modified TWV calculation to include unrealized PnL 3. **Protocol-specific logic**: Market management and position tracking 4. **Governance integration**: `configuratorOnly` modifier for admin functions For architectural background, see [Credit Suite Architecture](https://docs.gearbox.finance/developers/gm-concept-accounts) and [Pool Architecture](https://docs.gearbox.finance/developers/gm-concept-markets). ## Security Considerations When extending core contracts: 1. **Preserve invariants** - Never break core protocol assumptions (debt tracking, collateral checks) 2. **Test extensively** - Fork mainnet and test against real pools/Credit Managers 3. **Audit thoroughly** - Core extensions require professional audits 4. **Consider upgrade paths** - Plan for parameter changes and emergency controls 5. **Monitor gas costs** - Overrides affect every user operation 6. **Validate inputs** - Custom logic must not allow manipulation of debt/collateral calculations ## Deployment Checklist * [ ] Core contracts audited by reputable firm * [ ] Fork tests against production Gearbox contracts * [ ] Governance proposal prepared with detailed specification * [ ] Documentation for users and integrators * [ ] Emergency pause mechanisms tested * [ ] Oracle manipulation scenarios analyzed * [ ] Gas profiling completed for all overridden functions * [ ] Upgradeability plan documented ## Related * [Adapter Development](https://docs.gearbox.finance/developers/gp-adapters) - Standard integration path * [Protocol Integration](https://docs.gearbox.finance/developers/gp-integration) - Building on top of Credit Accounts * [Credit Suite Architecture](https://docs.gearbox.finance/developers/gm-concept-accounts) - Core architecture reference * [Pool Architecture](https://docs.gearbox.finance/developers/gm-concept-markets) - Pool mechanics reference ## Deployment Addresses Source: https://docs.gearbox.finance/developers/res-addresses File: content/developers/res-addresses.mdx Gearbox Protocol contracts are deployed across multiple networks. This page lists the core contract addresses for each supported chain. ## Programmatic Discovery The recommended way to resolve contract addresses at runtime is through the **AddressProvider** contract. Rather than hardcoding addresses, query the AddressProvider to get the latest verified deployment for any protocol component: ```typescript import { GearboxSDK } from '@gearbox-protocol/sdk'; const sdk = await GearboxSDK.attach({ client, marketConfigurators: [] }); // Resolve any protocol contract by its key const [address, version] = sdk.addressProvider.mustGetLatest( AP_MARKET_COMPRESSOR, VERSION_RANGE_310 ); ``` See the [SDK setup guide](https://docs.gearbox.finance/developers/gm-start-ts) for full initialization details. *** ## Ethereum Mainnet | Contract | Address | | ---------------------- | -------------------------------------------- | | AddressProvider | `0x0000000000000000000000000000000000000001` | | MarketCompressor | `0x0000000000000000000000000000000000000002` | | CreditAccountCompressor| `0x0000000000000000000000000000000000000003` | | PriceFeedCompressor | `0x0000000000000000000000000000000000000004` | | BotList | `0x0000000000000000000000000000000000000005` | > Placeholder addresses shown above. Refer to the AddressProvider or the [Gearbox GitHub](https://github.com/Gearbox-protocol) for canonical deployments. *** ## Arbitrum | Contract | Address | | ---------------------- | -------------------------------------------- | | AddressProvider | `0x0000000000000000000000000000000000000001` | | MarketCompressor | `0x0000000000000000000000000000000000000002` | | CreditAccountCompressor| `0x0000000000000000000000000000000000000003` | | PriceFeedCompressor | `0x0000000000000000000000000000000000000004` | | BotList | `0x0000000000000000000000000000000000000005` | > Placeholder addresses shown above. Refer to the AddressProvider or the [Gearbox GitHub](https://github.com/Gearbox-protocol) for canonical deployments. *** ## Optimism | Contract | Address | | ---------------------- | -------------------------------------------- | | AddressProvider | `0x0000000000000000000000000000000000000001` | | MarketCompressor | `0x0000000000000000000000000000000000000002` | | CreditAccountCompressor| `0x0000000000000000000000000000000000000003` | | PriceFeedCompressor | `0x0000000000000000000000000000000000000004` | | BotList | `0x0000000000000000000000000000000000000005` | > Placeholder addresses shown above. Refer to the AddressProvider or the [Gearbox GitHub](https://github.com/Gearbox-protocol) for canonical deployments. *** ## Notes - Always prefer the **AddressProvider** for programmatic address resolution. Hardcoded addresses may become stale after protocol upgrades. - Market-specific contracts (Pools, Credit Managers, Credit Facades) are discoverable through the MarketCompressor once you have the AddressProvider. - For a complete list of address keys, see the `AP_*` constants exported by the SDK. ## Security & Audits Source: https://docs.gearbox.finance/developers/res-security File: content/developers/res-security.mdx Gearbox Protocol takes security seriously. The protocol has undergone multiple independent audits and maintains an active bug bounty program. ## Audit Reports Gearbox smart contracts have been audited by leading security firms. The following reports cover the core protocol and its extensions: | Auditor | Scope | Report | | --------------- | ------------------ | ------------------------------------------------------- | | ChainSecurity | Core V3 | [View Report](https://github.com/Gearbox-protocol/security/blob/main/audits/PLACEHOLDER) | | Consensys Diligence | Core V3 | [View Report](https://github.com/Gearbox-protocol/security/blob/main/audits/PLACEHOLDER) | | Sigma Prime | V3 Integrations | [View Report](https://github.com/Gearbox-protocol/security/blob/main/audits/PLACEHOLDER) | | ABDK | V3 Math Libraries | [View Report](https://github.com/Gearbox-protocol/security/blob/main/audits/PLACEHOLDER) | > Links above are placeholders. Visit the [Gearbox security repository](https://github.com/Gearbox-protocol/security) for the latest audit reports. *** ## Bug Bounty Program Gearbox maintains an active bug bounty program on **Immunefi**, one of the largest Web3 security platforms. - **Platform:** [Immunefi](https://immunefi.com/) - **Scope:** Smart contracts, protocol logic, and integrations - **Rewards:** Up to $1,000,000 for critical vulnerabilities (severity-dependent) If you discover a potential vulnerability, please report it through the Immunefi platform rather than public disclosure. Responsible disclosure is critical for protecting user funds. *** ## Bytecode Repository (BCR) Verification Gearbox uses a Bytecode Repository (BCR) to ensure that only audited, verified bytecode is deployed on-chain. This provides an additional layer of security beyond source-code audits by verifying the exact compiled output. For details on how BCR verification works and how to check deployed contracts, see [BCR Verification](https://docs.gearbox.finance/developers/gp-bcr). *** ## Security Contact For security-related inquiries that do not fall under the bug bounty program: - **Email:** security@gearbox.fi - **PGP Key:** Available on the [Gearbox security repository](https://github.com/Gearbox-protocol/security) Please do not report vulnerabilities via email. Use the Immunefi bug bounty program for all vulnerability disclosures. *** ## Security Practices The Gearbox Protocol follows several security best practices: - **Multiple independent audits** before each major release - **Formal verification** of critical mathematical components - **Timelocks and multisig governance** for parameter changes - **Immutable core logic** with upgradeable configuration - **On-chain BCR verification** ensuring only audited bytecode is deployed - **Continuous monitoring** of protocol health and anomalous activity ## Glossary Source: https://docs.gearbox.finance/developers/res-glossary File: content/developers/res-glossary.mdx Key terms used throughout the Gearbox Protocol documentation. | Term | Definition | | --- | --- | | Adapter | A thin wrapper contract that translates Credit Account multicalls into calls to an external DeFi protocol (e.g., Uniswap, Aave), enforcing collateral checks before and after execution. | | Bytecode Repository (BCR) | An on-chain registry of audited contract bytecode. Deployments are verified against the BCR to ensure only approved, audited code runs in production. | | Credit Account | An isolated smart-contract account that holds a user's collateral and borrowed funds. All leveraged operations happen inside a Credit Account. | | Credit Configurator | The admin-facing contract that governs a Credit Manager's parameters, such as allowed tokens, adapters, debt limits, and liquidation thresholds. | | Credit Facade | The user-facing entry point for a Credit Manager. It validates multicalls, enforces health-factor checks, and emits events for account operations. | | Credit Manager | The core contract that manages Credit Accounts for a given market. It tracks collateral, debt, and permissions, and delegates user interactions to the Credit Facade. | | Credit Suite | The combination of a Credit Manager, its Credit Facade, and its Credit Configurator, together forming the complete management layer for leveraged accounts in a market. | | Cross-Chain Multisig (CCM) | A governance mechanism that coordinates administrative actions across multiple chains, ensuring consistent parameter changes for Gearbox deployments on different networks. | | Debt Ceiling | The maximum total amount that can be borrowed from a pool by all Credit Managers combined, limiting the pool's overall leverage exposure. | | Diesel Token | The ERC-20 LP share token received when depositing into a Gearbox pool. Its value appreciates over time as interest accrues to the pool. | | Health Factor | The ratio of a Credit Account's total weighted collateral value to its total debt. A health factor below 1.0 means the account is eligible for liquidation. | | Instance | A single deployment of the Gearbox Protocol on a specific chain, consisting of an AddressProvider and all contracts registered through it. | | Instance Owner | The governance address (typically a multisig or DAO) that controls an Instance's AddressProvider and can register or update protocol contracts. | | Liquidation Threshold (LT) | A per-token coefficient (in basis points) that discounts a collateral token's value when computing the weighted collateral for health-factor calculations. A lower LT means the protocol treats the token more conservatively. | | Market Curator | An entity or role responsible for configuring and managing a specific market's parameters, including allowed collaterals, debt limits, and risk settings. | | Multicall | A batched sequence of operations executed atomically on a Credit Account within a single transaction. All health-factor checks are deferred until the end of the batch. | | Omni-EVM | Gearbox's cross-chain execution model that allows Credit Accounts to interact with protocols on multiple EVM-compatible chains from a single position. | | Pool | A lending pool that accepts deposits from liquidity providers and lends to Credit Managers. Each pool is denominated in a single underlying token. | | Protocol DAO | The decentralized governance body that oversees the Gearbox Protocol, controlling upgrades, risk parameters, and treasury operations. | | Quota | A per-token borrowing allocation within a pool that limits how much of a specific collateral type can be used across all Credit Accounts. | | Quota Rate | The additional interest rate (RAY-scaled, per-second) charged on a Credit Account for holding a non-underlying collateral token that requires a quota. | | Total Weighted Value (TWV) | The sum of each collateral token's balance multiplied by its price and its liquidation threshold. TWV divided by total debt gives the health factor. | ## Gearbox Agentic Overview Source: https://docs.gearbox.finance/developers/ga-overview File: content/developers/ga-overview.mdx Gearbox Agentic is AI-powered capital management built on top of Gearbox Credit Accounts. Autonomous agents discover yield opportunities, analyze risk, build transactions, preview exact on-chain outcomes, execute, and monitor positions — all through a structured loop and a single SDK. ## What It Does An AI agent manages DeFi capital across chains using the Gearbox SDK. The SDK handles protocol complexity — the agent handles decisions. The SDK builds transactions and previews outcomes but **never signs or sends** — execution stays with the caller. ## The 6-Step Agent Loop Every agent interaction follows the same structured cycle: | Step | Purpose | Who Decides | | --- | --- | --- | | **Discover** | Scan all current opportunities | SDK returns data | | **Analyze** | Due diligence on shortlisted candidates | Agent reasons over data | | **Propose** | Find optimal rebalance — or decide to do nothing | Agent + Router | | **Preview** | Verify on-chain feasibility right now | SDK simulates | | **Execute** | Sign and submit | User or agent via bot API | | **Monitor** | React to events that require response | SDK returns live state | [Read more about the Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) ## Two Execution Modes ### Preview + Verify (Human-in-the-Loop) The agent builds and previews a transaction, then encodes the preview as a URL for [verify.gearbox.finance](https://verify.gearbox.finance). A human reviews decoded calldata, balance changes, and health factor projections before signing. ### Bot Permissions (Autonomous) The agent operates with bounded on-chain autonomy via a permission bitmask on a Credit Account. It cannot exceed its granted authority. [Read more about Execution](https://docs.gearbox.finance/developers/ga-execution) ## Learn More - [Concepts](https://docs.gearbox.finance/developers/ga-concepts) — Agent Loop, Architecture & Tools, MCP Server - [Getting Started](https://docs.gearbox.finance/developers/ga-start) — MCP Setup and First Agent tutorial - [Execution](https://docs.gearbox.finance/developers/ga-execution) — Human-in-the-Loop and Bot Execution ## Concepts Source: https://docs.gearbox.finance/developers/ga-concepts File: content/developers/ga-concepts.mdx The foundational concepts behind Gearbox Agentic. - [**The Agent Loop**](https://docs.gearbox.finance/developers/ga-agent-loop) — The 6-step cycle: Discover → Analyze → Propose → Preview → Execute → Monitor. Event-driven review triggers adapt pace to urgency. - [**Architecture & Tools**](https://docs.gearbox.finance/developers/ga-architecture) — How agents and frontends share the same SDK. The Tool Visualization Map: one SDK method → one MCP tool → one UI component. - [**MCP Server**](https://docs.gearbox.finance/developers/ga-mcp) — 16 tools exposed as MCP tool calls for LLM agents, mapped directly to SDK methods. ## The Agent Loop Source: https://docs.gearbox.finance/developers/ga-agent-loop File: content/developers/ga-agent-loop.mdx The agent loop is a structured 6-step cycle for AI-powered capital management on Gearbox. Each stage has clear inputs, outputs, and decision points. ```mermaid graph LR D[Discover] --> A[Analyze] A --> P[Propose] P --> Pr[Preview] Pr -->|"not feasible"| P Pr --> E[Execute] E --> M[Monitor] M -->|"event"| A ``` | Step | Purpose | Who Decides | | --- | --- | --- | | **Discover** | Scan all current opportunities | SDK returns data | | **Analyze** | Due diligence on shortlisted candidates | Agent reasons over data | | **Propose** | Find optimal rebalance — or decide to do nothing | Agent + Router | | **Preview** | Verify on-chain feasibility right now | SDK simulates | | **Execute** | Sign and submit | User or agent via bot API | | **Monitor** | React to events that require faster response | SDK returns live state | ## Step 1: Discover Scan all available opportunities across chains. This is a broad survey — every pool, every strategy, every chain. The data is lightweight: headline APY, TVL, access requirements. ```typescript const opportunities = await sdk.opportunities.search({ chainIds: ["Mainnet", "Monad"], types: ["pool", "strategy"], assets: [Asset.STABLE], }); ``` The agent applies its own filters (APY floor, TVL minimum, permissionless-only, etc.) and produces a **shortlist** of candidates for deeper analysis. ## Step 2: Analyze Due diligence on the shortlisted candidates. The agent inspects each one in detail using specialized research sub-agents: | Sub-Agent | What It Evaluates | | --- | --- | | **Curator Research** | Governance quality, bad debt history, track record | | **Token Research** | Collateral liquidity, oracle reliability, risk classification | | **Profitability Forecast** | APY sustainability, yield type (organic vs incentivized), trend | | **Risk Scoring** | 5 dimensions: collateral, curator, smart contract, market, exit | | **Final Ranking** | `finalScore = adjustedApy * (1 - overallRisk)` | ```typescript // Deep dive into a candidate const detail = await sdk.pools.getDetail({ chainId, address: poolAddress }); const curator = await sdk.curators.getProfile(detail.curatorId); const apyHistory = await sdk.history.getMetric({ target, metric: "apy", periodDays: 30 }); const events = await sdk.events.getFeed({ chainId, target, sinceDays: 30 }); ``` The output is `AnalyzedOpportunity[]` — ranked by score, each with profitability forecasts, risk breakdowns, and reasoning. ## Step 3: Propose From the analyzed candidates, find the **optimal action** — or decide that no rebalance is needed. The agent considers: - Is the current position already optimal? - Would the rebalance cost (gas, slippage) exceed the expected gain? - What's the best route for the chosen strategy? If a rebalance makes sense: ```typescript // Query router for optimal path const route = await sdk.router.findOpenStrategyPath({ chainId, creditManagerAddress, collateralToken, collateralAmount, debtAmount, targetToken, slippageBps: 50, }); // Build the unsigned transaction const tx = await sdk.accounts.openCreditAccount({ chainId, creditManagerAddress, collateralToken, collateralAmount, debtAmount, pathCalls: route.calls, slippageBps: 50, }); ``` If no action is needed, the agent skips to Monitor. ## Step 4: Preview Verify that the transaction is **feasible on-chain right now**. LLMs are not instant — by the time the agent finishes reasoning, on-chain conditions may have changed (liquidity exhausted, price moved, quota filled). ```typescript const preview = await sdk.previewTransaction(tx); ``` The preview simulates the exact bytes against current chain state and returns: - `success` — would this execute? - `healthFactor` — projected HF after entry - `actions[]` — human-readable step descriptions - `balanceChanges[]` — net token movements - `warnings[]` — concerns (stale oracle, high utilization, etc.) ### If preview fails → back to Propose The agent does **not** re-analyze. The due diligence is still valid — the candidates are sound. Only the execution parameters need adjustment: - Liquidity exhausted → reduce position size or try different route - Price moved → adjust slippage tolerance - Quota filled → switch to the next-ranked candidate from Analyze ```typescript if (!preview.success || (preview.healthFactor ?? 0) < 1.4) { // Adjust parameters and re-propose // Do NOT re-run Analyze — the research is still valid } ``` ## Step 5: Execute Sign and submit the transaction. The SDK **never signs** — execution is the caller's responsibility. Two paths: - **[Human-in-the-Loop](https://docs.gearbox.finance/developers/ga-human-loop)** — agent encodes preview as URL for verify.gearbox.finance, human reviews and signs - **[Bot Execution](https://docs.gearbox.finance/developers/ga-bot-execution)** — agent signs via bot API with bounded on-chain permissions ```typescript const txHash = await walletClient.sendTransaction({ to: tx.to, data: tx.calldata, value: tx.value, }); ``` ## Step 6: Monitor Watch the live position and react to events. The monitor waits for signals — either scheduled (cron) or event-driven (alerts, external data). ```typescript const position = await sdk.accounts.getStatus({ chainId, creditAccount }); const alerts = await sdk.monitor.getAlerts({ chainId, creditAccount }); ``` ### When events trigger a review → back to Analyze The monitor doesn't jump to Propose — it goes back to **Analyze** to re-evaluate the situation with fresh data. This is critical: if a collateral token is dropping, the agent needs to understand the severity before deciding what to do. | Event | Urgency | Response | | --- | --- | --- | | Scheduled check (cron, every 4h) | Normal | Full Analyze → Propose cycle | | Collateral price dropping | Elevated | Quick Analyze → Propose adjustment | | Critical event (hack, depeg, tweet) | Immediate | Emergency exit via Propose → Preview → Execute | | Better opportunity detected | Normal | Analyze alternatives → Propose migration | | HF approaching liquidation | Urgent | Analyze → Propose collateral top-up or debt reduction | The urgency of the trigger determines the pace. A cron check runs the full cycle. A critical event skips straight to action — but still passes through Analyze to confirm the threat is real before executing. ## Learn More - [MCP Server](https://docs.gearbox.finance/developers/ga-mcp) — MCP tools organized by loop stage - [Execution](https://docs.gearbox.finance/developers/ga-execution) — How agents interact with the protocol safely ## Architecture & Tools Source: https://docs.gearbox.finance/developers/ga-architecture File: content/developers/ga-architecture.mdx How agents and frontends share the same SDK, and why every tool has a visual representation. ## Architecture ```mermaid flowchart TD Agent[Agent] --> MCP[MCP Server] Frontend[Frontend] --> SDK MCP --> SDK[Gearbox SDK] SDK --> RPC[On-chain RPC] SDK --> Backend[Backend API] ``` - **Agents** access the SDK through the [MCP Server](https://docs.gearbox.finance/developers/ga-mcp) — 16 tools exposed as MCP tool calls - **Frontends** use the SDK directly — same methods, same types - Both paths reach the same canonical API — identical data, identical types The SDK talks to two backends: - **On-chain RPC** — protocol reads, transaction simulation, live state - **Backend API** — history, cached metrics, curator/token metadata (optional — SDK degrades gracefully without it) ## One Type, One Tool, One Component Every SDK method returns a typed result. That same type is: - Exposed as an **MCP tool** for agents (JSON) - Rendered by a **UI component** for humans (React) This means an agent chat interface can show the same cards, tables, and previews that a regular user sees — not just raw text. ## Tool Visualization Map | SDK Method | MCP Tool | UI Component | Data | | --- | --- | --- | --- | | `sdk.opportunities.search()` | `list_opportunities` | `` | Pools & strategies with APY, TVL, access | | `sdk.pools.list()` | `list_pools` | `` | LP opportunities | | `sdk.strategies.list()` | `list_strategies` | `` | Leveraged strategies | | `sdk.pools.getDetail()` | `get_pool_detail` | `` | Full pool snapshot: tokens, rates, capacity | | `sdk.strategies.getDetail()` | `get_strategy_detail` | `` | Strategy params, leverage, collaterals | | `sdk.curators.getProfile()` | `get_curator` | `` | Track record, bad debt history, TVL | | `sdk.tokens.getMarketData()` | `get_token_liquidity` | `` | Oracle price, liquidity depth | | `sdk.history.getMetric()` | `get_metric_history` | `` | APY, utilization, TVL time series | | `sdk.events.getFeed()` | `get_events` | `` | Parameter changes, pending governance | | `sdk.positions.prepareOpen()` | `prepare_position` | `` | Unsigned transaction (RawTx) | | `sdk.previewTransaction()` | `simulate_position` | `` | Simulated outcome: HF, actions, warnings | | `sdk.accounts.getStatus()` | `get_position_status` | `` | Live health factor, balances, alerts | ## What This Enables - **Frontend developers** build UIs using SDK + `@gearbox-protocol/uikit` components - **Agent developers** get identical data quality through MCP tools - **Agent UIs** (chat with visualization) render MCP tool results using the same UI components — same experience as the native frontend ## Learn More - [MCP Server](https://docs.gearbox.finance/developers/ga-mcp) — all 16 tools in detail - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) — how tools are used at each stage - [SDK Namespaces](https://docs.gearbox.finance/developers/gm-start-namespaces) — full SDK namespace reference ## MCP Server Source: https://docs.gearbox.finance/developers/ga-mcp File: content/developers/ga-mcp.mdx The Gearbox MCP (Model Context Protocol) Server exposes SDK methods as tool calls for LLM agents. It is a thin adapter -- no duplicated business logic. MCP tool responses are direct SDK models or thin wrappers around them. ## MCP in the Stack ```mermaid graph TD Agent[AI Agent] --> MCP[MCP Server] MCP --> SDK[Gearbox SDK] SDK --> Chain[On-chain RPC and Contracts] SDK --> Backend[Backend: history, cache, metadata] ``` The MCP Server is **not** the source of truth. The source of truth is: 1. Smart contracts for live protocol state 2. SDK public domain model for integration 3. Backend for enrichment, history, and metadata MCP is the agent transport layer -- a tool-oriented presentation of SDK methods. ## Tools by Stage The 16 MCP tools map directly to the [agent loop](https://docs.gearbox.finance/developers/ga-agent-loop) stages: ### Discovery | MCP Tool | SDK Method | Description | |----------|-----------|-------------| | `list_opportunities` | `sdk.opportunities.search()` | Unified discovery across pools and strategies | | `list_pools` | `sdk.pools.list()` | Pool-specific discovery with APY, TVL, active assets | | `list_strategies` | `sdk.strategies.list()` | Strategy-specific discovery with leverage, debt bounds | **Example -- list_opportunities:** ```typescript // MCP input list_opportunities({ chain_ids: ["Mainnet", "Monad"], types: ["pool", "strategy"], assets: ["stable"], include_paused: false, }) // Maps to SDK const results = await sdk.opportunities.search({ chainIds: ["Mainnet", "Monad"], types: ["pool", "strategy"], assets: [Asset.STABLE], includePaused: false, }); ``` ### Analysis | MCP Tool | SDK Method | Description | |----------|-----------|-------------| | `get_pool_detail` | `sdk.pools.getDetail()` | Full pool parameters, allowed tokens, constraints | | `get_strategy_detail` | `sdk.strategies.getDetail()` | Strategy parameters, CM addresses, exit mechanics | | `get_metric_history` | `sdk.history.getMetric()` | Historical APY, TVL, utilization, borrow rates | | `get_events` | `sdk.events.getFeed()` | Parameter changes, governance events, pending changes | | `get_curator` | `sdk.curators.getProfile()` | Curator track record, bad debt history, managed pools | | `get_token_info` | `sdk.tokens.getProfiles()` | Token profiles and metadata | | `get_token_liquidity` | `sdk.tokens.getMarketData()` | Liquidity depth, market data for sizing checks | **Example -- get_pool_detail:** ```typescript // MCP input get_pool_detail({ chain_id: "Mainnet", pool_address: "0x...", }) // Maps to SDK const detail = await sdk.pools.getDetail({ chainId: "Mainnet", poolAddress: "0x...", }); ``` ### Action Preparation | MCP Tool | SDK Method | Description | |----------|-----------|-------------| | `prepare_deposit` | `sdk.positions.prepareDeposit()` | Build a pool deposit transaction | | `prepare_position` | `sdk.positions.prepareOpen()` | Build a strategy open transaction with route | | `simulate_deposit` | `sdk.positions.previewDeposit()` | Preview a pool deposit outcome | | `simulate_position` | `sdk.positions.previewOpen()` | Preview a strategy position outcome | **Example -- prepare_position:** ```typescript // MCP input prepare_position({ chain_id: "Mainnet", credit_manager: "0x...", collateral_token: "0xA0b8...USDC", collateral_amount: "100000000000", debt_amount: "200000000000", target_token: "0xae78...stETH", slippage_bps: 50, }) // Maps to SDK const route = await sdk.router.findOpenStrategyPath({ ... }); const tx = await sdk.accounts.openCreditAccount({ ... }); const preview = await sdk.previewTransaction(tx); ``` ### Execution | MCP Tool | SDK Method | Description | |----------|-----------|-------------| | `execute_transaction` | `sdk.transactions.execute()` | Submit a prepared and previewed transaction | The SDK does not hold private keys. The `execute_transaction` tool routes the `RawTx` through the configured signer. ### Monitoring | MCP Tool | SDK Method | Description | |----------|-----------|-------------| | `get_pool_status` | `sdk.pools.getStatus()` | Current pool state: rates, utilization, liquidity | | `get_position_status` | `sdk.accounts.getStatus()` | Position health factor, value, debt, alerts | **Example -- get_position_status:** ```typescript // MCP input get_position_status({ chain_id: "Mainnet", credit_account: "0x...", }) // Maps to SDK const status = await sdk.accounts.getStatus({ chainId: "Mainnet", creditAccount: "0x...", }); ``` ## Complete Mapping Reference | MCP Tool | SDK Method | Stage | |----------|-----------|-------| | `list_opportunities` | `sdk.opportunities.search()` | Discover | | `list_pools` | `sdk.pools.list()` | Discover | | `list_strategies` | `sdk.strategies.list()` | Discover | | `get_pool_detail` | `sdk.pools.getDetail()` | Analyze | | `get_strategy_detail` | `sdk.strategies.getDetail()` | Analyze | | `get_metric_history` | `sdk.history.getMetric()` | Analyze | | `get_events` | `sdk.events.getFeed()` | Analyze | | `get_curator` | `sdk.curators.getProfile()` | Analyze | | `get_token_info` | `sdk.tokens.getProfiles()` | Analyze | | `get_token_liquidity` | `sdk.tokens.getMarketData()` | Analyze | | `prepare_deposit` | `sdk.positions.prepareDeposit()` | Propose | | `prepare_position` | `sdk.positions.prepareOpen()` | Propose | | `simulate_deposit` | `sdk.positions.previewDeposit()` | Preview | | `simulate_position` | `sdk.positions.previewOpen()` | Preview | | `execute_transaction` | `sdk.transactions.execute()` | Execute | | `get_pool_status` | `sdk.pools.getStatus()` | Monitor | | `get_position_status` | `sdk.accounts.getStatus()` | Monitor | ## Runtime Modes The MCP Server inherits SDK runtime modes: **Core-Only Mode** -- chain access works, backend unavailable. Discovery, prepare, simulate, execute, and monitoring all work. History, curator profiles, and cached APY are degraded. **Enriched Mode** -- chain + backend available. Full history, metadata, human-readable events, and cached classification surfaces. All tool responses carry `freshness` metadata so the agent knows the data quality: ```typescript { asOf: "2025-04-07T10:30:00Z", sources: ["onchain", "backend-cache"], backendAvailable: true } ``` ## Learn More - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) -- how tools map to the 6-step loop - [Transaction Preview](https://docs.gearbox.finance/developers/ga-preview) -- the security gate between Propose and Execute - [Execution Modes](https://docs.gearbox.finance/developers/ga-execution) -- what happens after the agent calls `execute_transaction` ## Transaction Preview Source: https://docs.gearbox.finance/developers/ga-preview File: content/developers/ga-preview.mdx Transaction Preview is the universal security gate in Gearbox Agentic. A single SDK method -- `sdk.previewTransaction(tx)` -- simulates any Gearbox transaction and returns a full breakdown of what will happen before any funds are committed. ## Key Principle **Same bytes previewed = same bytes executed.** The input to `previewTransaction` is the actual `RawTx` -- the exact calldata that would be sent to the network. This is not a parameter-based estimate; it is a simulation of the real transaction. ## Transaction Lifecycle ```mermaid graph LR P[Propose] --> Pr[Preview] Pr --> V[Validate] V -->|pass| E[Execute] V -->|fail| P E --> M[Monitor] ``` ## Input The method accepts a `RawTx` -- the unsigned transaction built in the Propose stage: ```typescript interface RawTx { to: Address; calldata: Hex; value?: bigint; } ``` One method handles all Gearbox transactions: open credit account, close, adjust, add collateral, swap -- everything. The agent does not need to know which preview method to call. ## Output ```typescript const preview = await sdk.previewTransaction(tx); ``` The preview returns: ```typescript interface TransactionPreview { // Would this transaction execute? success: boolean; // Any concerns warnings: string[]; // Projected health factor after entry healthFactor?: number; // Estimated gas cost gasEstimate?: string; // Human-readable breakdown of multicall actions actions: Array<{ title: string; // e.g. "Deposit 100,000 USDC" description: string; // e.g. "Collateral added to credit account" protocol?: string; // e.g. "Curve", "1inch" }>; // Net token movements for the wallet balanceChanges: Array<{ token: TokenRef; delta: string; direction: "in" | "out"; }>; // Swap route details routes?: Array<{ tokenIn: TokenRef; tokenOut: TokenRef; amountIn: string; expectedOut: string; priceImpactBps?: number; dex?: string; }>; // Exit characteristics for this position exitInfo?: { hasDelayedWithdrawal: boolean; zeroSlippageAvailable: boolean; }; } ``` ## What the Agent Validates Before proceeding to execution, the agent checks every aspect of the preview: ```typescript // 1. Did simulation succeed? if (!preview.success) { throw new Error("Transaction would revert"); } // 2. Is health factor safe? if ((preview.healthFactor ?? 0) < 1.4) { // Go back to PROPOSE, reduce leverage } // 3. Any warnings? if (preview.warnings.length > 0) { // Evaluate each warning, decide to proceed or abort } // 4. Review balance changes for (const change of preview.balanceChanges) { // Verify no unexpected tokens leave the wallet // Verify amounts match expectations } // 5. Review actions for (const action of preview.actions) { // "Deposit 100,000 USDC" -- correct // "Borrow 200,000 USDC" -- correct // "Swap 200,000 USDC to stETH via 1inch" -- correct } // 6. Check swap routes for (const route of preview.routes ?? []) { if ((route.priceImpactBps ?? 0) > 100) { // > 1% price impact -- consider reducing size } } // 7. Understand exit conditions if (preview.exitInfo?.hasDelayedWithdrawal) { // Closing will involve phantom tokens -- factor into decision } ``` ## Verify URL The preview can be encoded as a URL for human review at [verify.gearbox.finance](https://verify.gearbox.finance). The verifier displays: - **Decoded calldata** -- the inner `MultiCall[]` structure decoded against Gearbox ABIs - **Actions** -- human-readable list of what the transaction does - **Balance changes** -- net token movements - **Health factor projection** -- projected HF after execution - **Warnings** -- any concerns flagged by the simulation This is the bridge between autonomous agents and human approval. The agent builds and previews; the human verifies and signs. ## Looping Back If the preview fails or shows unacceptable results, the agent adjusts and retries: ``` PREVIEW -> constraints fail -> PROPOSE (adjust parameters) -> PREVIEW ``` The agent can adjust leverage, debt amount, slippage tolerance, or select a different strategy entirely, then rebuild the transaction and preview again. ## Why One Method Traditional DeFi integrations require different preview methods for different transaction types. Gearbox uses a single `previewTransaction` that works for any `RawTx`. This means: - Agents do not need to know the transaction type to preview it - The same security gate applies to every operation - Independent verifiers can simulate any Gearbox transaction using the same method - The preview surface cannot be bypassed by using a different code path ## Learn More - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) -- how Preview fits into the 6-step loop - [Execution Modes](https://docs.gearbox.finance/developers/ga-execution) -- what happens after preview validation - [MCP Server](https://docs.gearbox.finance/developers/ga-mcp) -- `simulate_deposit` and `simulate_position` tools ## Execution Source: https://docs.gearbox.finance/developers/ga-execution File: content/developers/ga-execution.mdx How agents interact with the Gearbox protocol on-chain — and why it's safe. ## The Security Model A core principle of Gearbox Agentic: **the SDK builds transactions, but never signs them**. Transaction construction (what to do) is separated from transaction execution (actually doing it). This creates a clear security boundary. Every transaction goes through **preview** before execution: 1. **Build** — `sdk.positions.prepareOpen()` → produces a `RawTx { to, calldata, value }` 2. **Preview** — `sdk.previewTransaction(rawTx)` → simulates the exact bytes, returns success, health factor, actions, balance changes, warnings 3. **Validate** — agent (or human) checks: success = true, HF > threshold, no critical warnings 4. **Execute** — sign and send via wallet The same bytes that were previewed are the bytes that go on-chain. No deviation. ## Two Execution Modes | Mode | Trust Level | Best For | | --- | --- | --- | | [**Human-in-the-Loop**](https://docs.gearbox.finance/developers/ga-human-loop) | Agent proposes, human approves | High-value positions, institutional compliance, initial trust building | | [**Bot Execution**](https://docs.gearbox.finance/developers/ga-bot-execution) | Agent executes autonomously within bounds | Rebalancing, liquidation monitoring, automated management | Both modes use the same preview mechanism. The difference is who signs. ## Learn More - [Human-in-the-Loop](https://docs.gearbox.finance/developers/ga-human-loop) — verify.gearbox.finance approval flow - [Bot Execution](https://docs.gearbox.finance/developers/ga-bot-execution) — bounded on-chain permissions - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) — how Preview and Execute fit in the 6-step cycle ## Getting Started Source: https://docs.gearbox.finance/developers/ga-start File: content/developers/ga-start.mdx Gearbox Agentic lets AI agents and programmatic integrators discover yield opportunities, analyze risk, and prepare transactions across the Gearbox protocol. There are two ways to get started, depending on how you want to interact. ## Option 1: MCP Setup (Recommended for LLM Agents) If you are using an LLM agent like Claude, ChatGPT, or any MCP-compatible client, the fastest path is to install the **Gearbox MCP server**. This gives your agent direct access to 16+ tools for discovering pools, analyzing curators, simulating deposits, and more — all through natural language conversation. Best for: Claude Desktop, Cursor, Windsurf, custom MCP clients. [Set up MCP Server](https://docs.gearbox.finance/developers/ga-setup-mcp) ## Option 2: Build a Custom Agent with the SDK If you are building a standalone agent, bot, or backend service, you can integrate the **Gearbox SDK** directly into your TypeScript application. The SDK is the canonical public API that the MCP server itself wraps. This gives you full control over the agent loop: discover, analyze, propose, preview, execute, and monitor. Best for: automated vaults, rebalancing bots, custom dashboards, programmatic integrators. [Build your first agent](https://docs.gearbox.finance/developers/ga-first-agent) ## The Agent Loop Both paths follow the same six-stage lifecycle: ```mermaid graph LR D[Discover] --> A[Analyze] A --> P[Propose] P --> Pr[Preview] Pr -->|constraints fail| P Pr --> E[Execute] E --> M[Monitor] M -->|rebalance| D M -->|adjust| P ``` | Stage | What happens | | -------- | ----------------------------------------------------- | | Discover | Find yield opportunities across chains | | Analyze | Inspect details, history, risk, curator quality | | Propose | Build intended transaction | | Preview | Simulate exact on-chain outcome before committing | | Execute | Sign and submit the transaction | | Monitor | Watch the live position and react to changes | ## Next Steps - [MCP Setup](https://docs.gearbox.finance/developers/ga-setup-mcp) — install and configure the MCP server - [First Agent](https://docs.gearbox.finance/developers/ga-first-agent) — walk through a complete discover-to-preview flow ## MCP Setup Source: https://docs.gearbox.finance/developers/ga-setup-mcp File: content/developers/ga-setup-mcp.mdx Connect the Gearbox MCP server to your AI coding tool and start interacting with the protocol. ## Installation Add the Gearbox MCP server to your client. The configuration is the same across all clients — just the config file location differs. ```json { "mcpServers": { "gearbox": { "command": "npx", "args": ["@gearbox-protocol/mcp-server"] } } } ``` ### Where to put this config | Client | Config File | | --- | --- | | **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) | | **Claude Code** | `.claude/settings.json` (project) or `~/.claude/settings.json` (global) | | **VS Code (Copilot)** | `.vscode/settings.json` — use `"mcp": { "servers": { ... } }` format | | **Cursor** | `.cursor/mcp.json` in project root | | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | After adding the config, restart your client. ## Verify the Connection Once configured, restart your MCP client and verify the connection by asking the agent to list available opportunities: > List all available Gearbox yield opportunities on Mainnet The agent should call `list_opportunities` and return a list of pools and strategies with APY, TVL, and access information. If you see results, the setup is working. ## What You Can Do The MCP server exposes 16 tools organized by the agent loop stages: ### Discover | Tool | Description | | -------------------- | ---------------------------------------------------- | | `list_opportunities` | Find opportunities across chains, filtered by asset type, chain, and kind | | `list_pools` | List pool (passive lending) opportunities | | `list_strategies` | List strategy (leveraged) opportunities | ### Analyze | Tool | Description | | -------------------- | ---------------------------------------------------- | | `get_pool_detail` | Full pool detail: allowed tokens, rates, capacity | | `get_strategy_detail`| Strategy detail: leverage, collaterals, exit modes | | `get_metric_history` | Historical APY, TVL, utilization over a time period | | `get_events` | Recent parameter changes and governance events | | `get_curator` | Curator profile: track record, bad debt history, managed TVL | | `get_token_info` | Token profiles and metadata | | `get_token_liquidity`| On-chain liquidity depth and market data | ### Propose | Tool | Description | | -------------------- | ---------------------------------------------------- | | `prepare_deposit` | Build a deposit transaction for a pool | | `prepare_position` | Build a leveraged position transaction for a strategy| ### Preview | Tool | Description | | -------------------- | ---------------------------------------------------- | | `simulate_deposit` | Simulate a deposit and see expected outcomes | | `simulate_position` | Simulate a leveraged position opening | ### Execute & Monitor | Tool | Description | | -------------------- | ---------------------------------------------------- | | `execute_transaction`| Submit a prepared transaction to the blockchain | | `get_pool_status` | Current pool state: rates, utilization, TVL | | `get_position_status`| Current position health factor, value, and alerts | ## Multi-Chain Support All tools are chain-aware. You can query across multiple chains or target a specific one: > Show me stablecoin opportunities on both Mainnet and Monad > Get pool details for 0x... on chain 1 Every entity reference includes a `chainId`, so there is no ambiguity when the same token or contract exists on multiple chains. ## Next Steps - [First Agent](https://docs.gearbox.finance/developers/ga-first-agent) — walk through a complete discover-to-preview flow using MCP tools - [Getting Started](https://docs.gearbox.finance/developers/ga-start) — overview of both integration paths ## First Agent Source: https://docs.gearbox.finance/developers/ga-first-agent File: content/developers/ga-first-agent.mdx This walkthrough takes you through a complete **discover, analyze, preview** flow using MCP tools. By the end, you will have found a yield opportunity, researched its risk profile, and simulated a deposit — all through natural conversation with an LLM agent. Before starting, make sure you have [set up the MCP server](https://docs.gearbox.finance/developers/ga-setup-mcp). ## Step 1: Discover Opportunities Start by asking your agent to find yield opportunities. The `list_opportunities` tool searches across chains and returns pools and strategies with headline APY, TVL, and access requirements. > Find all permissionless pool opportunities on Mainnet with APY above 5% The agent calls: ``` Tool: list_opportunities Input: { "types": ["pool"], "chain_ids": [1], "include_paused": false } ``` You get back a list of opportunities, each with: - **title** — human-readable name (e.g. "USDC Lending Pool") - **headlineApy** — current total APY - **tvlUsd** — total value locked - **depositToken** — what you deposit (USDC, WETH, etc.) - **access** — whether it is permissionless or requires KYC - **risk.warnings** — any red flags Pick a candidate that matches your criteria. Note its `chainId` and `poolAddress` for the next step. ## Step 2: Analyze the Candidate Now dig deeper into the pool you selected. This stage uses multiple tools to build a full picture of risk and return. ### 2a. Get Pool Details > Show me the full details for pool 0xABC... on Mainnet ``` Tool: get_pool_detail Input: { "chain_id": 1, "address": "0xABC..." } ``` This returns the pool's allowed tokens, current utilization, borrow rates, capacity limits, and the curator who manages its risk parameters. Pay attention to: - **allowedTokens** — which collateral the pool accepts and their liquidation thresholds - **availableLiquidity** — how much room remains for deposits - **isPaused** — whether the pool is currently active ### 2b. Research the Curator Every pool is managed by a curator who sets risk parameters. Check their track record: > Who is the curator for this pool? What is their track record? ``` Tool: get_curator Input: { "curator_id": "steakhouse" } ``` The curator profile reveals: - **badDebtEvents** and **badDebtUsd** — historical losses (lower is better) - **parameterChanges30d** — how actively they manage the pool (too few changes may signal neglect, too many may signal instability) - **totalTvlUsd** — total value they manage across all pools - **isActive** — whether they are still actively managing ### 2c. Check Historical Performance > Show me the APY history for this pool over the last 30 days ``` Tool: get_metric_history Input: { "chain_id": 1, "target": "0xABC...", "metric": "apy", "period_days": 30 } ``` Compare the current APY against the 30-day trend. A pool showing 12% APY today but averaging 4% over the month may be experiencing a temporary spike. Look for: - **Stability** — low standard deviation means more predictable returns - **Trend** — is APY trending up or down? - **Yield type** — organic yield is more sustainable than incentivized yield ## Step 3: Prepare a Deposit Once you are satisfied with the analysis, prepare the deposit transaction. This builds the transaction without sending it. > Prepare a deposit of 10,000 USDC into pool 0xABC... on Mainnet ``` Tool: prepare_deposit Input: { "chain_id": 1, "pool_address": "0xABC...", "amount": "10000000000", "token": "USDC" } ``` The tool returns a `RawTx` object containing the transaction `to` address and `calldata`. This is the exact transaction that would be sent to the blockchain, but it has not been signed or submitted yet. ## Step 4: Preview the Transaction Before committing any funds, simulate the transaction to see exactly what will happen on-chain. > Simulate this deposit and show me the expected outcome ``` Tool: simulate_deposit Input: { "raw_tx": { "to": "0x...", "calldata": "0x..." } } ``` The simulation returns a full breakdown: ``` { "success": true, "healthFactor": 2.1, "warnings": [], "actions": [ { "title": "Deposit 10,000 USDC", "description": "Added to lending pool" } ], "balanceChanges": [ { "token": "USDC", "delta": "-10000", "direction": "out" }, { "token": "dUSDC", "delta": "9987.5", "direction": "in" } ], "gasEstimate": "142000" } ``` ### What to Check Before approving the transaction, verify these conditions: 1. **`success` is `true`** — the transaction would not revert 2. **`healthFactor` > 1.4** — sufficient safety margin (for leveraged positions) 3. **No critical warnings** — review any items in the `warnings` array 4. **Balance changes look correct** — you are sending the expected token and receiving the right pool token 5. **Actions match your intent** — the human-readable action list should describe exactly what you asked for 6. **Gas estimate is reasonable** — no unexpectedly high gas costs ## Step 5: Review Before Execution At this point, you have a fully simulated transaction with confirmed outcomes. Before executing: ### Option A: Execute Through the Agent If your agent has signing capabilities and you trust the setup: > Execute this transaction The agent calls `execute_transaction` with the raw transaction data. The SDK submits it to the blockchain and returns a transaction hash. ### Option B: Manual Review and Approval For higher security, review the transaction independently before signing. The transaction calldata can be inspected at: **[verify.gearbox.finance](https://verify.gearbox.finance)** — paste the raw transaction to decode the multicall, view each step, and simulate on a fork before signing in your wallet. This is the recommended approach for large deposits or when the agent is not trusted with automatic execution. ### Understanding the Preview Output | Field | What it tells you | | ---------------- | ------------------------------------------------------- | | `success` | Whether the transaction would execute without reverting | | `healthFactor` | Projected solvency ratio (only for leveraged positions) | | `warnings` | Risk flags — review each one before proceeding | | `actions` | Step-by-step breakdown of what the multicall does | | `balanceChanges` | Net token movements for your wallet | | `routes` | Swap paths used, including price impact per hop | | `exitInfo` | Whether closing will involve delayed withdrawal | | `gasEstimate` | Estimated gas cost in wei | ## Full Flow Summary ```mermaid graph TD A["1. list_opportunities\nFind pools with APY > 5%"] --> B["2a. get_pool_detail\nInspect allowed tokens, rates"] B --> C["2b. get_curator\nCheck curator track record"] C --> D["2c. get_metric_history\nReview 30-day APY trend"] D --> E["3. prepare_deposit\nBuild the transaction"] E --> F["4. simulate_deposit\nVerify success, health, warnings"] F --> G{"All checks pass?"} G -->|Yes| H["5. Execute or review manually"] G -->|No| I["Adjust parameters\nor pick another pool"] I --> E ``` ## Next Steps - [MCP Setup](https://docs.gearbox.finance/developers/ga-setup-mcp) — configure additional chains or environment variables - [Getting Started](https://docs.gearbox.finance/developers/ga-start) — overview of both MCP and SDK integration paths ## Human-in-the-Loop Source: https://docs.gearbox.finance/developers/ga-human-loop File: content/developers/ga-human-loop.mdx The Human-in-the-Loop execution mode means the agent builds and previews transactions, but a human reviews and approves every action before it goes on-chain. ## The Flow ```mermaid flowchart LR Agent[Agent builds tx] --> Preview[Preview tx] Preview --> Encode[Encode preview URL] Encode --> Verify[verify.gearbox.finance] Verify --> Human[Human reviews] Human -->|approve| Sign[Sign in wallet] Sign --> Chain[On-chain] Human -->|reject| Agent ``` 1. **Agent builds** — `sdk.positions.prepareOpen(params)` → `RawTx` 2. **Agent previews** — `sdk.previewTransaction(rawTx)` → `TransactionPreview` 3. **Agent encodes** — preview data encoded as URL: `verify.gearbox.finance/?tx=` 4. **Human reviews** at verify.gearbox.finance: - Decoded calldata (human-readable actions) - Token balance changes (what goes in, what comes out) - Swap routes and price impact - Projected Health Factor - Warnings and concerns 5. **Human approves** → signs in wallet (MetaMask, Safe, etc.) 6. **Transaction executes** — exact bytes from preview ## Why This Works - **Same bytes** — what was previewed is what gets signed. No deviation. - **No key management** — the agent never holds private keys - **Auditable** — the preview URL creates a shareable record of what was proposed - **LLM-friendly** — the preview actions are human-readable, so both the agent and the human can understand them ## When to Use | Scenario | Recommendation | | --- | --- | | First deployment / building trust | Always human-in-the-loop | | High-value positions (>\$100K) | Human-in-the-loop | | Institutional / compliance requirements | Human-in-the-loop | | Routine rebalancing | Consider [Bot Execution](https://docs.gearbox.finance/developers/ga-bot-execution) | ## Example ```typescript // Agent builds the transaction const rawTx = await sdk.positions.prepareOpen({ chainId: "Mainnet", strategy: strategyId, depositToken: "USDC", depositAmount: 10_000n * 10n ** 6n, leverage: 3, }); // Agent previews const preview = await sdk.previewTransaction(rawTx); if (preview.success && preview.healthFactor > 1.4) { // Encode for human review const verifyUrl = `https://verify.gearbox.finance/?tx=${encodePreview(preview)}`; console.log(`Review and approve: ${verifyUrl}`); // Human opens URL, reviews, signs in wallet } ``` ## Learn More - [Bot Execution](https://docs.gearbox.finance/developers/ga-bot-execution) — autonomous execution with bounded permissions - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) — where Preview and Execute fit in the cycle ## Bot Execution Source: https://docs.gearbox.finance/developers/ga-bot-execution File: content/developers/ga-bot-execution.mdx Bot Execution mode allows an agent to operate autonomously on-chain, but within strictly bounded permissions. The agent has its own address with explicit, revocable permissions granted per Credit Account. ## How It Works The Gearbox [Bot System](https://docs.gearbox.finance/developers/gm-bots) provides granular permission control: 1. **Account owner grants permissions** — specific operations allowed for a specific bot on a specific account 2. **Agent executes via `botMulticall`** — operations go through CreditFacade with permission checks 3. **Protocol enforces boundaries** — any unpermitted operation reverts the transaction 4. **Solvency check still applies** — bot multicalls undergo the same HF >= 1 check as user multicalls ## Permission Bitmask Each permission is a bit flag. The account owner composes a bitmask of allowed operations: | Permission | What the Bot Can Do | | --- | --- | | `ADD_COLLATERAL` | Deposit tokens into the account | | `INCREASE_DEBT` | Borrow more from the pool | | `DECREASE_DEBT` | Repay debt | | `WITHDRAW_COLLATERAL` | Remove tokens from the account | | `UPDATE_QUOTA` | Adjust token quotas | | `EXTERNAL_CALLS` | Call DeFi protocols via adapters | An agent managing a yield strategy might need `ADD_COLLATERAL | EXTERNAL_CALLS | UPDATE_QUOTA` but explicitly **not** `WITHDRAW_COLLATERAL` — preventing it from removing funds. ## Safety Model - **Scoped** — permissions are per `(bot, creditManager, creditAccount)` tuple. One bot's permissions on one account don't affect any other. - **Revocable** — the account owner can revoke at any time by setting permissions to 0 - **Immutable boundary** — bots cannot modify their own permissions - **DAO forbid list** — the DAO can globally forbid a malicious bot address - **Same solvency rules** — bot operations undergo identical collateral checks ## Monitoring Bot Activity The agent (or a separate monitor) should track bot state: ```typescript const position = await sdk.accounts.getStatus({ chainId: "Mainnet", creditAccount: accountAddress, }); // Check active bots for (const bot of position.bots) { console.log(`Bot: ${bot.address}, permissions: ${bot.permissions}`); } ``` ## When to Use | Scenario | Recommendation | | --- | --- | | Automated rebalancing | Bot with EXTERNAL_CALLS + UPDATE_QUOTA | | Liquidation protection | Bot with ADD_COLLATERAL + DECREASE_DEBT | | Full autonomy | Bot with all permissions (high trust required) | | Withdrawal needed | Prefer [Human-in-the-Loop](https://docs.gearbox.finance/developers/ga-human-loop) | ## Combining Both Modes A common pattern: use **Bot Execution** for routine operations (rebalancing, quota adjustments) and **Human-in-the-Loop** for high-impact actions (opening new positions, withdrawals, strategy changes). The agent runs autonomously within its bounded permissions, but when it needs to do something outside those bounds, it generates a preview URL for human approval. ## Learn More - [Human-in-the-Loop](https://docs.gearbox.finance/developers/ga-human-loop) — preview + verify flow for human approval - [Bot System](https://docs.gearbox.finance/developers/gm-bots) — detailed bot permission mechanics - [The Agent Loop](https://docs.gearbox.finance/developers/ga-agent-loop) — how execution fits in the 6-step cycle