Get All Withdraws for a User

Fetch all withdraws for a liquidity provider on Meteora DLMM

Positions in liquidity pairs represent the ownership stake of liquidity. Similar to deposits, liquidity providers (users) can also withdraw liquidity from a liquidity pool. With SHYFT's graphQL APIs, we can find out how much liquidity one particular liquidity provider has withdrawn. The process however involves two simple steps:

  1. Suppose we have a liquidity provider (user) address, we fetch all the positions on meteora that the provider holds, and their lbPair details. This step should give us the position address(s) for the provider, and the tokens involved for each of the related pool. (A liquidity provider may have positions for multiple pools)

  2. Once we have the above details, we get all the transactions for the position address received. The liquidity withdrawn details are stored in the remove liquidity transactions for positionsV1, and as token transfers for PositionsV2 (with sender being the pool address).

You can directly copy paste this code on replit and see it in action.

import { ShyftSdk,Network } from "@shyft-to/js";

const SHYFT_API_KEY = "YOUR_SHYFT_API_KEY";

const shyft = new ShyftSdk({ apiKey: SHYFT_API_KEY, network: Network.Mainnet });

async function getLbPairDetails(lbPairAddress) {
    const LbPairDetails = await fetch(
        `https://programs.shyft.to/v0/graphql/accounts?api_key=${SHYFT_API_KEY}&network=mainnet-beta`, //SHYFT's GQL endpoint
        {
          method: "POST",
          body: JSON.stringify({
            query: `
                query MyQuery {
                    meteora_dlmm_LbPair(
                        where: {pubkey: {_eq: ${JSON.stringify(lbPairAddress)}}}
                ) {
                    pubkey
                    tokenXMint
                    tokenYMint
                    }
                }
            `, //querying the LB pair details
            variables: {},
            operationName: "MyQuery",
          }),
        }
      );
      const LBPairResponse = await LbPairDetails.json();

      return{
        pubkey: LBPairResponse.data.meteora_dlmm_LbPair[0].pubkey,
        tokenY: LBPairResponse.data.meteora_dlmm_LbPair[0].tokenYMint,
        tokenX: LBPairResponse.data.meteora_dlmm_LbPair[0].tokenXMint,
      };

}

async function getUserWithdrawsByPositionV1(positionAddress,tokenX,tokenY) {
  let genesisTxnReached = false;
  let removeLiquidityTxns = [];
  let lastSignature = undefined;


  while (!genesisTxnReached) {
      const transactions = await shyft.transaction.history({
          account: positionAddress,
          network: Network.Mainnet,
          txNum: 10,
          beforeTxSignature: lastSignature
      });
      transactions.map((txn) => {
          if(txn.type === "REMOVE_LIQUIDITY")
              removeLiquidityTxns.push(txn);
      });

      if(transactions.length < 10){
          genesisTxnReached = true;
          break;
      }
      lastSignature = transactions[transactions.length - 1].signatures[0];
  }
  const removedLiquidityDetails = [];

  if(removeLiquidityTxns.length > 0){

      removeLiquidityTxns.forEach((removeLiquidityTxn) => {
          let eachRemovedTxn = {
              "txn_id":removeLiquidityTxn.signatures[0],
              "onchain_timestamp": removeLiquidityTxn.timestamp,
          };
          removeLiquidityTxn.actions.map((action) => {
              if(action.type === "REMOVE_LIQUIDITY"){
                  action.info.liquidity_removed.map((liquidityDetails) => {
                      if(liquidityDetails.token_address === tokenX){
                          eachRemovedTxn = {
                              ...eachRemovedTxn,
                              "tokenX_amount":liquidityDetails.amount_raw,
                              // "tokenX_symbol":liquidityDetails.symbol,
                              "tokenX_address":liquidityDetails.token_address
                          }
                      }
                      if(liquidityDetails.token_address === tokenY){
                          eachRemovedTxn = {
                              ...eachRemovedTxn,
                              "tokenY_amount":liquidityDetails.amount_raw,
                              // "tokenY_symbol":liquidityDetails.symbol,
                              "tokenY_address":liquidityDetails.token_address
                          }
                      }
                  })
              }
          })
          removedLiquidityDetails.push(eachRemovedTxn);
      });
  }
  console.log(removedLiquidityDetails);
}

