Install
openclaw skills install turbosIntegrate the Turbos Finance CLMM SDK on Sui for pools, swaps, positions, fees, and liquidity workflows. Use when building or analyzing apps that interact with Turbos Finance concentrated liquidity pools.
openclaw skills install turbosTurbos Finance is a Concentrated Liquidity Market Maker (CLMM) DEX on Sui. This guide covers the turbos-clmm-sdk package for building DeFi applications.
turbos-clmm-sdkpnpm add turbos-clmm-sdk @mysten/sui
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' });
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 |
Protocol configuration and fee tier management.
import type { Contract } from 'turbos-clmm-sdk';
const config: Contract.Config = await sdk.contract.getConfig();
// Returns: { PackageId, PackageIdOriginal, PoolConfig, Positions, Versioned, PoolTableId, ... }
const fees: Contract.Fee[] = await sdk.contract.getFees();
// Each fee: { fee: number, objectId: string, type: string, tickSpacing: number }
// Get fee tier by tick spacing
const fee: Contract.Fee = await sdk.contract.getFee(60);
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;
}
Core pool operations: create, add/remove liquidity, collect fees/rewards.
// Get all unlocked pools
const pools: Pool.Pool[] = await sdk.pool.getPools();
// Include locked pools
const allPools: Pool.Pool[] = await sdk.pool.getPools(true);
const pool: Pool.Pool = await sdk.pool.getPool(poolId);
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
});
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
});
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,
});
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',
});
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
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'],
});
const txb = await sdk.pool.collectFee({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
collectAmountA: '1000',
collectAmountB: '1000',
});
const txb = await sdk.pool.collectReward({
pool: poolId,
nft: positionNftId,
address: 'YOUR_SUI_ADDRESS',
rewardAmounts: ['500', '0', '0'], // Amount per reward slot
});
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,
});
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
});
// Returns [coinTypeA, coinTypeB, feeType]
const types: Pool.Types = await sdk.pool.getPoolTypeArguments(poolId);
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, ... }
Swap operations with single-hop and multi-hop routing.
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, ... }
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,
});
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
});
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',
});
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',
});
Position (NFT) management and analytics. sdk.position replaces the deprecated sdk.nft.
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:
{PackageIdOriginal}::position_nft::TurbosPositionNFTposition_id and pool_id fields linking to the actual position data{PackageId}::position_manager::TurbosPositionBurnNFT, with pool ID nested at position_nft.fields.pool_idsdk.math.bitsToNumber(tick_index.fields.bits) to convert tick index from on-chain I32 formatconst fields = await sdk.position.getFields(nftId);
// Returns: { description, id, img_url, name, pool_id, position_id }
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 }
const position = await sdk.position.getPositionFieldsByPositionId(positionId);
const tick = await sdk.position.getPositionTick(poolId, position.tick_lower_index);
// Returns: { tickIndex, initialized, liquidityNet, liquidityGross,
// feeGrowthOutsideA, feeGrowthOutsideB, rewardGrowthsOutside }
const usdValue = await sdk.position.getPositionLiquidityUSD({
poolId,
position, // PositionField
priceA: '1.00', // USD price of coin A
priceB: '50000', // USD price of coin B
});
const result = await sdk.position.getUnclaimedFeesAndRewards({
poolId,
position,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { fees, rewards, total, fields: { feeOwedA, feeOwedB, collectRewards, ... } }
const fees = await sdk.position.getUnclaimedFees({
pool,
position,
tickLowerDetail,
tickUpperDetail,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { feeOwedA, feeOwedB, unclaimedFees, scaledFeeOwedA, scaledFeeOwedB }
const rewards = await sdk.position.getUnclaimedRewards({
pool,
position,
tickLowerDetail,
tickUpperDetail,
getPrice: async (coinType) => getPriceForCoin(coinType),
});
// Returns: { unclaimedRewards, collectRewards, scaledCollectRewards }
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 a position NFT (must have 0 liquidity):
const txb = await sdk.position.burn({
pool: poolId,
nft: positionNftId,
});
Keypair and mnemonic utilities.
const mnemonic24 = sdk.account.generateMnemonic(24); // 24 words (default)
const mnemonic12 = sdk.account.generateMnemonic(12); // 12 words
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}'
Tick, price, and sqrt math utilities accessed via sdk.math.
// 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);
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 } };
}
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 };
}
enum Network {
mainnet = 'mainnet',
testnet = 'testnet',
}
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 });
// 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 | npm | Purpose |
|---|---|---|
| CLMM SDK | turbos-clmm-sdk | Concentrated liquidity AMM (pools, positions, swaps) |
| Peer Dep | @mysten/sui | Sui blockchain client |