diff --git a/ts-client/README.md b/ts-client/README.md
index 44208f76..678c4699 100644
--- a/ts-client/README.md
+++ b/ts-client/README.md
@@ -5,6 +5,189 @@
+## Getting started
+
+NPM: https://www.npmjs.com/package/@meteora-ag/dlmm-sdk-public
+
+SDK: https://github.com/MeteoraAg/dlmm-sdk
+
+
+
+Discord: https://discord.com/channels/841152225564950528/864859354335412224
+
+## Install
+
+1. Install deps
+
+```
+npm i @meteora-ag/dlmm-sdk-public @coral-xyz/anchor @solana/web3.js
+```
+
+2. Initialize DLMM instance
+
+```ts
+const USDC_USDT_POOL = new PublicKey('ARwi1S4DaiTG5DX7S4M4ZsrXqpMD1MrTmbu9ue2tpmEq') // You can get your desired pool address from the API https://dlmm-api.meteora.ag/pair/all
+const dlmmPool = await DLMM.create(connection, USDC_USDT_POOL, {
+ cluster: "devnet",
+});
+
+// If you need to create multiple, can consider using `createMultiple`
+const dlmmPool = await DLMM.create(connection, [USDC_USDT_POOL, ...], {
+ cluster: "devnet",
+});
+
+```
+
+3. To interact with the AmmImpl
+
+- Get Active Bin
+
+```ts
+const activeBin = await dlmmPool.getActiveBin();
+const activeBinPriceLamport = activeBin.price;
+const activeBinPricePerToken = dlmmPool.fromPricePerLamport(
+ Number(activeBin.price)
+);
+```
+
+- Create Position
+
+```ts
+const TOTAL_RANGE_INTERVAL = 10; // 10 bins on each side of the active bin
+const bins = [activeBin.binId]; // Make sure bins is less than 70, as currently only support up to 70 bins for 1 position
+for (
+ let i = activeBin.binId;
+ i < activeBin.binId + TOTAL_RANGE_INTERVAL / 2;
+ i++
+) {
+ const rightNextBinId = i + 1;
+ const leftPrevBinId = activeBin.binId - (rightNextBinId - activeBin.binId);
+ bins.push(rightNextBinId);
+ bins.unshift(leftPrevBinId);
+}
+
+const activeBinPricePerToken = dlmmPool.fromPricePerLamport(
+ Number(activeBin.price)
+);
+const totalXAmount = new BN(100);
+const totalYAmount = totalXAmount.mul(new BN(Number(activeBinPricePerToken)));
+
+// Get spot distribution (You can calculate with other strategy `calculateSpotDistribution`, `calculateNormalDistribution`)
+const spotXYAmountDistribution = calculateSpotDistribution(
+ activeBin.binId,
+ bins
+);
+const newPosition = new Keypair();
+const createPositionTx =
+ await dlmmPool.initializePositionAndAddLiquidityByWeight({
+ positionPubKey: newPosition.publicKey,
+ lbPairPubKey: dlmmPool.pubkey,
+ user: user.publicKey,
+ totalXAmount,
+ totalYAmount,
+ xYAmountDistribution: spotXYAmountDistribution,
+ });
+
+try {
+ for (let tx of Array.isArray(createPositionTx)
+ ? createPositionTx
+ : [createPositionTx]) {
+ const createPositionTxHash = await sendAndConfirmTransaction(
+ connection,
+ tx,
+ [user, newPosition]
+ );
+ }
+} catch (error) {}
+```
+
+- Get list of positions
+
+```ts
+const { userPositions } = await dlmmPool.getPositionsByUserAndLbPair(
+ user.publicKey
+);
+const binData = userPositions[0].positionData.positionBinData;
+```
+
+- Add liquidity to existing position
+
+```ts
+const addLiquidityTx = await dlmmPool.addLiquidityByWeight({
+ positionPubKey: userPositions[0].publicKey,
+ lbPairPubKey: dlmmPool.pubkey,
+ user: user.publicKey,
+ totalXAmount,
+ totalYAmount,
+ xYAmountDistribution: spotXYAmountDistribution,
+});
+
+try {
+ for (let tx of Array.isArray(addLiquidityTx)
+ ? addLiquidityTx
+ : [addLiquidityTx]) {
+ const addLiquidityTxHash = await sendAndConfirmTransaction(connection, tx, [
+ user,
+ newPosition,
+ ]);
+ }
+} catch (error) {}
+```
+
+- Remove Liquidity
+
+```ts
+const binIdsToRemove = userPositions[0].positionData.positionBinData.map(
+ (bin) => bin.binId
+);
+const removeLiquidityTx = await dlmmPool.removeLiquidity({
+ position: userPositions[0].publicKey,
+ user: user.publicKey,
+ binIds: binIdsToRemove,
+ liquiditiesBpsToRemove: new Array(binIdsToRemove.length).fill(
+ new BN(100 * 100)
+ ), // 100% (range from 0 to 100)
+ shouldClaimAndClose: true, // should claim swap fee and close position together
+});
+
+try {
+ for (let tx of Array.isArray(removeLiquidityTx)
+ ? removeLiquidityTx
+ : [removeLiquidityTx]) {
+ const removeLiquidityTxHash = await sendAndConfirmTransaction(
+ connection,
+ tx,
+ [user, newPosition]
+ );
+ }
+} catch (error) {}
+```
+
+- Swap
+
+```ts
+const swapAmount = new BN(100);
+// Swap quote
+const swapQuote = await dlmmPool.swapQuote(swapAmount, true, new BN(10));
+
+// Swap
+const swapTx = await dlmmPool.swap({
+ inToken: dlmmPool.tokenX.publicKey,
+ binArraysPubkey: swapQuote.binArraysPubkey,
+ inAmount: swapAmount,
+ lbPair: dlmmPool.pubkey,
+ user: user.publicKey,
+ minOutAmount: swapQuote.minOutAmount,
+ outToken: dlmmPool.tokenY.publicKey,
+});
+
+try {
+ const swapTxHash = await sendAndConfirmTransaction(connection, swapTx, [
+ user,
+ ]);
+} catch (error) {}
+```
+
## Static functions
| Function | Description | Return |
@@ -41,3 +224,7 @@
| `claimSwapFee` | Claim swap fees for a specific position owned by a specific owner | `Promise` |
| `claimAllSwapFee` | Claim swap fees for multiple positions owned by a specific owner | `Promise` |
| `claimAllRewards` | Claim swap fees and LM rewards for multiple positions owned by a specific owner | `Promise` |
+
+```
+
+```
diff --git a/ts-client/package.json b/ts-client/package.json
index 0fb6f05b..3ca2d9cf 100644
--- a/ts-client/package.json
+++ b/ts-client/package.json
@@ -6,7 +6,8 @@
"scripts": {
"build": "tsup",
"start": "npm run build -- --watch",
- "test": "jest src/test/sdk.test.ts"
+ "test": "jest src/test/sdk.test.ts",
+ "example": "dotenv -e .env npx ts-node src/example.ts"
},
"devDependencies": {
"@babel/preset-env": "^7.22.5",
diff --git a/ts-client/src/dlmm/helpers/index.ts b/ts-client/src/dlmm/helpers/index.ts
index 249d69d8..9f66b2a1 100644
--- a/ts-client/src/dlmm/helpers/index.ts
+++ b/ts-client/src/dlmm/helpers/index.ts
@@ -11,6 +11,7 @@ import {
} from "@solana/spl-token";
import { SCALE_OFFSET } from "../constants";
import {
+ ComputeBudgetProgram,
Connection,
PublicKey,
SystemProgram,
@@ -179,3 +180,9 @@ export async function chunkedGetMultipleAccountInfos(
return accountInfos;
}
+
+export const computeBudgetIx = () => {
+ return ComputeBudgetProgram.setComputeUnitLimit({
+ units: 1_400_000,
+ });
+};
diff --git a/ts-client/src/dlmm/helpers/strategy.ts b/ts-client/src/dlmm/helpers/strategy.ts
index bfc20df7..9a0f40d3 100644
--- a/ts-client/src/dlmm/helpers/strategy.ts
+++ b/ts-client/src/dlmm/helpers/strategy.ts
@@ -9,8 +9,7 @@ import {
} from "../types";
import Decimal from "decimal.js";
-/** private */
-function getPriceOfBinByBinId(binId: number, binStep: number): Decimal {
+export function getPriceOfBinByBinId(binId: number, binStep: number): Decimal {
const binStepNum = new Decimal(binStep).div(new Decimal(BASIS_POINT_MAX));
return new Decimal(1).add(new Decimal(binStepNum)).pow(new Decimal(binId));
}
diff --git a/ts-client/src/dlmm/index.ts b/ts-client/src/dlmm/index.ts
index f66a3a9d..37d6e716 100644
--- a/ts-client/src/dlmm/index.ts
+++ b/ts-client/src/dlmm/index.ts
@@ -1,6 +1,5 @@
import {
Cluster,
- ComputeBudgetProgram,
Connection,
PublicKey,
TransactionInstruction,
@@ -79,6 +78,7 @@ import {
deriveLbPair,
deriveOracle,
derivePresetParameter,
+ computeBudgetIx,
} from "./helpers";
import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
import Decimal from "decimal.js";
@@ -1453,9 +1453,7 @@ export class DLMM {
closeWrappedSOLIx && postInstructions.push(closeWrappedSOLIx);
}
- const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
- units: 1_400_000,
- });
+ const setComputeUnitLimitIx = computeBudgetIx();
const minBinId = Math.min(...binIds);
const maxBinId = Math.max(...binIds);
@@ -1745,9 +1743,7 @@ export class DLMM {
closeWrappedSOLIx && postInstructions.push(closeWrappedSOLIx);
}
- const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
- units: 1_400_000,
- });
+ const setComputeUnitLimitIx = computeBudgetIx();
const liquidityParams: LiquidityParameterByWeight = {
amountX: totalXAmount,
@@ -1909,9 +1905,7 @@ export class DLMM {
);
const preInstructions: Array = [];
- const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
- units: 1_400_000,
- });
+ const setComputeUnitLimitIx = computeBudgetIx();
preInstructions.push(setComputeUnitLimitIx);
const [
@@ -2093,7 +2087,7 @@ export class DLMM {
position,
}: {
owner: PublicKey;
- position: { publicKey: PublicKey; positionData: PositionData };
+ position: Position;
}): Promise {
const { lowerBinId } = await this.program.account.positionV2.fetch(
position.publicKey
@@ -2287,11 +2281,7 @@ export class DLMM {
const { tokenXMint, tokenYMint, reserveX, reserveY, activeId, oracle } =
await this.program.account.lbPair.fetch(lbPair);
- const preInstructions: TransactionInstruction[] = [
- ComputeBudgetProgram.setComputeUnitLimit({
- units: 1_400_000,
- }),
- ];
+ const preInstructions: TransactionInstruction[] = [computeBudgetIx()];
const [
{ ataPubKey: userTokenIn, ix: createInTokenAccountIx },
@@ -2422,16 +2412,17 @@ export class DLMM {
const chunkedClaimAllTx = chunks(claimAllTxs, MAX_CLAIM_ALL_ALLOWED);
+ const { blockhash, lastValidBlockHeight } =
+ await this.program.provider.connection.getLatestBlockhash("confirmed");
return Promise.all(
chunkedClaimAllTx.map(async (claimAllTx) => {
return new Transaction({
feePayer: owner,
- ...(await this.program.provider.connection.getLatestBlockhash(
- "finalized"
- )),
+ blockhash,
+ lastValidBlockHeight,
})
- .add(...claimAllTx)
- .add(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }));
+ .add(computeBudgetIx())
+ .add(...claimAllTx);
})
);
}
@@ -2466,7 +2457,7 @@ export class DLMM {
* @param
* - `owner`: The public key of the owner of the positions.
* - `positions`: An array of objects of type `PositionData` that represents the positions to claim swap fees from.
- * @returns a Promise that resolves to an array of Transaction objects.
+ * @returns {Promise}
*/
public async claimAllSwapFee({
owner,
@@ -2492,12 +2483,58 @@ export class DLMM {
return Promise.all(
chunkedClaimAllTx.map(async (claimAllTx) => {
+ const { recentBlockhash, lastValidBlockHeight } = claimAllTx[0];
+ return new Transaction({
+ feePayer: owner,
+ blockhash: recentBlockhash,
+ lastValidBlockHeight,
+ })
+ .add(computeBudgetIx())
+ .add(...claimAllTx);
+ })
+ );
+ }
+
+ /**
+ * The function `claimAllRewardsByPosition` allows a user to claim all rewards for a specific
+ * position.
+ * @param
+ * - `owner`: The public key of the owner of the position.
+ * - `position`: The public key of the position account.
+ * @returns {Promise}
+ */
+ public async claimAllRewardsByPosition({
+ owner,
+ position,
+ }: {
+ owner: PublicKey;
+ position: Position;
+ }): Promise {
+ const claimAllSwapFeeTxs = await this.createClaimSwapFeeMethod({
+ owner,
+ position,
+ });
+ const claimAllLMTxs = await this.createClaimBuildMethod({
+ owner,
+ position,
+ });
+
+ const claimAllTxs = chunks(
+ [claimAllSwapFeeTxs, ...claimAllLMTxs],
+ MAX_CLAIM_ALL_ALLOWED
+ );
+
+ const { blockhash, lastValidBlockHeight } =
+ await this.program.provider.connection.getLatestBlockhash("confirmed");
+ return Promise.all(
+ claimAllTxs.map(async (claimAllTx) => {
return new Transaction({
feePayer: owner,
- ...(await this.program.provider.connection.getLatestBlockhash(
- "finalized"
- )),
- }).add(...claimAllTx);
+ blockhash,
+ lastValidBlockHeight,
+ })
+ .add(computeBudgetIx())
+ .add(...claimAllTx);
})
);
}
@@ -2546,14 +2583,17 @@ export class DLMM {
MAX_CLAIM_ALL_ALLOWED
);
+ const { blockhash, lastValidBlockHeight } =
+ await this.program.provider.connection.getLatestBlockhash("confirmed");
return Promise.all(
chunkedClaimAllTx.map(async (claimAllTx) => {
return new Transaction({
feePayer: owner,
- ...(await this.program.provider.connection.getLatestBlockhash(
- "finalized"
- )),
- }).add(...claimAllTx);
+ blockhash,
+ lastValidBlockHeight,
+ })
+ .add(computeBudgetIx())
+ .add(...claimAllTx);
})
);
}
@@ -3163,7 +3203,7 @@ export class DLMM {
const rewardInfo = this.lbPair.rewardInfos[i];
if (!rewardInfo || rewardInfo.mint.equals(PublicKey.default)) continue;
- const preInstructions: Array = [];
+ const preInstructions = [];
const { ataPubKey, ix } = await getOrCreateATAInstruction(
this.program.provider.connection,
rewardInfo.mint,
@@ -3230,10 +3270,6 @@ export class DLMM {
);
const preInstructions: TransactionInstruction[] = [];
- const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
- units: 1_400_000,
- });
- preInstructions.push(computeBudgetIx);
const [
{ ataPubKey: userTokenX, ix: createInTokenAccountIx },
{ ataPubKey: userTokenY, ix: createOutTokenAccountIx },
diff --git a/ts-client/src/example.ts b/ts-client/src/example.ts
index e69de29b..54ecdd32 100644
--- a/ts-client/src/example.ts
+++ b/ts-client/src/example.ts
@@ -0,0 +1,170 @@
+import {
+ Connection,
+ Keypair,
+ PublicKey,
+ sendAndConfirmTransaction,
+} from "@solana/web3.js";
+import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
+import { DLMM } from "./dlmm";
+import { calculateSpotDistribution } from "./dlmm/helpers";
+import BN from "bn.js";
+
+const user = Keypair.fromSecretKey(
+ new Uint8Array(bs58.decode(process.env.USER_PRIVATE_KEY))
+);
+const RPC = process.env.RPC || "https://api.devnet.solana.com";
+const connection = new Connection(RPC, "finalized");
+
+const devnetPool = new PublicKey(
+ "3W2HKgUa96Z69zzG3LK1g8KdcRAWzAttiLiHfYnKuPw5"
+);
+
+async function main() {
+ const dlmmPool = await DLMM.create(connection, devnetPool, {
+ cluster: "devnet",
+ });
+
+ // Get pool state
+ const activeBin = await dlmmPool.getActiveBin();
+ console.log("🚀 ~ activeBin:", activeBin);
+
+ const TOTAL_RANGE_INTERVAL = 10; // 10 bins on each side of the active bin
+ const bins = [activeBin.binId]; // Make sure bins is less than 70, as currently only support up to 70 bins for 1 position
+ for (
+ let i = activeBin.binId;
+ i < activeBin.binId + TOTAL_RANGE_INTERVAL / 2;
+ i++
+ ) {
+ const rightNextBinId = i + 1;
+ const leftPrevBinId = activeBin.binId - (rightNextBinId - activeBin.binId);
+ bins.push(rightNextBinId);
+ bins.unshift(leftPrevBinId);
+ }
+
+ const activeBinPricePerToken = dlmmPool.fromPricePerLamport(
+ Number(activeBin.price)
+ );
+ const totalXAmount = new BN(100);
+ const totalYAmount = totalXAmount.mul(new BN(Number(activeBinPricePerToken)));
+
+ // Get spot distribution
+ const spotXYAmountDistribution = calculateSpotDistribution(
+ activeBin.binId,
+ bins
+ );
+
+ // Create Position
+ const newPosition = new Keypair();
+ const createPositionTx =
+ await dlmmPool.initializePositionAndAddLiquidityByWeight({
+ positionPubKey: newPosition.publicKey,
+ lbPairPubKey: dlmmPool.pubkey,
+ user: user.publicKey,
+ totalXAmount,
+ totalYAmount,
+ xYAmountDistribution: spotXYAmountDistribution,
+ });
+
+ try {
+ for (let tx of Array.isArray(createPositionTx)
+ ? createPositionTx
+ : [createPositionTx]) {
+ const createPositionTxHash = await sendAndConfirmTransaction(
+ connection,
+ tx,
+ [user, newPosition]
+ );
+ console.log("🚀 ~ createPositionTxHash:", createPositionTxHash);
+ }
+ } catch (error) {
+ console.log("🚀 ~ error:", JSON.parse(JSON.stringify(error)));
+ }
+
+ // Get position state
+ const { userPositions } = await dlmmPool.getPositionsByUserAndLbPair(
+ user.publicKey
+ );
+ console.log("🚀 ~ userPositions:", userPositions);
+
+ // Add Liquidity to existing position
+ const addLiquidityTx = await dlmmPool.addLiquidityByWeight({
+ positionPubKey: userPositions[0].publicKey,
+ lbPairPubKey: dlmmPool.pubkey,
+ user: user.publicKey,
+ totalXAmount,
+ totalYAmount,
+ xYAmountDistribution: spotXYAmountDistribution,
+ });
+
+ try {
+ for (let tx of Array.isArray(addLiquidityTx)
+ ? addLiquidityTx
+ : [addLiquidityTx]) {
+ const addLiquidityTxHash = await sendAndConfirmTransaction(
+ connection,
+ tx,
+ [user, newPosition]
+ );
+ console.log("🚀 ~ addLiquidityTxHash:", addLiquidityTxHash);
+ }
+ } catch (error) {
+ console.log("🚀 ~ error:", JSON.parse(JSON.stringify(error)));
+ }
+
+ // Remove Liquidity
+ const binIdsToRemove = userPositions[0].positionData.positionBinData.map(
+ (bin) => bin.binId
+ );
+ const removeLiquidityTx = await dlmmPool.removeLiquidity({
+ position: userPositions[0].publicKey,
+ user: user.publicKey,
+ binIds: binIdsToRemove,
+ liquiditiesBpsToRemove: new Array(binIdsToRemove.length).fill(
+ new BN(100 * 100)
+ ), // 100% (range from 0 to 100)
+ shouldClaimAndClose: true, // should claim swap fee and close position together
+ });
+
+ try {
+ for (let tx of Array.isArray(removeLiquidityTx)
+ ? removeLiquidityTx
+ : [removeLiquidityTx]) {
+ const removeLiquidityTxHash = await sendAndConfirmTransaction(
+ connection,
+ tx,
+ [user, newPosition],
+ { skipPreflight: false, preflightCommitment: "singleGossip" }
+ );
+ console.log("🚀 ~ removeLiquidityTxHash:", removeLiquidityTxHash);
+ }
+ } catch (error) {
+ console.log("🚀 ~ error:", JSON.parse(JSON.stringify(error)));
+ }
+
+ const swapAmount = new BN(100);
+ // Swap quote
+ const swapQuote = await dlmmPool.swapQuote(swapAmount, true, new BN(10));
+ console.log("🚀 ~ swapQuote:", swapQuote);
+
+ // Swap
+ const swapTx = await dlmmPool.swap({
+ inToken: dlmmPool.tokenX.publicKey,
+ binArraysPubkey: swapQuote.binArraysPubkey,
+ inAmount: swapAmount,
+ lbPair: dlmmPool.pubkey,
+ user: user.publicKey,
+ minOutAmount: swapQuote.minOutAmount,
+ outToken: dlmmPool.tokenY.publicKey,
+ });
+
+ try {
+ const swapTxHash = await sendAndConfirmTransaction(connection, swapTx, [
+ user,
+ ]);
+ console.log("🚀 ~ swapTxHash:", swapTxHash);
+ } catch (error) {
+ console.log("🚀 ~ error:", JSON.parse(JSON.stringify(error)));
+ }
+}
+
+main();