Stable swap pools
Tapp's stable swap pools are designed for assets that trade near a 1:1 price ratio—like two stablecoins or wrapped versions of the same token. They implement Curve-style math in the stable::stable
module to achieve very low slippage.
This guide summarizes the Move code and explains how liquidity providers interact with these pools.
Key data types
Config
authority
: address allowed to modify global settings.pending_authority
: temporary storage when transferring authority.fee_tiers
: supported trading fees (0.01% and 0.05%).
Pool
Each pool stores many fields:
struct Pool has key, store {
pool_addr: address, // resource account for the pool
n_coins: u64, // number of assets (2‒8)
rate_multipliers: vector<u256>,// decimals normalization factors
stored_balances: vector<u256>, // pool reserves
fee: u256, // trade fee
offpeg_fee_multiplier: u256, // extra fee when off the peg
initial_a: u256, // A parameter at start of ramp
future_a: u256, // target A parameter
initial_a_time: u64, // ramp start time
future_a_time: u64, // ramp end time
positions: BigOrderedMap<u64, Position>,
position_index: u64,
total_shares: u256,
}
Position
A Position
simply tracks a user's share count:
struct Position has copy, drop, store {
index: u64,
shares: u256,
}
Creating a pool
create_pool(pool_signer, assets, assets_decimals, fee, rest_args, creator)
initializes a new resource account that holds the Pool
struct. Arguments include:
assets
: vector of coin type addresses.assets_decimals
: decimal places for each asset so reserves can be normalised.fee
: must match a configured tier.rest_args
: BCS-encodedamp
(initial A factor) andoffpeg_fee_multiplier
.creator
: recorded in thePoolCreated
event.
Most other entry functions accept a stream: &mut BCSStream
parameter that encodes additional arguments in Binary Canonical Serialization (BCS) format. This keeps the signatures short while allowing complex parameter lists.
Balances start at zero and rate_multipliers
adjust each token to 36 decimals. The amplification factor A
controls the curvature of the swap function and can later be adjusted through ramping operations.
Adding liquidity
add_liquidity(pool_signer, position_idx, stream, receiver)
accepts a BCSStream
that encodes:
amounts
– vector of deposit amounts (one per coin in the pool)min_mint_amount
– minimum LP tokens to mint
The function then:
Computes the current invariant
D
usingget_D_mem
.Transfers tokens in and updates
stored_balances
.Recalculates
D
and applies fees on any imbalance using a dynamic fee formula.Mints pool shares proportional to the change in
D
.
If position_idx
is some
, the existing position receives the new shares. Otherwise a new index is created.
Removing liquidity
remove_liquidity(pool_signer, position_idx, stream, creator)
also expects a BCSStream
. The first byte selects the withdrawal mode:
Single coin withdrawal – stream then carries
burn_amount
,i
(coin index) andmin_received
.Imbalanced withdrawal – followed by a vector
amounts
andmax_burn_amount
.Proportional withdrawal – remaining bytes encode
burn_amount
andmin_amounts
for each coin.
ramp_a(stream)
– stream containsfuture_a
(u256) andfuture_a_time
(u64) to gradually change the amplification factor.set_new_fee(stream)
– stream providesnew_fee
(u256) andnew_offpeg_fee_multiplier
(u256).
Swapping tokens
swap(pool_signer, stream, receiver)
reads the sold token index i
, bought token index j
, input amount dx
and minimum output min_dy
. It calculates normalized balances (xp
), calls internal_swap
to solve the stable swap invariant and applies a dynamic fee based on how far the trade moves the pool off the peg. Reserves are updated and a Swapped
event is emitted.
Pool operations
Administrative actions are routed through run_pool_op
:
ramp_a(stream)
– gradually change the amplification factor betweeninitial_a
andfuture_a
over time.stop_ramp_a()
– freezeA
at its current value.set_new_fee(stream)
– update the fee and off-peg multiplier.
These operations emit RampA
, StopRampA
and ApplyNewFee
events respectively.
Math overview
Stable pools use an invariant D
derived from Curve's formula that blends constant sum and constant product behavior. Key helpers include:
get_D(xp, amp)
– iteratively solves for the invariant given normalized balancesxp
and amplification factoramp
.get_y(i, j, x, xp, amp, d)
– given a new balancex
for coini
, computes the resulting balance of coinj
while keepingD
constant.amp()
– returns the current amplification coefficient, linearly interpolating during a ramp.internal_dynamic_fee(xpi, xpj, fee)
– increases the fee when trading pushes the pool away from the peg.
These functions ensure swaps and liquidity changes respect the invariant while allowing very efficient trades around the equilibrium price.
Utility functions
The module exposes several read‑only helpers:
n_coins
,rate_multipliers
,stored_balances
initial_a
,future_a
,initial_a_time
,future_a_time
fee_rate
andfee_denominator
total_shares
,get_position
andposition_shares
get_dx
/get_dy
to preview swap amounts
Events
Stable swap pools emit rich events for indexers:
PoolCreated
LiquidityAdded
LiquidityRemoved
,LiquidityOneRemoved
,LiquidityImbalanceRemoved
Swapped
RampA
andStopRampA
ApplyNewFee
Security considerations
The code asserts valid fee tiers, checks invariant calculations, prevents position underflow and enforces ramping constraints. These safeguards help maintain pool integrity and protect LPs.
Stable swap pools complement Tapp's AMM and CLMM offerings by enabling efficient trading between pegged assets. Understanding the Move implementation allows integrators to estimate pricing, manage liquidity and monitor the health of each pool.
Last updated