# 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