async function getUserWithdrawsByPositionV2(ownerAddress, positionAddress, lbPair, tokenX, tokenY) {
  let genesisTxnReached = false;
  let tokenTransferTxns = [];
  let lastSignature = undefined;

  while (!genesisTxnReached) {
      const transactions = await shyft.transaction.history({
          account: positionAddress,
          network: Network.Mainnet,
          txNum: 10,
          beforeTxSignature: lastSignature
      });
      console.log("Current Txn length:", transactions.length);
      transactions.map((txn) => {
          if(txn.type === "TOKEN_TRANSFER")
          {
              txn.actions.map((action) => {
                  if(action.type === "TOKEN_TRANSFER" && action.info.sender === lbPair && action.info.receiver === ownerAddress){
                      tokenTransferTxns.push(txn)
                  }
              })
          }
      });

      if(transactions.length < 10){
          genesisTxnReached = true;
          break;
      }
      lastSignature = transactions[transactions.length - 1].signatures[0];
  }
  const removedLiquidityDetails = [];

  if(tokenTransferTxns.length > 0){

      tokenTransferTxns.forEach((tokenTransferTxn) => {

          let eachRemovedTxn = {
              "txn_id":tokenTransferTxn.signatures[0],
              "onchain_timestamp": tokenTransferTxn.timestamp,
          };
          tokenTransferTxn.actions.map((action) => {
              if(action.type === "TOKEN_TRANSFER"){
                  if(action.info.token_address === tokenX){
                      eachRemovedTxn = {
                          ...eachRemovedTxn,
                          "tokenX_amount":action.info.amount_raw,
                          "tokenX_address":action.info.token_address
                      }
                  }
                  if(action.info.token_address === tokenY){
                      eachRemovedTxn = {
                          ...eachRemovedTxn,
                          "tokenY_amount":action.info.amount_raw,
                          "tokenY_address":action.info.token_address
                      }
                  }

              }
          })
          if(removedLiquidityDetails.length === 0 || removedLiquidityDetails.map((txn) => txn.txn_id).includes(eachRemovedTxn.txn_id) === false)
              removedLiquidityDetails.push(eachRemovedTxn);
      });
  }
  console.log(removedLiquidityDetails);
}

async function getPositionLiquidityWithdraws(ownerAddress) {
  //getting all positions for the user and the lbPair address
  const operationsDoc = `
      query MyQuery {
        meteora_dlmm_PositionV2(
          where: {owner: {_eq: ${JSON.stringify(ownerAddress)}}}
        ) {
          upperBinId
          lowerBinId
          totalClaimedFeeYAmount
          totalClaimedFeeXAmount
          lastUpdatedAt
          lbPair
          owner
          pubkey
        }
        meteora_dlmm_Position(
          where: {owner: {_eq: ${JSON.stringify(ownerAddress)}}}
        ) {
          lastUpdatedAt
          lbPair
          lowerBinId
          upperBinId
          totalClaimedFeeYAmount
          totalClaimedFeeXAmount
          owner
          pubkey
        }
      }
  `; //you can cherrypick the fields as per your requirement
    const result = await fetch(
      `https://programs.shyft.to/v0/graphql/accounts?api_key=${SHYFT_API_KEY}&network=mainnet-beta`, //SHYFT's GQL endpoint
      {
        method: "POST",
        body: JSON.stringify({
          query: operationsDoc,
          variables: {},
          operationName: "MyQuery",
        }),
      }
    );

    const { errors, data } = await result.json();

    //adding a delay of 2 seconds to avoid rate limiting, only for free API Keys.
    await new Promise((resolve) => setTimeout(resolve, 2000));

    if (data.meteora_dlmm_Position.length > 0) {
      for (let index = 0; index < data.meteora_dlmm_Position.length; index++) {
        const position = data.meteora_dlmm_Position[index];

        //get all Lb pair details for the position
        const LbPairDetails = await getLbPairDetails(position.lbPair);
        // getting deposit values from transactions
        await getUserWithdrawsByPositionV1(position.pubkey, LbPairDetails.tokenX, LbPairDetails.tokenY)


      }
    }

    //adding a delay of 2 seconds to avoid rate limiting, only for free API Keys.
    await new Promise((resolve) => setTimeout(resolve, 2000));

    if (data.meteora_dlmm_PositionV2.length > 0) {
      for (let index = 0; index < data.meteora_dlmm_PositionV2.length; index++) {
        const position = data.meteora_dlmm_PositionV2[index];

        //get all Lb pair details for the positionV2
        const LbPairDetails = await getLbPairDetails(position.lbPair);
          // getting deposit values from transactions
        await getUserWithdrawsByPositionV2(position.owner, position.pubkey, position.lbPair, LbPairDetails.tokenX, LbPairDetails.tokenY)
      }
    }
  }
  getPositionLiquidityWithdraws("Hox8Ueuu8jb4pRuQfKTGxGfyiYetzAo7BMMzkDTJs917")

Last updated