# STEAMM Developer Integration Guide

## Table of Contents

1. [Overview](#overview)
2. [Architecture](#architecture)
3. [Quick Start](#quick-start)
4. [Pool Types](#pool-types)
5. [Creating Pools](#creating-pools)
6. [Liquidity Operations](#liquidity-operations)
7. [Swapping](#swapping)
8. [Suilend Integration](#suilend-integration)
9. [Advanced Usage](#advanced-usage)
10. [Error Handling](#error-handling)
11. [Practical Integration](#practical-integration)
12. [Code Examples](#code-examples)
13. [Reference](#reference)

## Overview

STEAMM is a next-generation Automated Market Maker (AMM) protocol on Sui that maximizes capital efficiency through:

* **Modular Quoter System**: Support for multiple AMM types (Constant Product, Oracle-based, Stable)
* **Liquidity Reutilization**: Optional integration with Suilend lending markets for yield generation
* **Shared Liquidity Model**: Banks aggregate liquidity across pools for improved efficiency
* **Yield-bearing LP Tokens**: LPs earn both trading fees and lending yields

### Key Benefits for Developers

1. **Flexible Integration**: Choose between simple AMM pools or advanced yield-bearing pools
2. **Capital Efficiency**: Up to 80% of idle liquidity can earn lending yields
3. **Multiple AMM Types**: Constant product, oracle-based, and stable coin AMMs
4. **Composable Design**: Easy integration with existing DeFi protocols

## Architecture

### Core Components

```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│    Registry     │    │      Pool       │    │      Bank       │
│   (Global)      │◄───┤   (Per Pair)    │◄───┤  (Per Token)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                                ▲                        ▲
                                │                        │
                       ┌─────────────────┐    ┌─────────────────┐
                       │     Quoter      │    │  Suilend Market │
                       │  (AMM Logic)    │    │   (Optional)    │
                       └─────────────────┘    └─────────────────┘
```

* **Registry**: Global tracker of all pools and banks
* **Pool**: Core AMM logic with modular quoter system
* **Bank**: Manages liquidity and optional Suilend integration
* **Quoter**: Pluggable AMM algorithms (CPMM, Oracle, Stable)

### Token Flow

```
User Tokens (USDC, SUI)
    ↓ (mint_btoken)
bTokens (bUSDC, bSUI)
    ↓ (Pool operations)
LP Tokens
    ↓ (Optionally deployed to)
Suilend cTokens (earning yield)
```

## Quick Start

### Dependencies

Add to your `Move.toml`:

```toml
[dependencies]
steamm = { git = "https://github.com/solendprotocol/steamm.git", subdir = "contracts/steamm", rev = "main" }
steamm_scripts = { git = "https://github.com/solendprotocol/steamm.git", subdir = "contracts/steamm_scripts", rev = "main" }
suilend = { git = "https://github.com/solendprotocol/suilend.git", subdir = "contracts/suilend", rev = "main" }
oracles = { git = "https://github.com/solendprotocol/suilend.git", subdir = "contracts/oracles", rev = "main" }
```

### Basic Pool Creation (No Suilend)

```move
use steamm::cpmm;
use steamm::registry::Registry;

// Create a simple constant product AMM pool
public fun create_basic_pool<A, B, LpType: drop>(
    registry: &mut Registry,
    meta_a: &CoinMetadata<A>,
    meta_b: &CoinMetadata<B>,
    meta_lp: &mut CoinMetadata<LpType>,
    lp_treasury: TreasuryCap<LpType>,
    swap_fee_bps: u64,  // e.g., 30 = 0.3%
    offset: u64,        // Usually 0 for standard pools
    ctx: &mut TxContext,
): Pool<A, B, CpQuoter, LpType> {
    cpmm::new<A, B, LpType>(
        registry,
        swap_fee_bps,
        offset,
        meta_a,
        meta_b,
        meta_lp,
        lp_treasury,
        ctx,
    )
}
```

### Basic Liquidity Operations

```move
// Add liquidity to a basic pool
public fun add_liquidity<A, B, Quoter: store, LpType: drop>(
    pool: &mut Pool<A, B, Quoter, LpType>,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    max_a: u64,
    max_b: u64,
    ctx: &mut TxContext,
): Coin<LpType> {
    let (lp_tokens, _deposit_result) = pool.deposit_liquidity(
        coin_a,
        coin_b,
        max_a,
        max_b,
        ctx,
    );
    lp_tokens
}
```

## Pool Types

STEAMM supports three main quoter types:

### 1. Constant Product AMM (CPMM)

Best for: General trading pairs, volatile assets

```move
// Formula: (x + offset) * y = k
use steamm::cpmm::CpQuoter;

let pool = cpmm::new<TokenA, TokenB, LpToken>(
    registry,
    30,    // 0.3% fee
    0,     // No offset
    meta_a, meta_b, meta_lp,
    lp_treasury,
    ctx,
);
```

**Features:**

* Classic x\*y=k formula with optional offset
* Suitable for most token pairs
* Predictable slippage curves

### 2. Oracle AMM (OMM)

Best for: Stablecoin pairs, reduced impermanent loss

```move
use steamm::omm_v2::OracleQuoterV2;
use oracles::oracles::OracleRegistry;

// Complete Oracle AMM creation - requires all parameters
let pool = omm_v2::new<P, A, B, B_A, B_B, LpType>(
    registry,
    lending_market,
    meta_a,           // Underlying token A metadata
    meta_b,           // Underlying token B metadata
    meta_b_a,         // bToken A metadata
    meta_b_b,         // bToken B metadata
    meta_lp,          // LP token metadata
    lp_treasury,      // LP token treasury cap
    oracle_registry,  // Oracle registry instance
    oracle_index_a,   // Oracle index for token A
    oracle_index_b,   // Oracle index for token B
    amplifier,        // Higher = more stable pricing (e.g., 10-1000)
    swap_fee_bps,     // Swap fee in basis points
    ctx,
);
```

**Features:**

* Uses external price feeds
* Dynamic fees based on volatility
* Better for correlated assets
* Requires Suilend integration (bTokens only)

### 3. Stable AMM

Best for: Highly correlated assets (USDC/USDT)

```move
// Currently using CPMM with low offset for stable pairs
// Dedicated stable quoter implementation coming soon
```

## Creating Pools

### Simple Pool (No Banks)

For basic AMM functionality without yield farming:

```move
module my_protocol::simple_amm {
    use steamm::cpmm;
    use steamm::pool::Pool;
    use steamm::registry::Registry;

    public struct MyLpToken has drop {}

    public fun create_usdc_sui_pool(
        registry: &mut Registry,
        ctx: &mut TxContext,
    ): Pool<USDC, SUI, CpQuoter, MyLpToken> {
        // Setup coin metadata (implementation details omitted)
        let (meta_usdc, meta_sui, mut meta_lp, lp_treasury) =
            setup_coin_metadata(ctx);

        cpmm::new<USDC, SUI, MyLpToken>(
            registry,
            30,     // 0.3% swap fee
            0,      // No offset
            &meta_usdc,
            &meta_sui,
            &mut meta_lp,
            lp_treasury,
            ctx,
        )
    }
}
```

### Advanced Pool with Suilend Integration

For yield-bearing pools that earn lending yields:

```move
module my_protocol::yield_amm {
    use steamm::bank;
    use steamm::cpmm;
    use suilend::lending_market::LendingMarket;

    public struct MyBTokenUSDC has drop {}
    public struct MyBTokenSUI has drop {}
    public struct MyLpToken has drop {}

    public fun create_yield_pool<P>(
        registry: &mut Registry,
        lending_market: &LendingMarket<P>,
        global_admin: &GlobalAdmin,
        ctx: &mut TxContext,
    ): (
        Pool<MyBTokenUSDC, MyBTokenSUI, CpQuoter, MyLpToken>,
        Bank<P, USDC, MyBTokenUSDC>,
        Bank<P, SUI, MyBTokenSUI>,
    ) {
        // Setup metadata
        let (
            meta_usdc, meta_sui, mut meta_lp,
            mut meta_b_usdc, mut meta_b_sui,
            lp_treasury, b_usdc_treasury, b_sui_treasury
        ) = setup_all_metadata(ctx);

        // Create banks first using the package function
        let mut bank_usdc = bank::create_bank<P, USDC, MyBTokenUSDC>(
            registry,
            &meta_usdc,
            &mut meta_b_usdc,
            b_usdc_treasury,
            lending_market,
            ctx,
        );

        let mut bank_sui = bank::create_bank<P, SUI, MyBTokenSUI>(
            registry,
            &meta_sui,
            &mut meta_b_sui,
            b_sui_treasury,
            lending_market,
            ctx,
        );

        // Initialize lending (optional)
        bank_usdc.init_lending(
            global_admin,
            lending_market,
            8000,  // 80% target utilization
            1000,  // 10% buffer
            ctx,
        );

        bank_sui.init_lending(
            global_admin,
            lending_market,
            8000,  // 80% target utilization
            1000,  // 10% buffer
            ctx,
        );

        // Create pool with bToken types
        let pool = cpmm::new<MyBTokenUSDC, MyBTokenSUI, MyLpToken>(
            registry,
            30,     // 0.3% swap fee
            0,      // No offset
            &meta_b_usdc,
            &meta_b_sui,
            &mut meta_lp,
            lp_treasury,
            ctx,
        );

        (pool, bank_usdc, bank_sui)
    }
}
```

## Liquidity Operations

### Adding Liquidity

#### Simple Pool

```move
public fun add_liquidity_simple<A, B, Quoter: store, LpType: drop>(
    pool: &mut Pool<A, B, Quoter, LpType>,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    max_a: u64,
    max_b: u64,
    ctx: &mut TxContext,
): Coin<LpType> {
    let (lp_tokens, _) = pool.deposit_liquidity(
        coin_a, coin_b, max_a, max_b, ctx
    );
    lp_tokens
}
```

#### Yield Pool (with Banks) - Recommended Approach

```move
use steamm_scripts::pool_script_v2;

public fun add_liquidity_with_yield<P, A, B, BTokenA, BTokenB, Quoter: store, LpType: drop>(
    pool: &mut Pool<BTokenA, BTokenB, Quoter, LpType>,
    bank_a: &mut Bank<P, A, BTokenA>,
    bank_b: &mut Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    max_a: u64,
    max_b: u64,
    clock: &Clock,
    ctx: &mut TxContext,
): Coin<LpType> {
    pool_script_v2::deposit_liquidity(
        pool, bank_a, bank_b, lending_market,
        coin_a, coin_b, max_a, max_b, clock, ctx
    )
}
```

### Removing Liquidity

#### Simple Pool

```move
public fun remove_liquidity<A, B, Quoter: store, LpType: drop>(
    pool: &mut Pool<A, B, Quoter, LpType>,
    lp_tokens: Coin<LpType>,
    min_a: u64,
    min_b: u64,
    ctx: &mut TxContext,
): (Coin<A>, Coin<B>) {
    let (coin_a, coin_b, _) = pool.redeem_liquidity(lp_tokens, min_a, min_b, ctx);
    (coin_a, coin_b)
}
```

#### Yield Pool - Recommended Approach

```move
public fun remove_liquidity_with_yield<P, A, B, BTokenA, BTokenB, Quoter: store, LpType: drop>(
    pool: &mut Pool<BTokenA, BTokenB, Quoter, LpType>,
    bank_a: &mut Bank<P, A, BTokenA>,
    bank_b: &mut Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    lp_tokens: Coin<LpType>,
    min_a: u64,
    min_b: u64,
    clock: &Clock,
    ctx: &mut TxContext,
): (Coin<A>, Coin<B>) {
    pool_script_v2::redeem_liquidity(
        pool, bank_a, bank_b, lending_market,
        lp_tokens, min_a, min_b, clock, ctx
    )
}
```

## Swapping

### Understanding STEAMM's Swap Model

STEAMM uses an **intent/execute pattern** for swaps, especially important for yield pools:

1. **Intent**: Declare intention to swap and get a quote
2. **Provision**: Banks provision necessary liquidity from Suilend
3. **Execute**: Perform the actual swap
4. **Rebalance**: Optionally rebalance bank utilization

### Simple Pool Swaps

```move
public fun swap_simple<A, B, Quoter: store, LpType: drop>(
    pool: &mut Pool<A, B, Quoter, LpType>,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    a2b: bool,          // true = A->B, false = B->A
    amount_in: u64,
    min_amount_out: u64,
    ctx: &mut TxContext,
) {
    // For CPMM pools
    pool.cpmm_swap(coin_a, coin_b, a2b, amount_in, min_amount_out, ctx);
}
```

### Yield Pool Swaps - Recommended Approach

**Always use `pool_script_v2` for yield pools** - it handles the complex bToken conversions:

```move
use steamm_scripts::pool_script_v2;

public fun swap_cpmm_with_yield<P, A, B, BTokenA, BTokenB, LpType: drop>(
    pool: &mut Pool<BTokenA, BTokenB, CpQuoter, LpType>,
    bank_a: &mut Bank<P, A, BTokenA>,
    bank_b: &mut Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    a2b: bool,
    amount_in: u64,
    min_amount_out: u64,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    pool_script_v2::cpmm_swap(
        pool, bank_a, bank_b, lending_market,
        coin_a, coin_b, a2b, amount_in, min_amount_out,
        clock, ctx
    );
}
```

### Oracle Pool Swaps

```move
public fun swap_oracle<P, A, B, B_A, B_B, LpType: drop>(
    pool: &mut Pool<B_A, B_B, OracleQuoterV2, LpType>,
    bank_a: &mut Bank<P, A, B_A>,
    bank_b: &mut Bank<P, B, B_B>,
    lending_market: &LendingMarket<P>,
    oracle_price_a: OraclePriceUpdate,
    oracle_price_b: OraclePriceUpdate,
    coin_a: &mut Coin<A>,
    coin_b: &mut Coin<B>,
    a2b: bool,
    amount_in: u64,
    min_amount_out: u64,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    pool_script_v2::omm_v2_swap(
        pool, bank_a, bank_b, lending_market,
        oracle_price_a, oracle_price_b,
        coin_a, coin_b, a2b, amount_in, min_amount_out,
        clock, ctx
    );
}
```

### Getting Quotes

**Always get quotes before executing swaps:**

```move
// For simple pools
public fun get_swap_quote<A, B, Quoter: store, LpType: drop>(
    pool: &Pool<A, B, Quoter, LpType>,
    amount_in: u64,
    a2b: bool,
): SwapQuote {
    pool.cpmm_quote_swap(amount_in, a2b)
}

// For yield pools with banks - RECOMMENDED
public fun get_yield_swap_quote<P, A, B, BTokenA, BTokenB, LpType: drop>(
    pool: &Pool<BTokenA, BTokenB, CpQuoter, LpType>,
    bank_a: &Bank<P, A, BTokenA>,
    bank_b: &Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    a2b: bool,
    amount_in: u64,
    clock: &Clock,
): SwapQuote {
    pool_script_v2::quote_cpmm_swap(
        pool, bank_a, bank_b, lending_market,
        a2b, amount_in, clock
    )
}
```

### Script Versions: When to Use Which

* **`pool_script`**: Original version, use for compatibility with older code
* **`pool_script_v2`**: **RECOMMENDED** - cleaner API, better error handling
* **Direct pool calls**: Only for simple pools without banks

## Suilend Integration

### Understanding Banks

Banks are the key component that enables Suilend integration:

* **Purpose**: Aggregate liquidity from multiple pools
* **Yield Generation**: Deploy idle liquidity to Suilend for lending yields
* **bTokens**: Yield-bearing representations of underlying tokens (note: singular "btoken")
* **Utilization Management**: Maintain liquidity buffers for instant swaps

### Bank Lifecycle

1. **Creation**: `bank::create_bank()` - Creates bank for a token type
2. **Lending Setup**: `bank.init_lending()` - Enables Suilend integration
3. **Operation**: Automatic yield generation and liquidity management
4. **Rebalancing**: Periodic adjustment of Suilend exposure

### Working with bTokens

```move
// Convert underlying tokens to bTokens
let btokens = bank.mint_btoken(
    lending_market,
    &mut coin,
    amount,
    clock,
    ctx
);

// Convert bTokens back to underlying tokens
let underlying = bank.burn_btoken(
    lending_market,
    &mut btokens,
    btoken_amount,
    clock,
    ctx
);
```

### Utilization Parameters

```move
// Configure how much liquidity gets deployed to Suilend
bank.init_lending(
    global_admin,
    lending_market,
    8000,  // target_utilization_bps: 80% deployed to Suilend
    1000,  // utilization_buffer_bps: 10% buffer (70-90% range)
    ctx,
);
```

**Target Utilization**: Percentage of bank funds deployed to Suilend\
**Buffer**: Allowed deviation before rebalancing occurs

### Benefits of Suilend Integration

1. **Additional Yield**: LPs earn trading fees + lending yields
2. **Capital Efficiency**: Up to 80% of idle liquidity generates yield
3. **Shared Liquidity**: Multiple pools share deeper liquidity
4. **Automatic Management**: Protocol handles Suilend interactions

### When to Use Suilend Integration

**Use Suilend Integration When:**

* You want maximum yield for LPs
* Pool expects significant idle periods
* Working with established tokens (USDC, SUI, etc.)
* You can manage the additional complexity

**Use Simple Pools When:**

* Rapid development needed
* Working with new/experimental tokens
* Minimizing smart contract dependencies
* Token doesn't have Suilend market

## Error Handling

### Common Error Codes

| Error                    | Code | Description                          | Solution                                  |
| ------------------------ | ---- | ------------------------------------ | ----------------------------------------- |
| `ESlippageExceeded`      | 0    | Output below minimum                 | Increase slippage tolerance               |
| `EInsufficientBankFunds` | 9    | Bank liquidity too low               | Wait for rebalancing or provide liquidity |
| `ELendingAlreadyActive`  | 5    | Bank already has lending initialized | Check bank state before init              |
| `EInvalidBTokenDecimals` | 1    | bToken must have 9 decimals          | Fix token metadata                        |
| `EInvalidLpDecimals`     | 0    | LP token must have 9 decimals        | Fix LP token metadata                     |
| `EEmptyBank`             | 1    | Bank has no funds                    | Add liquidity first                       |
| `EInvariantViolation`    | 0    | CPMM invariant broken                | Check swap parameters                     |

### Error Handling Patterns

```move
// Always check for sufficient liquidity before large operations
public fun safe_swap_with_check<P, A, B, BTokenA, BTokenB, LpType: drop>(
    pool: &Pool<BTokenA, BTokenB, CpQuoter, LpType>,
    bank_a: &Bank<P, A, BTokenA>,
    bank_b: &Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    amount_in: u64,
    a2b: bool,
    clock: &Clock,
) {
    // Get quote first to check if swap is possible
    let quote = pool_script_v2::quote_cpmm_swap(
        pool, bank_a, bank_b, lending_market,
        a2b, amount_in, clock
    );

    // Check if we got a reasonable output
    assert!(quote.amount_out() > 0, EInsufficientLiquidity);

    // Proceed with actual swap...
}

// Check bank utilization before large operations
public fun check_bank_health<P, T, BToken>(
    bank: &Bank<P, T, BToken>,
    lending_market: &LendingMarket<P>,
    clock: &Clock,
): bool {
    bank.needs_rebalance(lending_market, clock).needs_rebalance()
}
```

## Practical Integration

### Integrating STEAMM into Your DeFi Protocol

#### 1. Router Pattern

```move
module my_protocol::dex_router {
    use steamm_scripts::pool_script_v2;

    public struct RouteConfig<phantom A, phantom B> has store {
        pool_id: ID,
        bank_a_id: ID,
        bank_b_id: ID,
        fee_bps: u64,
    }

    public fun execute_route<P, A, B, BTokenA, BTokenB, LpType: drop>(
        pool: &mut Pool<BTokenA, BTokenB, CpQuoter, LpType>,
        bank_a: &mut Bank<P, A, BTokenA>,
        bank_b: &mut Bank<P, B, BTokenB>,
        lending_market: &LendingMarket<P>,
        coin_in: Coin<A>,
        min_out: u64,
        clock: &Clock,
        ctx: &mut TxContext,
    ): Coin<B> {
        let amount_in = coin_in.value();
        let mut coin_a = coin_in;
        let mut coin_b = coin::zero<B>(ctx);

        pool_script_v2::cpmm_swap(
            pool, bank_a, bank_b, lending_market,
            &mut coin_a, &mut coin_b,
            true, amount_in, min_out,
            clock, ctx
        );

        // Clean up remaining input coin
        if (coin_a.value() > 0) {
            transfer::public_transfer(coin_a, ctx.sender());
        } else {
            coin::destroy_zero(coin_a);
        };

        coin_b
    }
}
```

#### 2. Yield Farming Integration

```move
module my_protocol::yield_farm {
    use steamm_scripts::pool_script_v2;

    public struct FarmPosition<phantom LpType> has store {
        lp_tokens: Balance<LpType>,
        deposited_at: u64,
        rewards_earned: u64,
    }

    public fun stake_in_farm<P, A, B, BTokenA, BTokenB, LpType: drop>(
        pool: &mut Pool<BTokenA, BTokenB, CpQuoter, LpType>,
        bank_a: &mut Bank<P, A, BTokenA>,
        bank_b: &mut Bank<P, B, BTokenB>,
        lending_market: &LendingMarket<P>,
        coin_a: Coin<A>,
        coin_b: Coin<B>,
        clock: &Clock,
        ctx: &mut TxContext,
    ): FarmPosition<LpType> {
        let mut coin_a = coin_a;
        let mut coin_b = coin_b;

        // Add liquidity to STEAMM pool
        let lp_tokens = pool_script_v2::deposit_liquidity(
            pool, bank_a, bank_b, lending_market,
            &mut coin_a, &mut coin_b,
            coin_a.value(), coin_b.value(),
            clock, ctx
        );

        // Handle any remaining coins
        if (coin_a.value() > 0) {
            transfer::public_transfer(coin_a, ctx.sender());
        } else {
            coin::destroy_zero(coin_a);
        };

        if (coin_b.value() > 0) {
            transfer::public_transfer(coin_b, ctx.sender());
        } else {
            coin::destroy_zero(coin_b);
        };

        FarmPosition {
            lp_tokens: lp_tokens.into_balance(),
            deposited_at: clock.timestamp_ms(),
            rewards_earned: 0,
        }
    }
}
```

#### 3. Arbitrage Bot Integration

```move
module my_protocol::arbitrage {
    use steamm_scripts::pool_script_v2;

    public fun execute_arbitrage<P, A, B, BTokenA, BTokenB, LpType: drop>(
        steamm_pool: &mut Pool<BTokenA, BTokenB, CpQuoter, LpType>,
        bank_a: &mut Bank<P, A, BTokenA>,
        bank_b: &mut Bank<P, B, BTokenB>,
        lending_market: &LendingMarket<P>,
        external_price: u64,  // Price from external source
        min_profit: u64,
        clock: &Clock,
        ctx: &mut TxContext,
    ) {
        // Get STEAMM price
        let test_amount = 1_000_000; // 1 token for price discovery
        let quote = pool_script_v2::quote_cpmm_swap(
            steamm_pool, bank_a, bank_b, lending_market,
            true, test_amount, clock
        );

        let steamm_price = (quote.amount_out() * 1_000_000) / test_amount;

        // Check for arbitrage opportunity
        if (steamm_price < external_price) {
            // Buy on STEAMM, sell elsewhere
            let profit_estimate = external_price - steamm_price;
            if (profit_estimate > min_profit) {
                // Execute arbitrage...
            }
        }
    }
}
```

## Advanced Usage

### Multi-Pool Routing

```move
public fun multi_hop_swap<P, A, B, C>(
    pool_ab: &mut Pool<A, B, CpQuoter, LpType1>,
    pool_bc: &mut Pool<B, C, CpQuoter, LpType2>,
    coin_a: &mut Coin<A>,
    amount_in: u64,
    min_amount_out: u64,
    ctx: &mut TxContext,
): Coin<C> {
    // Get intermediate quote
    let quote_ab = pool_ab.cpmm_quote_swap(amount_in, true);
    let amount_b = quote_ab.amount_out();

    // First swap: A -> B
    let mut coin_b = coin::zero<B>(ctx);
    pool_ab.cpmm_swap(coin_a, &mut coin_b, true, amount_in, 0, ctx);

    // Second swap: B -> C
    let mut coin_c = coin::zero<C>(ctx);
    pool_bc.cpmm_swap(&mut coin_b, &mut coin_c, true, amount_b, min_amount_out, ctx);

    // Clean up intermediate token
    coin::destroy_zero(coin_b);

    coin_c
}
```

### Fee Management and Revenue

```move
public fun collect_protocol_fees<P, A, B, BTokenA, BTokenB, Quoter: store, LpType: drop>(
    pool: &mut Pool<BTokenA, BTokenB, Quoter, LpType>,
    bank_a: &mut Bank<P, A, BTokenA>,
    bank_b: &mut Bank<P, B, BTokenB>,
) {
    // Fees are automatically moved to banks for distribution
    steamm::fee_crank::crank_fees(pool, bank_a, bank_b);
}

// Claim trading fees earned by the bank
public fun claim_trading_fees<P, T, BToken>(
    bank: &mut Bank<P, T, BToken>,
    lending_market: &LendingMarket<P>,
    registry: &Registry,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    bank.claim_fees(lending_market, registry, clock, ctx);
}

// Claim lending rewards from Suilend
public fun claim_lending_rewards<P, T, BToken, RToken>(
    bank: &mut Bank<P, T, BToken>,
    lending_market: &mut LendingMarket<P>,
    registry: &Registry,
    reward_index: u64,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    bank.claim_rewards<P, T, BToken, RToken>(
        lending_market, registry, reward_index, clock, ctx
    );
}
```

### Monitoring Pool Health

```move
public fun get_comprehensive_pool_info<P, A, B, BTokenA, BTokenB, Quoter: store, LpType: drop>(
    pool: &Pool<BTokenA, BTokenB, Quoter, LpType>,
    bank_a: &Bank<P, A, BTokenA>,
    bank_b: &Bank<P, B, BTokenB>,
    lending_market: &LendingMarket<P>,
    clock: &Clock,
): PoolHealthInfo {
    let (reserve_a, reserve_b) = pool.balance_amounts();
    let lp_supply = pool.lp_supply_val();

    // Get bank utilizations
    let util_a = get_bank_utilization(bank_a, lending_market, clock);
    let util_b = get_bank_utilization(bank_b, lending_market, clock);

    // Check if rebalancing is needed
    let needs_rebalance_a = bank_a.needs_rebalance(lending_market, clock);
    let needs_rebalance_b = bank_b.needs_rebalance(lending_market, clock);

    PoolHealthInfo {
        reserve_a,
        reserve_b,
        lp_supply,
        utilization_a: util_a,
        utilization_b: util_b,
        needs_rebalance_a: needs_rebalance_a.needs_rebalance(),
        needs_rebalance_b: needs_rebalance_b.needs_rebalance(),
    }
}

public struct PoolHealthInfo has copy, drop {
    reserve_a: u64,
    reserve_b: u64,
    lp_supply: u64,
    utilization_a: u64,
    utilization_b: u64,
    needs_rebalance_a: bool,
    needs_rebalance_b: bool,
}

public fun get_bank_utilization<P, T, BToken>(
    bank: &Bank<P, T, BToken>,
    lending_market: &LendingMarket<P>,
    clock: &Clock,
): u64 {
    if (bank.lending().is_none()) {
        return 0
    };

    let (total_funds, _) = bank.get_btoken_ratio(lending_market, clock);
    let funds_available = balance::value(bank.funds_available());
    let total = total_funds.floor();

    if (total == 0) 0
    else ((total - funds_available) * 10000) / total  // Return utilization in bps
}
```

## Code Examples

### Complete Pool Setup

```move
module my_protocol::complete_example {
    use steamm::{registry, bank, cpmm, global_admin};
    use steamm::pool::Pool;
    use steamm_scripts::pool_script_v2;
    use suilend::lending_market::LendingMarket;

    // Token types
    public struct USDC has drop {}
    public struct SUI has drop {}
    public struct B_USDC has drop {}
    public struct B_SUI has drop {}
    public struct LP_USDC_SUI has drop {}

    /// Complete setup for a yield-bearing USDC/SUI pool
    public fun setup_complete_pool<P>(
        registry: &mut Registry,
        lending_market: &mut LendingMarket<P>,
        global_admin: &GlobalAdmin,
        ctx: &mut TxContext,
    ): (
        Pool<B_USDC, B_SUI, CpQuoter, LP_USDC_SUI>,
        Bank<P, USDC, B_USDC>,
        Bank<P, SUI, B_SUI>,
    ) {
        // 1. Setup all coin metadata (implementation details omitted)
        let (
            meta_usdc, meta_sui, mut meta_lp,
            mut meta_b_usdc, mut meta_b_sui,
            lp_treasury, b_usdc_treasury, b_sui_treasury
        ) = setup_all_coin_metadata(ctx);

        // 2. Create banks
        let mut bank_usdc = bank::create_bank<P, USDC, B_USDC>(
            registry, &meta_usdc, &mut meta_b_usdc,
            b_usdc_treasury, lending_market, ctx
        );

        let mut bank_sui = bank::create_bank<P, SUI, B_SUI>(
            registry, &meta_sui, &mut meta_b_sui,
            b_sui_treasury, lending_market, ctx
        );

        // 3. Initialize lending with 80% utilization
        bank_usdc.init_lending(
            global_admin, lending_market,
            8000, 1000, ctx  // 80% ± 10%
        );

        bank_sui.init_lending(
            global_admin, lending_market,
            8000, 1000, ctx  // 80% ± 10%
        );

        // 4. Create the AMM pool
        let pool = cpmm::new<B_USDC, B_SUI, LP_USDC_SUI>(
            registry,
            30,    // 0.3% swap fee
            0,     // No offset
            &meta_b_usdc,
            &meta_b_sui,
            &mut meta_lp,
            lp_treasury,
            ctx,
        );

        (pool, bank_usdc, bank_sui)
    }

    /// Add initial liquidity to the pool
    public fun bootstrap_liquidity<P>(
        pool: &mut Pool<B_USDC, B_SUI, CpQuoter, LP_USDC_SUI>,
        bank_usdc: &mut Bank<P, USDC, B_USDC>,
        bank_sui: &mut Bank<P, SUI, B_SUI>,
        lending_market: &LendingMarket<P>,
        usdc_amount: u64,
        sui_amount: u64,
        clock: &Clock,
        ctx: &mut TxContext,
    ): Coin<LP_USDC_SUI> {
        // Create coins for initial liquidity
        let mut usdc_coin = coin::mint_for_testing<USDC>(usdc_amount, ctx);
        let mut sui_coin = coin::mint_for_testing<SUI>(sui_amount, ctx);

        // Add liquidity through the script helper
        let lp_tokens = pool_script_v2::deposit_liquidity(
            pool, bank_usdc, bank_sui, lending_market,
            &mut usdc_coin, &mut sui_coin,
            usdc_amount, sui_amount,
            clock, ctx
        );

        // Clean up remaining coins
        if (usdc_coin.value() > 0) {
            transfer::public_transfer(usdc_coin, ctx.sender());
        } else {
            coin::destroy_zero(usdc_coin);
        };

        if (sui_coin.value() > 0) {
            transfer::public_transfer(sui_coin, ctx.sender());
        } else {
            coin::destroy_zero(sui_coin);
        };

        lp_tokens
    }
}
```

### Trading Interface

```move
module my_protocol::trading_interface {
    use steamm_scripts::pool_script_v2;

    /// Execute a swap with proper slippage protection
    public fun execute_swap<P>(
        pool: &mut Pool<B_USDC, B_SUI, CpQuoter, LP_USDC_SUI>,
        bank_usdc: &mut Bank<P, USDC, B_USDC>,
        bank_sui: &mut Bank<P, SUI, B_SUI>,
        lending_market: &LendingMarket<P>,
        input_coin: Coin<USDC>,
        expected_output: u64,
        slippage_bps: u64,  // e.g., 100 = 1%
        clock: &Clock,
        ctx: &mut TxContext,
    ): Coin<SUI> {
        let amount_in = input_coin.value();
        let min_amount_out = expected_output * (10000 - slippage_bps) / 10000;

        let mut usdc_coin = input_coin;
        let mut sui_coin = coin::zero<SUI>(ctx);

        pool_script_v2::cpmm_swap(
            pool, bank_usdc, bank_sui, lending_market,
            &mut usdc_coin, &mut sui_coin,
            true,  // USDC -> SUI
            amount_in,
            min_amount_out,
            clock, ctx
        );

        // Handle remaining USDC (should be 0)
        if (usdc_coin.value() > 0) {
            transfer::public_transfer(usdc_coin, ctx.sender());
        } else {
            coin::destroy_zero(usdc_coin);
        };

        sui_coin
    }

    /// Get accurate quote for a potential swap
    public fun get_quote<P>(
        pool: &Pool<B_USDC, B_SUI, CpQuoter, LP_USDC_SUI>,
        bank_usdc: &Bank<P, USDC, B_USDC>,
        bank_sui: &Bank<P, SUI, B_SUI>,
        lending_market: &LendingMarket<P>,
        amount_in: u64,
        usdc_to_sui: bool,
        clock: &Clock,
    ): (u64, u64) {  // (amount_out, fees)
        let quote = pool_script_v2::quote_cpmm_swap(
            pool, bank_usdc, bank_sui, lending_market,
            usdc_to_sui, amount_in, clock
        );

        (quote.amount_out(), quote.output_fees().pool_fees())
    }
}
```

## Reference

### Key Functions by Component

#### Registry

* `registry::init_for_testing()` - Create registry for testing
* Auto-registration happens when creating pools/banks

#### Bank

* `bank::create_bank()` - Create new bank (package function)
* `bank.init_lending()` - Enable Suilend integration
* `bank.mint_btoken()` - Convert tokens to bTokens (singular!)
* `bank.burn_btoken()` - Convert bTokens back to tokens (singular!)
* `bank.rebalance()` - Manual rebalancing trigger

#### Pool

* `pool.deposit_liquidity()` - Add liquidity
* `pool.redeem_liquidity()` - Remove liquidity
* `pool.cpmm_swap()` - Execute CPMM swap
* `pool.cpmm_quote_swap()` - Get CPMM quote

#### Quoters

* `cpmm::new()` - Create constant product pool
* `omm_v2::new()` - Create oracle AMM pool (requires full parameters)
* Various `swap()` and `quote_swap()` functions

#### Scripts (Recommended)

* `pool_script_v2::deposit_liquidity()` - **PREFERRED** - Add liquidity with banks
* `pool_script_v2::redeem_liquidity()` - **PREFERRED** - Remove liquidity with banks
* `pool_script_v2::cpmm_swap()` - **PREFERRED** - Swap with banks
* `pool_script_v2::quote_cpmm_swap()` - **PREFERRED** - Quote with banks

### Gas Optimization Tips

1. **Use Scripts**: Always use `pool_script_v2` functions for yield pools
2. **Batch Operations**: Combine multiple operations when possible
3. **Pre-calculate**: Get quotes off-chain when possible
4. **Rebalance Timing**: Banks auto-rebalance, but manual triggers available
5. **Avoid Micro-transactions**: Respect minimum token block sizes

### Testing

```move
#[test_only]
use steamm::test_utils;

#[test]
fun test_my_pool() {
    let mut scenario = test_scenario::begin(@0x0);

    // Use test utilities for quick setup
    let (pool, bank_a, bank_b, lending_market, lend_cap, prices, bag, clock) =
        test_utils::test_setup_cpmm(30, 0, &mut scenario);

    // Your test logic here

    // Clean up
    destroy(pool);
    destroy(bank_a);
    // ... destroy other objects
    test_scenario::end(scenario);
}
```

### Migration and Versioning

STEAMM uses versioned contracts. When integrating:

1. Always check current versions in Move.toml files
2. Use the latest published package addresses
3. Handle version upgrades gracefully
4. Test with the exact versions you'll use in production

### Best Practices

1. **Start Simple**: Begin with basic CPMM pools, add yield later
2. **Use Scripts**: Always prefer `pool_script_v2` for yield pools
3. **Monitor Health**: Regularly check bank utilization and rebalancing needs
4. **Handle Errors**: Implement proper error handling for common failure modes
5. **Test Thoroughly**: Use the provided test utilities extensively
6. **Gas Awareness**: Be mindful of gas costs, especially for bank operations


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.suilend.fi/steamm-developer-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
