Skip to main content

Minting a Position

Introduction

After pool creation you can create position (see create_position.rs).

You will learn how to create a new liquidity position, add liquidity, and then remove liquidity in this guide.

Examples

Create position on existing pool

Ticks and position list for the position must be created if they don't exist already. This can be done using createTick method.

// first the ticks must be created if they don't exist already
const createLowerTickVars: CreateTick = {
pair,
index: lowerTick,
payer: owner.publicKey,
};

const createUpperTickVars: CreateTick = {
pair,
index: upperTick,
payer: owner.publicKey,
};

const ticks = await market.getAllIndexedTicks(pair);
if (!ticks.get(lowerTick)) {
await market.createTick(createLowerTickVars, owner);
}

if (!ticks.get(upperTick)) {
await market.createTick(createUpperTickVars, owner);
}

// create position list if it doesn't exist yet
try {
await market.getPositionList(positionOwner.publicKey);
} catch (e) {
await market.createPositionList(positionOwner.publicKey, positionOwner);
}
const positionList = await market.getPositionList(positionOwner.publicKey);

The highest amount of the liquidity for the position can be calculated based on owned token amounts using the getLiquidity function.

export const getLiquidity = (
x: BN,
y: BN,
lowerTick: number,
upperTick: number,
currentSqrtPrice: Decimal,
roundingUp: boolean,
tickSpacing?: number
): {
x: BN
y: BN
liquidity: BN
}

const pool = await market.getPool(pair)
const {
x,
y,
liquidity: liquidityDelta
} = getLiquidity(
tokenXAmount,
tokenYAmount,
lowerTick,
upperTick,
pool.sqrtPrice,
true,
pool.tickSpacing
)

Create position on existing pool

Having all the necessary data position can be created using initPosition entrypoint.

const initPositionVars: InitPosition = {
pair,
owner: owner.publicKey,
userTokenX: userTokenXAccount,
userTokenY: userTokenYAccount,
lowerTick,
upperTick,
liquidityDelta,
knownPrice: pool.sqrtPrice,
slippage: slippage,
};
await market.initPosition(initPositionVars, positionOwner, {
positionList: { head: positionList.head, initialized: true },
lowerTickExists: true,
upperTickExists: true,
pool,
tokenXProgramAddress: TOKEN_PROGRAM_ID,
tokenYProgramAddress: TOKEN_PROGRAM_ID,
});

Create position on existing pool with wrapped ETH

if (wrappedEthPair.tokenX === NATIVE_MINT) {
wethAmount = x;
} else {
wethAmount = y;
}
const { createIx, initIx, transferIx, unwrapIx } =
createNativeAtaWithTransferInstructions(
wrappedEthAccount.publicKey,
positionOwner.publicKey,
Network.LOCAL,
wethAmount
);

const slippage = toDecimal(0, 0);

let initPositionVars: InitPosition;

if (wrappedEthPair.tokenX === NATIVE_MINT) {
initPositionVars = {
pair: wrappedEthPair,
owner: positionOwner.publicKey,
userTokenX: wrappedEthAccount.publicKey,
userTokenY: userTokenAccount,
lowerTick,
upperTick,
liquidityDelta,
knownPrice: pool.sqrtPrice,
slippage: slippage,
};
} else {
initPositionVars = {
pair: wrappedEthPair,
owner: positionOwner.publicKey,
userTokenX: userTokenAccount,
userTokenY: wrappedEthAccount.publicKey,
lowerTick,
upperTick,
liquidityDelta,
knownPrice: pool.sqrtPrice,
slippage: slippage,
};
}

const initPositionIx = await market.initPositionTx(initPositionVars);
const tx = new Transaction()
// first 3 ixs create a weth account
// last one returns the funds to the wallet
.add(createIx)
.add(transferIx)
.add(initIx)
.add(initPositionIx)
.add(unwrapIx);
await signAndSend(tx, [positionOwner, wrappedEthAccount], connection);

Create pool and position

Pools price is defined by a tick index, it can be calculated as follows based on real price of the tokens.

const decimalsX = await tokenX.getMintInfo().then((mint) => mint.decimals);
const decimalsY = await tokenY.getMintInfo().then((mint) => mint.decimals);

