STEAMM Integration
The STEAMM SDK can be found here: https://www.npmjs.com/package/@suilend/steamm-sdk.
Click here to access the STEAMM Github repo. Any other questions? Head to the STEAMM channel on the Suilend Discord.
If you wish to play around with STEAMM on testnet we provide a fully integrated set of shell scripts to deploy the entire STEAMM suite with it's Suilend dependencies here : https://github.com/userdarius/steamm-testnet
STEAMM Developer Integration Guide
Table of Contents
Overview
Architecture
Quick Start
Pool Types
Creating Pools
Liquidity Operations
Swapping
Suilend Integration
Advanced Usage
Error Handling
Practical Integration
Code Examples
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
Flexible Integration: All pools use banks for liquidity management, with optional yield farming via Suilend
Capital Efficiency: Up to 80% of idle liquidity can earn lending yields (when enabled)
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
Important: All pools require banks and use bToken types. Banks must be created first.
use steamm::{cpmm, bank};
use steamm::registry::Registry;
use suilend::lending_market::LendingMarket;
// Create a CPMM pool (banks required)
public fun create_basic_pool<P, A, B, BTokenA, BTokenB, LpType: drop>(
registry: &mut Registry,
lending_market: &LendingMarket<P>,
meta_a: &CoinMetadata<A>,
meta_b: &CoinMetadata<B>,
meta_b_a: &mut CoinMetadata<BTokenA>,
meta_b_b: &mut CoinMetadata<BTokenB>,
meta_lp: &mut CoinMetadata<LpType>,
lp_treasury: TreasuryCap<LpType>,
b_a_treasury: TreasuryCap<BTokenA>,
b_b_treasury: TreasuryCap<BTokenB>,
swap_fee_bps: u64, // e.g., 30 = 0.3%
offset: u64, // Usually 0 for standard pools
ctx: &mut TxContext,
): (Pool<BTokenA, BTokenB, CpQuoter, LpType>, ID, ID) {
// Create banks for both tokens
let bank_a_id = bank::create_bank_and_share<P, A, BTokenA>(
registry, meta_a, meta_b_a, b_a_treasury, lending_market, ctx
);
let bank_b_id = bank::create_bank_and_share<P, B, BTokenB>(
registry, meta_b, meta_b_b, b_b_treasury, lending_market, ctx
);
// Create pool using bToken types
let pool = cpmm::new<BTokenA, BTokenB, LpType>(
registry,
swap_fee_bps,
offset,
meta_b_a, // bToken A metadata
meta_b_b, // bToken B metadata
meta_lp,
lp_treasury,
ctx,
);
(pool, bank_a_id, bank_b_id)
}
Basic Liquidity Operations
Note: Use pool_script_v2
functions for all pools as they handle the required bank operations:
use steamm_scripts::pool_script_v2;
// Add liquidity using banks (recommended for all pools)
public fun add_liquidity<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
)
}
Pool Types
STEAMM supports three main quoter types:
1. Constant Product AMM (CPMM)
Best for: General trading pairs, volatile assets
Requirements: Banks and bTokens are required for all tokens. You only need to create banks for tokens that don't already have them.
// Formula: (x + offset) * y = k
// Note: Pool uses bToken types, not underlying token types
use steamm::cpmm::CpQuoter;
let pool = cpmm::new<BTokenA, BTokenB, LpToken>(
registry,
30, // 0.3% fee
0, // No offset
meta_b_a, // bToken A metadata
meta_b_b, // bToken B metadata
meta_lp,
lp_treasury,
ctx,
);
Features:
Classic x*y=k formula with optional offset
Suitable for most token pairs
Predictable slippage curves
Requires banks for liquidity management
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
Better for correlated assets
Requires Suilend integration (bTokens only)
3. Stable AMM (Oracle Quoter V2 with High Amplifier)
Best for: Highly correlated assets (USDC/USDT)
Current Implementation: Oracle Quoter V2 with amplifier A = 1000
Features:
Uses Curve StableSwap invariant with oracle prices
High amplifier (A = 1000) provides minimal slippage for stable pairs
Combines oracle price feeds with stable swap mathematics
Dynamic amplifier adjustment based on pool balance ratios
Requires Suilend integration (bTokens only)
// Oracle Quoter V2 configured for stable pairs
let stable_pool = omm_v2::new<P, USDC, USDT, bUSDC, bUSDT, LP>(
registry,
lending_market,
usdc_metadata,
usdt_metadata,
busdc_metadata,
busdt_metadata,
lp_metadata,
lp_treasury,
oracle_registry,
oracle_index_usdc, // Oracle index for USDC
oracle_index_usdt, // Oracle index for USDT
amplifier: 1000, // High amplifier for stable pairs
swap_fee_bps: 5, // 0.05% swap fee
ctx
);
Creating Pools
Bank Creation Methods
Before creating pools, you need to create banks for any tokens that don't already have them. STEAMM provides two methods for bank creation:
Method 1: Simple Bank Creation (Recommended for most cases)
use steamm::bank;
// Create and share a bank in one call
let bank_id: ID = bank::create_bank_and_share<P, USDC, bUSDC>(
registry,
&meta_usdc,
&mut meta_b_usdc,
b_usdc_treasury,
lending_market,
ctx,
);
// The bank is automatically shared and can be accessed via the registry
Method 2: Bank Builder Pattern (For advanced setup)
The create_bank()
function is package-only, so external protocols must use the builder pattern:
use steamm::bank;
// Step 1: Create a bank builder
let mut builder = bank::new<P, USDC, bUSDC>(
registry,
&meta_usdc,
&mut meta_b_usdc,
b_usdc_treasury,
lending_market,
ctx,
);
// Step 2: Optionally perform setup operations on the bank before sharing
// (Advanced use cases - most developers can skip this)
let bank_mut = builder.bank_mut();
// ... perform any pre-sharing setup ...
// Step 3: Share the bank and get its ID
let bank_id: ID = builder.build();
When to use each method:
Use
create_bank_and_share()
for standard bank creationUse the builder pattern only when you need to configure the bank before it becomes shared (advanced use cases)
Fee Tiers
STEAMM supports multiple fee tiers to accommodate different asset pairs and trading scenarios. All fees are specified in basis points (bps), where 100 bps = 1%.
Available Fee Tiers:
1
0.01%
Ultra-stable pairs (e.g., USDC/USDT)
5
0.05%
Stable pairs with high correlation
10
0.10%
Correlated assets
20
0.20%
Standard stable pairs
25
0.25%
Slightly volatile pairs
30
0.30%
Standard trading pairs (most common)
50
0.50%
Volatile asset pairs
100
1.00%
High volatility pairs
200
2.00%
Very volatile or exotic pairs
1000
10.00%
Extremely high risk pairs
5000
50.00%
Experimental or very high risk
Fee Selection Guidelines:
Stable pairs (USDC/USDT): Use 1-10 bps for minimal slippage
Standard pairs (ETH/USDC): Use 30 bps (0.3%) as the default
Volatile pairs: Use 50-100 bps to compensate for higher risk
Exotic pairs: Use higher fees (200+ bps) for increased LP protection
Pool Creation with Existing Banks
When creating pools for tokens that already have banks (like SUI):
module my_protocol::usdc_sui_pool {
use steamm::cpmm;
use steamm::pool::Pool;
use steamm::registry::Registry;
public struct MyLpToken has drop {}
public struct MyBTokenUSDC has drop {}
public fun create_usdc_sui_pool(
registry: &mut Registry,
lending_market: &LendingMarket<P>,
ctx: &mut TxContext,
): (Pool<MyBTokenUSDC, bSUI, CpQuoter, MyLpToken>, ID) {
// Setup coin metadata (implementation details omitted)
let (meta_usdc, meta_sui, mut meta_lp, mut meta_b_usdc, meta_b_sui,
lp_treasury, b_usdc_treasury) = setup_coin_metadata(ctx);
// Create bank only for USDC since SUI bank already exists
let bank_usdc_id = bank::create_bank_and_share<P, USDC, MyBTokenUSDC>(
registry,
&meta_usdc,
&mut meta_b_usdc,
b_usdc_treasury,
lending_market,
ctx,
);
// Create pool using bToken types
let pool = cpmm::new<MyBTokenUSDC, bSUI, MyLpToken>(
registry,
30, // 0.3% swap fee
0, // No offset
&meta_b_usdc, // bToken USDC metadata
&meta_b_sui, // Existing bToken SUI metadata
&mut meta_lp,
lp_treasury,
ctx,
);
(pool, bank_usdc_id)
}
}
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>,
ID, // Bank USDC ID
ID, // Bank SUI ID
) {
// 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 (they become shared objects)
let bank_usdc_id = bank::create_bank_and_share<P, USDC, MyBTokenUSDC>(
registry,
&meta_usdc,
&mut meta_b_usdc,
b_usdc_treasury,
lending_market,
ctx,
);
let bank_sui_id = bank::create_bank_and_share<P, SUI, MyBTokenSUI>(
registry,
&meta_sui,
&mut meta_b_sui,
b_sui_treasury,
lending_market,
ctx,
);
// Banks are now shared objects and must be accessed via separate transactions
// The bank IDs can be used to reference the banks later
// Note: Lending initialization and pool creation would typically happen
// in separate transactions since banks are shared objects
// 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_id, bank_sui_id)
}
}
Liquidity Operations
Adding Liquidity
All pools require banks and should use the script functions:
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
All pools require banks and should use the script functions:
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 a direct swap model where swaps are executed immediately:
Quote: Get a quote for the intended swap amount
Provision: Banks automatically provision necessary liquidity from Suilend (for yield pools)
Swap: Perform the swap in a single transaction
Rebalance: Banks automatically rebalance utilization as needed
Pool Swaps
Always use pool_script_v2
for all pools - it handles the required bank operations and 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: Important: Quote functions return amounts in bToken values, not underlying token values. For pools without lending, bTokens have a 1:1 ratio with underlying tokens. For yield pools with active lending, the bToken:underlying ratio may differ due to accrued lending returns.
// For all pools - RECOMMENDED (handles bank operations)
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, // In underlying token units
clock: &Clock,
): SwapQuote {
pool_script_v2::quote_cpmm_swap(
pool, bank_a, bank_b, lending_market,
a2b, amount_in, clock // Returns bToken amounts
)
}
// Converting between bToken and underlying token values
public fun btoken_to_underlying<P, T, BT>(
bank: &Bank<P, T, BT>,
lending_market: &LendingMarket<P>,
btoken_amount: u64,
): u64 {
bank.redeem_shares_for_amount(lending_market, btoken_amount)
}
public fun underlying_to_btoken<P, T, BT>(
bank: &Bank<P, T, BT>,
lending_market: &LendingMarket<P>,
underlying_amount: u64,
): u64 {
bank.amount_to_shares(lending_market, underlying_amount)
}
Script Versions: When to Use Which
The choice between script versions depends on gas costs and lending market access requirements:
pool_script_v2
: RECOMMENDED - Uses immutable&LendingMarket
Lower gas costs due to immutable reference
Suitable for most swap operations
Cannot perform preemptive rebalancing
pool_script
: Uses mutable&mut LendingMarket
Higher gas costs due to mutable reference
Required when large withdrawals need preemptive rebalancing
Allows modification of lending market state
Use only when
pool_script_v2
fails due to rebalancing needs
Direct pool calls: Not recommended - use script functions instead
When to use pool_script
:
Large withdrawal operations that trigger rebalancing requirements
When you encounter errors related to insufficient liquidity that require preemptive rebalancing
As a fallback when
pool_script_v2
operations fail
Default recommendation: Always try pool_script_v2
first for better gas efficiency, fallback to pool_script
only when necessary.
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_and_share()
or builder pattern - Creates bank for a token typeLending Setup:
bank.init_lending()
- Enables Suilend integration (admin-gated, only Suilend can toggle)Operation: 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
Admin Access Required: init_lending
is admin-gated and can only be called by Suilend administrators.
// Configure how much liquidity gets deployed to Suilend
bank.init_lending(
global_admin, // Must be Suilend admin account
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 Enable Suilend Lending
All pools use banks, but lending integration is optional and requires admin permission:
Enable Lending Integration When:
You want maximum yield for LPs
Pool expects significant idle periods
Working with established tokens (USDC, SUI, etc.)
Token has an active Suilend market
Keep Lending Disabled When:
Working with new/experimental tokens
Token doesn't have a Suilend market
Minimizing yield complexity
Rapid prototyping phases
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. Simple Swap Integration
module my_protocol::simple_swaps {
use steamm_scripts::pool_script_v2;
public fun execute_swap<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>,
ID, // Bank USDC ID
ID, // Bank SUI ID
) {
// 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 using builder pattern (needed for lending setup)
let mut builder_usdc = bank::new<P, USDC, B_USDC>(
registry, &meta_usdc, &mut meta_b_usdc,
b_usdc_treasury, lending_market, ctx
);
let mut builder_sui = bank::new<P, SUI, B_SUI>(
registry, &meta_sui, &mut meta_b_sui,
b_sui_treasury, lending_market, ctx
);
// 3. Initialize lending with 80% utilization before sharing
builder_usdc.bank_mut().init_lending(
global_admin, lending_market,
8000, 1000, ctx // 80% ± 10%
);
builder_sui.bank_mut().init_lending(
global_admin, lending_market,
8000, 1000, ctx // 80% ± 10%
);
// 4. Share the banks
let bank_usdc_id = builder_usdc.build();
let bank_sui_id = builder_sui.build();
// 5. 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_id, bank_sui_id)
}
/// 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_and_share()
- Create and share new bank (simple method)bank::new()
/builder.build()
- Create bank via builder pattern (advanced method)bank.init_lending()
- Enable Suilend integration (admin-gated)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 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
Last updated