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.
V3 pools support free balances: per-user balances held within the pool that can be used for subsequent operations without additional Spark transfers. This reduces latency and overhead for market makers and frequent traders.
How It Works
When you withdraw liquidity, collect fees, or rebalance positions, you can retain the funds in a pool-level free balance instead of receiving a Spark transfer. These retained funds can then fund future operations like adding liquidity, all without leaving the pool.
Benefits
- Reduced latency: No waiting for Spark transfer confirmations when redeploying capital
- Lower overhead: Eliminates transfer fees for funds that stay in the pool
- Atomic availability: Retained funds are immediately usable for the next operation
- Simplified workflows: Compound fees or rebalance without capital leaving the system
Retaining Funds
On Decrease Liquidity
const response = await client.decreaseLiquidity({
poolId: "pool-public-key",
tickLower: -23040,
tickUpper: -22980,
liquidityToRemove: "0", // All
amountAMin: "0",
amountBMin: "0",
retainInBalance: true, // Keep funds in free balance
});
console.log('Retained in balance:', response.retainedInBalance);
console.log('Current balance:', response.currentBalance);
// { balanceA: "1000000", balanceB: "500000" }
On Collect Fees
const response = await client.collectFees({
poolId: "pool-public-key",
tickLower: -23040,
tickUpper: -22980,
retainInBalance: true,
});
console.log('Fees retained:', response.feesCollectedA, response.feesCollectedB);
console.log('Current balance:', response.currentBalance);
On Rebalance
const response = await client.rebalancePosition({
poolId: "pool-public-key",
oldTickLower: -23040,
oldTickUpper: -22980,
newTickLower: -23100,
newTickUpper: -22920,
liquidityToMove: "0",
retainInBalance: true, // Keep any net excess in balance
});
Using Free Balances
Add Liquidity from Balance
Use useFreeBalanceA and useFreeBalanceB to fund operations from your retained balance:
const response = await client.increaseLiquidity({
poolId: "pool-public-key",
tickLower: -23100,
tickUpper: -22920,
amountADesired: "100000000",
amountBDesired: "100000000",
amountAMin: "0",
amountBMin: "0",
useFreeBalanceA: true, // Draw from free balance first
useFreeBalanceB: true,
});
console.log('Liquidity added:', response.liquidityAdded);
console.log('Amount A used:', response.amountAUsed);
console.log('Amount B used:', response.amountBUsed);
If your free balance is insufficient, the SDK automatically supplements with a Spark transfer for the difference.
Retain Excess on Add
When adding liquidity, any unused tokens (refunds) can be retained instead of returned via Spark:
const response = await client.increaseLiquidity({
poolId: "pool-public-key",
tickLower: -23100,
tickUpper: -22920,
amountADesired: "100000000",
amountBDesired: "100000000",
amountAMin: "0",
amountBMin: "0",
retainExcessInBalance: true, // Keep refunds in balance
});
console.log('Liquidity added:', response.liquidityAdded);
console.log('Amount A refund:', response.amountARefund);
console.log('Amount B refund:', response.amountBRefund);
console.log('Retained in balance:', response.retainedInBalance);
Checking Balances
Single Pool
const balance = await client.getConcentratedBalance("pool-public-key");
console.log('Pool:', balance.poolId);
console.log('Asset A:', balance.assetAAddress);
console.log('Asset B:', balance.assetBAddress);
console.log('Balance A:', balance.balanceA);
console.log('Balance B:', balance.balanceB);
console.log('Available A:', balance.availableA); // Total minus locked
console.log('Available B:', balance.availableB);
console.log('Locked A:', balance.lockedA); // In-flight operations
console.log('Locked B:', balance.lockedB);
All Pools
const response = await client.getConcentratedBalances();
for (const balance of response.balances) {
console.log(`Pool ${balance.poolId}:`);
console.log(` A: ${balance.balanceA} (${balance.availableA} available)`);
console.log(` B: ${balance.balanceB} (${balance.availableB} available)`);
}
Withdrawing Balances
To move funds from your free balance to your Spark wallet:
const response = await client.withdrawConcentratedBalance({
poolId: "pool-public-key",
amountA: "500000", // Specific amount
amountB: "max", // All available
});
console.log('Withdrawn A:', response.amountAWithdrawn);
console.log('Withdrawn B:', response.amountBWithdrawn);
console.log('Remaining A:', response.remainingBalanceA);
console.log('Remaining B:', response.remainingBalanceB);
console.log('Transfer IDs:', response.outboundTransferIds);
Amount Options
| Value | Behavior |
|---|
"500000" | Withdraw specific amount |
"max" or "0" | Withdraw entire balance |
| Empty string | Skip this asset |
Common Workflows
Fee Compounding
Reinvest earned fees without Spark transfers:
async function compoundFees(poolId: string, tickLower: number, tickUpper: number) {
// 1. Collect fees and retain
await client.collectFees({
poolId,
tickLower,
tickUpper,
retainInBalance: true,
});
// 2. Check accumulated balance
const balance = await client.getConcentratedBalance(poolId);
console.log('Compounding:', balance.balanceA, 'A,', balance.balanceB, 'B');
// 3. Add liquidity from balance
await client.increaseLiquidity({
poolId,
tickLower,
tickUpper,
amountADesired: balance.availableA,
amountBDesired: balance.availableB,
amountAMin: "0",
amountBMin: "0",
useFreeBalanceA: true,
useFreeBalanceB: true,
});
console.log('Fees compounded into position');
}
Zero-Transfer Rebalancing
Rebalance positions without any Spark overhead:
async function zeroTransferRebalance(
poolId: string,
oldLower: number,
oldUpper: number,
newLower: number,
newUpper: number
) {
// 1. Remove and retain
await client.decreaseLiquidity({
poolId,
tickLower: oldLower,
tickUpper: oldUpper,
liquidityToRemove: "0",
amountAMin: "0",
amountBMin: "0",
retainInBalance: true,
});
// 2. Check balance
const balance = await client.getConcentratedBalance(poolId);
// 3. Create new position from balance
await client.increaseLiquidity({
poolId,
tickLower: newLower,
tickUpper: newUpper,
amountADesired: balance.availableA,
amountBDesired: balance.availableB,
amountAMin: "0",
amountBMin: "0",
useFreeBalanceA: true,
useFreeBalanceB: true,
retainExcessInBalance: true,
});
console.log('Rebalanced with zero Spark transfers');
}
Or use the atomic rebalance operation:
await client.rebalancePosition({
poolId,
oldTickLower: oldLower,
oldTickUpper: oldUpper,
newTickLower: newLower,
newTickUpper: newUpper,
liquidityToMove: "0",
retainInBalance: true, // Keep any net difference in balance
});
Market Maker Loop
High-frequency position management:
async function mmRebalanceLoop(poolId: string) {
let currentLower = -23040;
let currentUpper = -22980;
while (true) {
// Check if still in range
const positions = await client.listConcentratedPositions({ poolId });
const position = positions.positions.find(
p => p.tickLower === currentLower && p.tickUpper === currentUpper
);
if (!position?.inRange) {
// Get current price
const liquidity = await client.getPoolLiquidity(poolId);
const spacing = liquidity.tickSpacing;
const tick = liquidity.currentTick;
// Calculate new range
const rangeWidth = currentUpper - currentLower;
const newLower = Math.floor((tick - rangeWidth / 2) / spacing) * spacing;
const newUpper = Math.ceil((tick + rangeWidth / 2) / spacing) * spacing;
// Atomic rebalance, retain excess
await client.rebalancePosition({
poolId,
oldTickLower: currentLower,
oldTickUpper: currentUpper,
newTickLower: newLower,
newTickUpper: newUpper,
liquidityToMove: "0",
retainInBalance: true,
});
currentLower = newLower;
currentUpper = newUpper;
console.log(`Rebalanced to ${newLower}-${newUpper}`);
}
await sleep(60000); // Check every minute
}
}
Response Fields
All V3 liquidity operations include balance information when using free balance features:
| Field | Type | Description |
|---|
retainedInBalance | boolean | Whether funds were retained in balance |
currentBalance | object | Updated balance after operation (balanceA, balanceB) |
amountARefund | string | Excess asset A returned (increase liquidity) |
amountBRefund | string | Excess asset B returned (increase liquidity) |
Balance States
| State | Description |
|---|
balance | Total funds in your free balance |
available | Amount you can use (total minus locked) |
locked | Funds in pending operations |
Next Steps