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
Flexible Integration: Choose between simple AMM pools or advanced yield-bearing pools
Capital Efficiency: Up to 80% of idle liquidity can earn lending yields
Multiple AMM Types: Constant product, oracle-based, and stable coin AMMs
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
}
Yield Pool (with Banks) - Recommended Approach
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)
}
Yield Pool - Recommended Approach
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:
Intent: Declare intention to swap and get a quote
Provision: Banks provision necessary liquidity from Suilend
Execute: Perform the actual swap
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);
}
Yield Pool Swaps - Recommended Approach
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 codepool_script_v2
: RECOMMENDED - cleaner API, better error handlingDirect 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
Creation:
bank::create_bank()
- Creates bank for a token typeLending Setup:
bank.init_lending()
- Enables Suilend integrationOperation: Automatic yield generation and liquidity management
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
Additional Yield: LPs earn trading fees + lending yields
Capital Efficiency: Up to 80% of idle liquidity generates yield
Shared Liquidity: Multiple pools share deeper liquidity
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
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 testingAuto-registration happens when creating pools/banks
Bank
bank::create_bank()
- Create new bank (package function)bank.init_lending()
- Enable Suilend integrationbank.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 liquiditypool.redeem_liquidity()
- Remove liquiditypool.cpmm_swap()
- Execute CPMM swappool.cpmm_quote_swap()
- Get CPMM quote
Quoters
cpmm::new()
- Create constant product poolomm_v2::new()
- Create oracle AMM pool (requires full parameters)Various
swap()
andquote_swap()
functions
Scripts (Recommended)
pool_script_v2::deposit_liquidity()
- PREFERRED - Add liquidity with bankspool_script_v2::redeem_liquidity()
- PREFERRED - Remove liquidity with bankspool_script_v2::cpmm_swap()
- PREFERRED - Swap with bankspool_script_v2::quote_cpmm_swap()
- PREFERRED - Quote with banks
Gas Optimization Tips
Use Scripts: Always use
pool_script_v2
functions for yield poolsBatch Operations: Combine multiple operations when possible
Pre-calculate: Get quotes off-chain when possible
Rebalance Timing: Banks auto-rebalance, but manual triggers available
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:
Always check current versions in Move.toml files
Use the latest published package addresses
Handle version upgrades gracefully
Test with the exact versions you'll use in production
Best Practices
Start Simple: Begin with basic CPMM pools, add yield later
Use Scripts: Always prefer
pool_script_v2
for yield poolsMonitor Health: Regularly check bank utilization and rebalancing needs
Handle Errors: Implement proper error handling for common failure modes
Test Thoroughly: Use the provided test utilities extensively
Gas Awareness: Be mindful of gas costs, especially for bank operations