Skip to main content

Liquidity Management

V2 positions are identified by (token0, token1, fee, owner, tickLower, tickUpper). There is no positionId. Tokens may be symbols or class keys; the SDK resolves and orders them.

The examples on this page continue from the gSwap client built in Getting started (with a signer and a walletAddress, which confirming a liquidity write needs) and run in an ES module (top-level await).

Ticks and price ranges​

addLiquidityByTicks is the precise API. Ticks must be aligned to the pool's fee-tier spacing. A range that does not contain the pool's current tick takes only one token and earns no fees, so derive the range from the pool's tick instead of hard-coding it (here 60 is the spacing of fee tier 3000):

const slot0 = await gSwap.pools.getSlot0('GALA', 'GUSDC', 3000);
const tickLower = Math.floor((slot0.tick - 6000) / 60) * 60;
const tickUpper = Math.ceil((slot0.tick + 6000) / 60) * 60;

const tx = await gSwap.positions.addLiquidityByTicks({
token0: 'GALA',
token1: 'GUSDC',
fee: 3000,
tickLower,
tickUpper,
amount: '100',
amountIsToken0: true,
});
await tx.confirm();

The spacing is 0 → 200, 500 → 10, 3000 → 60, and 10000 → 200. The lower tick must be below the upper tick, both ticks must be within the contract bounds, and each must be divisible by the pool spacing.

Use addLiquidityByPrice when the UI works in token1-per-token0 prices. The SDK converts the range to ticks and aligns it to the pool spacing. Prices are branded (PriceIn, see Branded Types): a plain string does not compile, so declare what it is with as PriceIn:

import type { PriceIn } from '@gala-chain/gswap-sdk';

const tx = await gSwap.positions.addLiquidityByPrice({
token0: 'GALA',
token1: 'GUSDC',
fee: 3000,
minPrice: '0.02' as PriceIn,
maxPrice: '0.04' as PriceIn,
amount: '100',
amountIsToken0: true,
});

Both add APIs require exactly one deposit side. Use estimateAddLiquidity first to calculate the other side for the selected range and current pool price.

Remove and collect​

removeLiquidity accepts an optional withdrawal quantity. Supplying neither quantity closes the position completely and sweeps its accrued fees:

// The ticks the position was added with (multiples of the pool's spacing).
const [tickLower, tickUpper] = [-22_860, -22_800];

const tx = await gSwap.positions.removeLiquidity({
token0: 'GALA',
token1: 'GUSDC',
fee: 3000,
tickLower, // the same ticks the position was added with
tickUpper,
});
await tx.confirm();

When withdrawing partially, provide at most one of the SDK's amount0 or amount1 options. The SDK maps it to the contract's withdrawalQuantityToken0 or withdrawalQuantityToken1. The contract's CollectPositionFees operation always sweeps the complete accrued fee balance; there are no partial collect amount fields:

const [tickLower, tickUpper] = [-22_860, -22_800]; // the position's own ticks

const tx = await gSwap.positions.collectPositionFees({
token0: 'GALA',
token1: 'GUSDC',
fee: 3000,
tickLower, // the same ticks the position was added with
tickUpper,
});
await tx.confirm();

Creating a pool​

CreatePool is the first place a token's trading symbol may be claimed. Both symbols must be ASCII alphanumeric, globally available, and equal to the token class's own symbol. A class key carries no symbol (collection and symbol differ for many tokens, every launchpad token shares the collection Token, and some collections contain $), so the SDK never derives one from it. Tokens already in the trading-symbol registry resolve automatically; for a token being registered for the first time you must pass token0Symbol / token1Symbol (read from the token list or FetchTokenClasses), otherwise createPool throws TOKEN_SYMBOL_NOT_REGISTERED. The canonical pair is ordered so the lower symbol is token0 using plain string comparison. Supply exactly one positive startingPrice or startingSqrtPrice:

// A pair with no pool yet; the symbols are the token classes' own `symbol` values.
const tx = await gSwap.positions.createPool({
token0: 'MYTOKEN|Unit|none|none',
token1: 'GUSDC|Unit|none|none',
token0Symbol: 'MYTOKEN',
fee: 3000,
startingPrice: '0.03',
});
console.log(tx.transactionId ?? tx.uniqueKey);

Creating a pool for a pair and fee tier that already has one is rejected (SYMBOL_CONFLICT or CHAIN_DISPATCH_FAILED, "already exists"), so GALA/GUSDC at 3000 cannot be created again.

An optional name (omitted when undefined or empty) is sent as the DTO's name.

CreatePool is not organization-gated. Treat symbols as a global namespace and choose them carefully; the first successful pool can register them permanently. CreatePool has no affected position to re-read, so its confirm() result is null; use the executed result and transaction metadata above.