> ## Documentation Index
> Fetch the complete documentation index at: https://build.flashnet.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Free Balances

> Retain and reuse funds within V3 pools to reduce transfer overhead

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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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

* [Pool Creation](/products/flashnet-amm/concentrated-creation) - Create new V3 pools
* [Managing Positions](/products/flashnet-amm/concentrated-positions) - Position lifecycle operations
* [Error Handling](/products/flashnet-amm/errors) - Handle failures gracefully
