Turbos Finance CLMM SDK - Integration Guide
Turbos Finance is a Concentrated Liquidity Market Maker (CLMM) DEX on Sui. This guide covers the turbos-clmm-sdk package for building DeFi applications.
Table of Contents
- Installation
- SDK Initialization
- Module: Contract
- Module: Pool
- Module: Trade
- Module: Position
- Module: Account
- Module: Math
- Key Types Reference
Installation
pnpm add turbos-clmm-sdk @mysten/sui
SDK Initialization
import { Network, TurbosSdk } from 'turbos-clmm-sdk';
import { SuiClient } from '@mysten/sui/client';
// Mainnet (default fullnode RPC)
const sdk = new TurbosSdk(Network.mainnet);
// Testnet
const sdk = new TurbosSdk(Network.testnet);
// Custom SuiClient
const client = new SuiClient({ url: 'YOUR_RPC_URL' });
const sdk = new TurbosSdk(Network.mainnet, client);
// Custom SuiClientOptions
const sdk = new TurbosSdk(Network.mainnet, { url: 'YOUR_RPC_URL' });
SDK Structure
The TurbosSdk instance exposes the following modules:
| Property | Type | Description |
|---|
sdk.pool | Pool | Pool creation, liquidity operations |
sdk.contract | Contract | Protocol configuration & fee tiers |
sdk.trade | Trade | Swap operations |
sdk.position | Position | Position/NFT queries (replaces sdk.nft) |
sdk.math | MathUtil | Tick/price/sqrt math utilities |
sdk.account | Account | Keypair & mnemonic helpers |
sdk.coin | Coin | Coin metadata & selection |
sdk.provider | SuiClient | Underlying Sui RPC client |
Module: Contract
Protocol configuration and fee tier management.
getConfig
import type { Contract } from 'turbos-clmm-sdk';
const config: Contract.Config = await sdk.contract.getConfig();
// Returns: { PackageId, PackageIdOriginal, PoolConfig, Positions, Versioned, PoolTableId, ... }
getFees
const fees: Contract.Fee[] = await sdk.contract.getFees();
// Each fee: { fee: number, objectId: string, type: string, tickSpacing: number }
getFee
// Get fee tier by tick spacing
const fee: Contract.Fee = await sdk.contract.getFee(60);
Contract.Config Interface
interface Config {
PackageId: string;
PackageIdOriginal: string;
PoolConfig: string;
Positions: string;
PoolFactoryAdminCap: string;
Versioned: string;
PoolTableId: string;
VaultOriginPackageId: string;
VaultPackageId: string;
VaultGlobalConfig: string;
VaultRewarderManager: string;
VaultUserTierConfig: string;
AclConfig: string;
}
Module: Pool
Core pool operations: create, add/remove liquidity, collect fees/rewards.
getPools
// Get all unlocked pools
const pools: Pool.Pool[] = await sdk.pool.getPools();
// Include locked pools
const allPools: Pool.Pool[] = await sdk.pool.getPools(true);
getPool
const pool: Pool.Pool = await sdk.pool.getPool(poolId);
createPool
const fee = await sdk.contract.getFee(60); // or other tick spacing
const txb = await sdk.pool.createPool({
fee, // Fee object from sdk.contract.getFees()
coinTypeA: '0x2::sui::SUI',
coinTypeB: '0x...::usdc::USDC',
sqrtPrice: '79226673515401279992447579055', // Initial sqrt price (X64)
address: 'YOUR_SUI_ADDRESS',
amountA: '1000000000', // Amount of coin A (raw units)
amountB: '1000000', // Amount of coin B (raw units)
tickLower: -100,
tickUpper: 100,
slippage: '5', // Percentage [0, 100)
deadline: 60000, // Optional, default 60s
});
addLiquidity (Open New Position)
const txb = await sdk.pool.addLiquidity({
pool: poolId,
address: 'YOUR_SUI_ADDRESS',
amountA: '1000000000',
amountB: '1000000',
tickLower: -100,
tickUpper: 100,
slippage: '5', // Percentage [0, 100)
deadline: 60000, // Optional
txb: existingTxb, // Optional, append to existing transaction
});
increaseLiquidity (Add to Existing Position)
const txb = await sdk.pool.increaseLiquidity({
pool: poolId,
nft: positionNftId, // Existing position NFT ID
address: 'YOUR_SUI_ADDRESS',
amountA: '500000000',
amountB: '500000',
slippage: '5',
deadline: 60000,
});
decreaseLiquidity
const txb = await sdk.pool.decreaseLiquidity({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
amountA: '500000000', // Expected amount A to receive
amountB: '500000', // Expected amount B to receive
decreaseLiquidity: '1000000', // Liquidity units to remove
slippage: '5',
});
decreaseLiquidityWithReturn
Returns coin objects instead of transferring, for composability in PTBs:
const { txb, coinA, coinB } = await sdk.pool.decreaseLiquidityWithReturn({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
amountA: '500000000',
amountB: '500000',
decreaseLiquidity: '1000000',
slippage: '5',
});
// coinA, coinB are TransactionObjectArguments for further PTB composition
removeLiquidity (Full Removal + Collect + Burn)
Combines decreaseLiquidity + collectFee + collectReward + burn:
const txb = await sdk.pool.removeLiquidity({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
amountA: '500000000',
amountB: '500000',
decreaseLiquidity: '1000000',
slippage: '5',
collectAmountA: '1000', // Fee amount A to collect
collectAmountB: '1000', // Fee amount B to collect
rewardAmounts: ['500', '0', '0'],
});
collectFee
const txb = await sdk.pool.collectFee({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
collectAmountA: '1000',
collectAmountB: '1000',
});
collectReward
const txb = await sdk.pool.collectReward({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
rewardAmounts: ['500', '0', '0'], // Amount per reward slot
});
estimateAmountsFromOneAmount
Estimate the required token pair amounts given a single token amount:
const [amountA, amountB] = sdk.pool.estimateAmountsFromOneAmount({
sqrtPrice: pool.sqrt_price,
tickLower: -100,
tickUpper: 100,
amount: '1000000000',
isAmountA: true,
});
getTokenAmountsFromLiquidity
Calculate token amounts from a liquidity value:
import BN from 'bn.js';
const [amountA, amountB] = sdk.pool.getTokenAmountsFromLiquidity({
currentSqrtPrice: new BN(pool.sqrt_price),
lowerSqrtPrice: sdk.math.tickIndexToSqrtPriceX64(tickLower),
upperSqrtPrice: sdk.math.tickIndexToSqrtPriceX64(tickUpper),
liquidity: new BN(position.liquidity),
ceil: true, // Optional, defaults true
});
getPoolTypeArguments
// Returns [coinTypeA, coinTypeB, feeType]
const types: Pool.Types = await sdk.pool.getPoolTypeArguments(poolId);
fetchTicks
Fetch all initialized ticks for a pool:
const ticks = await sdk.pool.fetchTicks(poolId);
// Each tick: { id, tick_index, liquidity_gross, liquidity_net, fee_growth_outside_a/b, ... }
Module: Trade
Swap operations with single-hop and multi-hop routing.
computeSwapResult
Simulate a swap to get expected output (single amount for all pools):
import type { Trade } from 'turbos-clmm-sdk';
const results: Trade.ComputedSwapResult[] = await sdk.trade.computeSwapResult({
pools: [{ pool: poolId, a2b: true }],
address: 'YOUR_SUI_ADDRESS',
amountSpecified: '1000000000',
amountSpecifiedIsInput: true,
tickStep: 100, // Optional, default 100
});
// Result fields: { a_to_b, amount_a, amount_b, fee_amount, protocol_fee,
// sqrt_price, tick_current_index, tick_pre_index, pool, ... }
computeSwapResultV2
Simulate a swap with per-pool amounts (useful for multi-hop with different amounts):
const results = await sdk.trade.computeSwapResultV2({
pools: [
{ pool: poolId1, a2b: true, amountSpecified: '500000000' },
{ pool: poolId2, a2b: false, amountSpecified: '500000000' },
],
address: 'YOUR_SUI_ADDRESS',
amountSpecifiedIsInput: true,
});
swap
Execute a swap (supports up to 2-hop routing):
const swapResult = await sdk.trade.computeSwapResult({ ... });
const txb = await sdk.trade.swap({
routes: [{
pool: poolId,
a2b: true,
nextTickIndex: sdk.math.bitsToNumber(swapResult[0].tick_current_index.bits),
}],
coinTypeA: '0x2::sui::SUI',
coinTypeB: '0x...::usdc::USDC',
address: 'YOUR_SUI_ADDRESS',
amountA: swapResult[0].amount_a,
amountB: swapResult[0].amount_b,
amountSpecifiedIsInput: true,
slippage: '1', // Percentage
});
swapWithReturn
Swap and return coin objects for PTB composability:
const { txb, coinVecA, coinVecB } = await sdk.trade.swapWithReturn({
poolId,
coinType: coinTypeA,
amountA: '1000000000',
amountB: '500000',
swapAmount: '1000000000',
nextTickIndex: tickIndex,
slippage: '1',
amountSpecifiedIsInput: true,
a2b: true,
address: 'YOUR_SUI_ADDRESS',
});
swapWithPartner
Swap with a partner fee split:
const txb = await sdk.trade.swapWithPartner({
poolId,
swapAmount: '1000000000',
amountSpecifiedIsInput: true,
slippage: '1',
a2b: true,
address: 'YOUR_SUI_ADDRESS',
partner: 'PARTNER_OBJECT_ID',
});
Module: Position
Position (NFT) management and analytics. sdk.position replaces the deprecated sdk.nft.
getPositionsByOwner (via SuiClient)
The SDK does not provide a built-in method to list all positions by owner address. Instead, use sdk.provider.getOwnedObjects to query TurbosPositionNFT objects, then batch-fetch position details. This is the pattern used by Turbos' own frontend:
import { unstable_getObjectFields, unstable_getObjectId, type NFT } from 'turbos-clmm-sdk';
import type { PaginatedObjectsResponse } from '@mysten/sui/client';
interface PositionInfo extends NFT.PositionField {
nftId: string;
positionId: string;
poolId: string;
tickLower: number;
tickUpper: number;
objectId: string;
}
async function getPositionsByOwner(
sdk: TurbosSdk,
ownerAddress: string,
): Promise<PositionInfo[]> {
const contract = await sdk.contract.getConfig();
// Step 1: Paginate through all TurbosPositionNFT objects owned by the address
const nfts: { nftId: string; positionId: string; poolId: string }[] = [];
let cursor: string | null | undefined;
let page: PaginatedObjectsResponse;
do {
page = await sdk.provider.getOwnedObjects({
owner: ownerAddress,
options: { showContent: true, showOwner: true },
cursor,
filter: {
StructType: `${contract.PackageIdOriginal}::position_nft::TurbosPositionNFT`,
},
});
page.data.forEach((item) => {
const nftId = unstable_getObjectId(item);
const fields = unstable_getObjectFields(item) as {
position_id: string;
pool_id: string;
};
if (fields.position_id && fields.pool_id) {
nfts.push({ nftId, positionId: fields.position_id, poolId: fields.pool_id });
}
});
cursor = page.nextCursor;
} while (page.hasNextPage);
if (nfts.length === 0) return [];
// Step 2: Batch fetch position details (max 50 per call)
const batchSize = 50;
const batches: Promise<any[]>[] = [];
for (let i = 0; i < nfts.length; i += batchSize) {
batches.push(
sdk.provider.multiGetObjects({
ids: nfts.slice(i, i + batchSize).map((n) => n.positionId),
options: { showContent: true },
}),
);
}
const allResults = (await Promise.all(batches)).flat();
// Step 3: Parse position fields
return allResults.map((obj): PositionInfo => {
const objectId = unstable_getObjectId(obj);
const fields = unstable_getObjectFields(obj) as unknown as NFT.PositionField;
const nft = nfts.find((n) => n.positionId === objectId)!;
return {
...fields,
...nft,
objectId,
tickLower: sdk.math.bitsToNumber(fields.tick_lower_index.fields.bits),
tickUpper: sdk.math.bitsToNumber(fields.tick_upper_index.fields.bits),
};
});
}
Key details:
- NFT struct type filter:
{PackageIdOriginal}::position_nft::TurbosPositionNFT
- Each NFT has
position_id and pool_id fields linking to the actual position data
- Burned/locked positions use a different struct:
{PackageId}::position_manager::TurbosPositionBurnNFT, with pool ID nested at position_nft.fields.pool_id
- Use
sdk.math.bitsToNumber(tick_index.fields.bits) to convert tick index from on-chain I32 format
getFields
const fields = await sdk.position.getFields(nftId);
// Returns: { description, id, img_url, name, pool_id, position_id }
getPositionFields
const position = await sdk.position.getPositionFields(nftId);
// Returns: { liquidity, tick_lower_index, tick_upper_index,
// fee_growth_inside_a/b, tokens_owed_a/b, reward_infos }
getPositionFieldsByPositionId
const position = await sdk.position.getPositionFieldsByPositionId(positionId);
getPositionTick
const tick = await sdk.position.getPositionTick(poolId, position.tick_lower_index);
// Returns: { tickIndex, initialized, liquidityNet, liquidityGross,
// feeGrowthOutsideA, feeGrowthOutsideB, rewardGrowthsOutside }
getPositionLiquidityUSD
const usdValue = await sdk.position.getPositionLiquidityUSD({
poolId,
position, // PositionField
priceA: '1.00', // USD price of coin A
priceB: '50000', // USD price of coin B
});
getUnclaimedFeesAndRewards
const result = await sdk.position.getUnclaimedFeesAndRewards({
poolId,
position,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { fees, rewards, total, fields: { feeOwedA, feeOwedB, collectRewards, ... } }
getUnclaimedFees
const fees = await sdk.position.getUnclaimedFees({
pool,
position,
tickLowerDetail,
tickUpperDetail,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { feeOwedA, feeOwedB, unclaimedFees, scaledFeeOwedA, scaledFeeOwedB }
getUnclaimedRewards
const rewards = await sdk.position.getUnclaimedRewards({
pool,
position,
tickLowerDetail,
tickUpperDetail,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { unclaimedRewards, collectRewards, scaledCollectRewards }
getPositionAPR
const apr = await sdk.position.getPositionAPR({
poolId,
tickLower: -100,
tickUpper: 100,
fees24h: '1000',
getPrice: async (coinType) => getPriceForCoin(coinType),
liquidity: '1000000', // Optional, defaults to pool liquidity
});
// Returns: { fees: string, rewards: string, total: string }
burn
Burn a position NFT (must have 0 liquidity):
const txb = await sdk.position.burn({
pool: poolId,
nft: positionNftId,
});
Module: Account
Keypair and mnemonic utilities.
generateMnemonic
const mnemonic24 = sdk.account.generateMnemonic(24); // 24 words (default)
const mnemonic12 = sdk.account.generateMnemonic(12); // 12 words
getKeypairFromMnemonics
const keypair = sdk.account.getKeypairFromMnemonics(mnemonic, {
accountIndex: 0, // Optional, default 0
isExternal: false, // Optional, default false
addressIndex: 0, // Optional, default 0
});
// Derive path: m/44'/784'/{accountIndex}'/{isExternal ? 1 : 0}'/{addressIndex}'
Module: Math
Tick, price, and sqrt math utilities accessed via sdk.math.
Key Methods
// Tick ↔ SqrtPrice
const sqrtPriceX64: BN = sdk.math.tickIndexToSqrtPriceX64(tickIndex);
const tickIndex: number = sdk.math.sqrtPriceX64ToTickIndex(sqrtPriceX64);
// Tick ↔ Price (human-readable)
const price: Decimal = sdk.math.tickIndexToPrice(tickIndex, decimalsA, decimalsB);
const sqrtPriceX64: BN = sdk.math.priceToSqrtPriceX64(price, decimalsA, decimalsB);
// Bits (I32/I128) ↔ Number
const num: number = sdk.math.bitsToNumber(bits); // I32
const num: number = sdk.math.bitsToNumber(bits, 128); // I128
// Scale conversions
const scaled: string = sdk.math.scaleDown(amount, decimals);
// X64 fixed-point conversions
const x64: Decimal = sdk.math.toX64_Decimal(value);
const fromX64: Decimal = sdk.math.fromX64_Decimal(value);
Key Types Reference
Pool.Pool
interface Pool {
objectId: string;
type: string;
types: [coinTypeA: string, coinTypeB: string, feeType: string];
coin_a: string;
coin_b: string;
fee: number;
fee_protocol: number;
liquidity: string;
sqrt_price: string;
tick_current_index: { type: string; fields: { bits: number } };
tick_spacing: number;
unlocked: boolean;
max_liquidity_per_tick: string;
fee_growth_global_a: string;
fee_growth_global_b: string;
protocol_fees_a: string;
protocol_fees_b: string;
reward_infos: {
type: string;
fields: {
emissions_per_second: string;
growth_global: string;
manager: string;
vault: string;
vault_coin_type: string;
};
}[];
deploy_time_ms: string;
reward_last_updated_time_ms: string;
tick_map: { type: string; fields: { id: { id: string }; size: string } };
}
Trade.ComputedSwapResult
interface ComputedSwapResult {
a_to_b: boolean;
amount_a: string;
amount_b: string;
fee_amount: string;
is_exact_in: boolean;
liquidity: string;
pool: string;
protocol_fee: string;
recipient: string;
sqrt_price: string;
tick_current_index: { bits: number };
tick_pre_index: { bits: number };
}
Network Enum
enum Network {
mainnet = 'mainnet',
testnet = 'testnet',
}
Common Patterns
Typical Swap Flow
import { Network, TurbosSdk } from 'turbos-clmm-sdk';
const sdk = new TurbosSdk(Network.mainnet);
// 1. Compute swap result
const swapResults = await sdk.trade.computeSwapResult({
pools: [{ pool: poolId, a2b: true }],
address: walletAddress,
amountSpecified: '1000000000',
amountSpecifiedIsInput: true,
});
// 2. Build swap transaction
const txb = await sdk.trade.swap({
routes: [{
pool: poolId,
a2b: true,
nextTickIndex: sdk.math.bitsToNumber(swapResults[0].tick_current_index.bits),
}],
coinTypeA,
coinTypeB,
address: walletAddress,
amountA: swapResults[0].amount_a,
amountB: swapResults[0].amount_b,
amountSpecifiedIsInput: true,
slippage: '1',
});
// 3. Sign and execute
await suiClient.signAndExecuteTransaction({ transaction: txb, signer: keypair });
Typical Liquidity Addition Flow
// 1. Get fee tier
const fee = await sdk.contract.getFee(60);
// 2. Estimate amounts from one side
const [amountA, amountB] = sdk.pool.estimateAmountsFromOneAmount({
sqrtPrice: pool.sqrt_price,
tickLower,
tickUpper,
amount: '1000000000',
isAmountA: true,
});
// 3. Add liquidity (opens new position)
const txb = await sdk.pool.addLiquidity({
pool: poolId,
address: walletAddress,
amountA,
amountB,
tickLower,
tickUpper,
slippage: '5',
});
// 4. Sign and execute
await suiClient.signAndExecuteTransaction({ transaction: txb, signer: keypair });
Package Reference
| Package | npm | Purpose |
|---|
| CLMM SDK | turbos-clmm-sdk | Concentrated liquidity AMM (pools, positions, swaps) |
| Peer Dep | @mysten/sui | Sui blockchain client |