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).
tick lower upper position id wallet address
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.
add liquidity by ticks price in estimate
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();
remove liquidity withdrawal quantity token0 token1 collect position fees
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.
create pool token0 symbol token1 fetch token classes not registered starting price sqrt conflict chain dispatch failed