Documentation Index
Fetch the complete documentation index at: https://flashnet-build.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Concentrated liquidity (V3) allows you to provide liquidity within a specific price range instead of across all prices. Capital concentrated in active trading ranges earns more fees per dollar deployed.
V3 and V2 Pools
V3 pools complement the existing V2 constant product and bonding curve pools. Both pool types coexist:
- V2 Constant Product: Liquidity spread across all prices, fungible LP tokens, simpler management
- V2 Bonding Curve: Single-sided pools that bond to constant product
- V3 Concentrated: Custom price ranges, non-fungible positions, higher capital efficiency
All pool types appear in listPools(). V3 pools have curveType: "V3_CONCENTRATED" and include additional fields like currentTick, tickSpacing, and totalLiquidity.
const pools = await client.listPools();
for (const pool of pools.pools) {
if (pool.curveType === 'V3_CONCENTRATED') {
console.log('V3 pool:', pool.lpPublicKey);
console.log(' Current tick:', pool.currentTick);
console.log(' Tick spacing:', pool.tickSpacing);
console.log(' Positions:', pool.positionCount);
} else {
console.log('V2 pool:', pool.lpPublicKey, pool.curveType);
}
}
V3 pools also have dedicated endpoints for detailed data: getPoolLiquidity() for depth chart visualization and getPoolTicks() for swap simulation.
How V3 Works
In V2 constant product pools, liquidity spreads from zero to infinity. Most of it sits idle outside the trading range. V3 pools let you choose where your capital works.
The math uses ticks to represent prices. Each tick is a 0.01% price change from the previous tick:
Your position is defined by a lower tick and upper tick. When the current price is within your range, your liquidity earns fees. When price moves outside, your position becomes single-sided and stops earning.
Quick Comparison
| Aspect | V2 Pools | V3 Pools |
|---|
| Liquidity Range | 0 to infinity | Custom price range |
| Capital Efficiency | ~50% active | Up to 4000x better |
| Position Type | Fungible LP tokens | Non-fungible positions |
| Fee Tracking | Global per pool | Per-position |
Getting Started
Create a Position
async function createPosition() {
const response = await client.increaseLiquidity({
poolId: "pool-public-key",
tickLower: -23040, // Lower price bound
tickUpper: -22980, // Upper price bound
amountADesired: "100000000", // 1 BTC in satoshis
amountBDesired: "100000000", // 100,000 USDB in base units
amountAMin: "99000000", // 1% slippage protection
amountBMin: "99000000",
});
console.log('Liquidity added:', response.liquidityAdded);
console.log('Asset A used:', response.amountAUsed);
console.log('Asset B used:', response.amountBUsed);
}
Convert Prices to Ticks
The SDK provides utilities to convert between human-readable prices and ticks:
import { V3TickMath } from '@flashnet/sdk';
// Convert a human price like "$90,000 per BTC" to a tick
const tick = V3TickMath.humanPriceToTick({
humanPrice: "90000",
baseDecimals: 8, // BTC (base asset)
quoteDecimals: 6, // USDB (quote asset)
tickSpacing: 60,
});
// Get a range around a target price
const range = V3TickMath.tickRangeFromPrices({
priceLower: "85000",
priceUpper: "95000",
baseDecimals: 8,
quoteDecimals: 6,
tickSpacing: 60,
});
console.log('Tick range:', range.tickLower, 'to', range.tickUpper);
Core Operations
Add Liquidity
Creates a new position or adds to an existing one at the same tick range:
const response = await client.increaseLiquidity({
poolId: "pool-public-key",
tickLower: -23040,
tickUpper: -22980,
amountADesired: "100000000",
amountBDesired: "100000000",
amountAMin: "99000000",
amountBMin: "99000000",
});
Remove Liquidity
Withdraws liquidity and collects accumulated fees:
const response = await client.decreaseLiquidity({
poolId: "pool-public-key",
tickLower: -23040,
tickUpper: -22980,
liquidityToRemove: "123456789", // V3 liquidity units, or "0" for all
amountAMin: "0",
amountBMin: "0",
});
console.log('Principal returned:', response.amountA, response.amountB);
console.log('Fees collected:', response.feesCollectedA, response.feesCollectedB);
Collect Fees
Claim earned fees without removing liquidity:
const response = await client.collectFees({
poolId: "pool-public-key",
tickLower: -23040,
tickUpper: -22980,
});
console.log('Fees collected:', response.feesCollectedA, response.feesCollectedB);
Rebalance Position
Move your liquidity to a new price range in a single atomic operation:
const response = await client.rebalancePosition({
poolId: "pool-public-key",
oldTickLower: -23040,
oldTickUpper: -22980,
newTickLower: -23100,
newTickUpper: -22920,
liquidityToMove: "0", // 0 = move all
});
console.log('Old liquidity:', response.oldLiquidity);
console.log('New liquidity:', response.newLiquidity);
console.log('Fees collected:', response.feesCollectedA, response.feesCollectedB);
Pool Data
Get Liquidity Distribution
For building depth charts and visualizations:
const liquidity = await client.getPoolLiquidity("pool-public-key");
console.log('Current price:', liquidity.currentPrice);
console.log('Active liquidity:', liquidity.activeLiquidity);
// Iterate through liquidity ranges
for (const range of liquidity.ranges) {
console.log(`${range.priceLower} - ${range.priceUpper}: ${range.liquidity}`);
console.log(` Status: ${range.status}`); // "in_range", "below_price", or "above_price"
}
Get Pool Ticks
For swap simulation and slippage calculation:
const ticks = await client.getPoolTicks("pool-public-key");
console.log('Current tick:', ticks.currentTick);
console.log('Current liquidity:', ticks.currentLiquidity);
console.log('LP fee:', ticks.lpFeeBps, 'bps');
// Use ticks for simulation
for (const tick of ticks.ticks) {
console.log(`Tick ${tick.tick}: net=${tick.liquidityNet}`);
}
Tick Spacing
Pools have a tick spacing that determines valid tick boundaries. Common values:
| Tick Spacing | Price Step | Use Case |
|---|
| 10 | 0.1% | Stable pairs, tight ranges |
| 60 | 0.6% | Most trading pairs |
| 200 | 2% | Volatile pairs |
Ticks must be divisible by the pool’s tick spacing:
// Round a tick to the nearest valid boundary
const validTick = V3TickMath.roundTick(arbitraryTick, tickSpacing);
// Round down (conservative for lower bounds)
const lower = V3TickMath.roundTickDown(tick, tickSpacing);
// Round up (conservative for upper bounds)
const upper = V3TickMath.roundTickUp(tick, tickSpacing);
// Raw price/tick conversions (without decimal adjustment)
const rawTick = V3TickMath.priceToTick(poolPrice);
const rawPrice = V3TickMath.tickToPrice(tick);
// Tick bounds
console.log('Valid tick range:', V3TickMath.MIN_TICK, 'to', V3TickMath.MAX_TICK);
Position States
Your position’s behavior depends on where the current price sits relative to your range:
| Price Location | Asset A | Asset B | Earning Fees |
|---|
| Below your range | 100% | 0% | No |
| Within your range | Mixed | Mixed | Yes |
| Above your range | 0% | 100% | No |
When price moves outside your range, your position converts entirely to one asset and stops earning fees until price returns.
Error Handling
import { isFlashnetError } from '@flashnet/sdk';
try {
await client.increaseLiquidity({ ... });
} catch (error) {
if (isFlashnetError(error)) {
console.log('Error:', error.errorCode);
console.log('Message:', error.userMessage);
// Check for fund recovery
if (error.wasClawbackAttempted()) {
const summary = error.clawbackSummary!;
console.log(`Recovered: ${summary.successCount}/${summary.totalTransfers}`);
}
}
throw error;
}
Next Steps