const realPrice = 64000; // y to x token ratio
const priceWithDecimals =
(realPrice * Math.pow(10, decimalsX)) / Math.pow(10, decimalsY);
const initTickPool = alignTickToSpacing(
priceToTick(priceWithDecimals),
otherFeeTier.tickSpacing!
);
const startingPrice = calculatePriceSqrt(initTickPool);

getLiquidity function can be used to calculate token amounts based on current balances.

const userTokenXAccount = await getOrCreateAssociatedTokenAccount(
connection,
positionOwner,
pair.tokenX,
positionOwner.publicKey
).then((ata) => ata.address);
const userTokenYAccount = await getOrCreateAssociatedTokenAccount(
connection,
positionOwner,
pair.tokenY,
positionOwner.publicKey
).then((ata) => ata.address);

// liquidity can be calculated using the getLiquidity function
// it will return an amount of liquidity that can be payed for with both tokens
const tokenXAmount = new BN(20000000);
const tokenYAmount = new BN(20000000);
const {
x,
y,
liquidity: liquidityDelta,
} = getLiquidity(
tokenXAmount,
tokenYAmount,
lowerTick,
upperTick,
startingPrice,
true,
otherPair.feeTier.tickSpacing
);

// the price that is calculated might differ slightly due to floating point and sdk inaccuracies so a small amount of slippage is required
const slippage = toDecimal(1, 2);

const initPositionVars: InitPoolAndPosition = {
pair: otherPair,
owner: positionOwner.publicKey,
userTokenX: userTokenXAccount,
userTokenY: userTokenYAccount,
lowerTick,
upperTick,
liquidityDelta,
knownPrice: startingPrice,
slippage: slippage,
initTick: initTickPool,
};

await market.initPoolAndPosition(initPositionVars, positionOwner);

RemovePosition

To remove a position you need to find the id of your position. Easiest way to do that is by querying all the positions.

const positionList = await market.getAllUserPositionsWithIds(
positionOwner.publicKey
);
// Index can be picked based on the position list
const positionToRemove = 0;
const removedPosition = positionList.find((p) => p[0] == positionToRemove);

console.log(removedPosition);

const positionId = positionList[positionToRemove][0];

Remove position

const removePosition: RemovePosition = {
pair,
owner: positionOwner.publicKey,
index: positionId,
userTokenX: userTokenXAccount,
userTokenY: userTokenYAccount,
};

await market.removePosition(removePosition, positionOwner);

Remove position with wrapped eth

const wrappedEthAccount = Keypair.generate();

const { createIx, initIx, unwrapIx } = createNativeAtaInstructions(
wrappedEthAccount.publicKey,
positionOwner.publicKey,
Network.LOCAL
);

let token: Token;
if (wrappedEthPair.tokenX === NATIVE_MINT) {
if (wrappedEthPair.tokenY === pair.tokenX) {
token = tokenX;
} else {
token = tokenY;
}
} else {
if (wrappedEthPair.tokenX === pair.tokenX) {
token = tokenX;
} else {
token = tokenY;
}
}

let token: PublicKey;
if (wrappedEthPair.tokenX === NATIVE_MINT) {
token = wrappedEthPair.tokenY;
} else {
token = wrappedEthPair.tokenX;
}

let userTokenX: PublicKey;
let userTokenY: PublicKey;
const userTokenAccount = getAssociatedTokenAddressSync(
token,
positionOwner.publicKey
);

if (wrappedEthPair.tokenX === NATIVE_MINT) {
userTokenX = wrappedEthAccount.publicKey;
userTokenY = userTokenAccount;
} else {
userTokenX = userTokenAccount;
userTokenY = wrappedEthAccount.publicKey;
}

const removePosition: RemovePosition = {
pair: wrappedEthPair,
owner: positionOwner.publicKey,
index: positionId,
userTokenX,
userTokenY,
};

const removePositionIx = await market.removePositionInstruction(removePosition);
const tx = new Transaction();
tx.add(createIx).add(initIx).add(removePositionIx).add(unwrapIx);

await signAndSend(tx, [positionOwner, wrappedEthAccount], connection);