STEAMM Developer Integration Guide

Table of Contents

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:

[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)

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

// 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

// 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

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)

// 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:

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:

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

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
}
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

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)
}
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

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);
}

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

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

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:

// 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

// 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

// 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

// 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

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

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

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

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

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

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

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

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

  • 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

#[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