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:
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)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")
[
{
"txn_id": "DzV1iioFdF65GTCog78AsJB4TWiQ9oSMMNoZm1KrES2iva24n9f5vY9vWd3VYAozX4dKMu2jHTUJRLZfvy3MAKw",
"onchain_timestamp": "2023-12-24T02:46:00.000Z",
"tokenX_amount": 248114633,
"tokenX_address": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4",
"tokenY_amount": 248114633,
"tokenY_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
}
]//sample response
Last updated