Skip to content

Commit

Permalink
Added an ability to find if the raydium liquidity pool has been burne…
Browse files Browse the repository at this point in the history
…d and locked
  • Loading branch information
Chintan Patel authored and Chintan Patel committed Aug 18, 2024
1 parent ab636e2 commit c21c093
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 2 deletions.
22 changes: 20 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<dependency>
<groupId>com.mmorrell</groupId>
<artifactId>solanaj</artifactId>
<version>1.17.2</version>
<version>1.17.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
Expand All @@ -78,6 +78,24 @@
<version>2.22.1</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.0</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>

</dependencies>

<distributionManagement>
Expand Down Expand Up @@ -145,7 +163,7 @@
</goals>
<configuration>
<homedir>/Users/chintan_mbp/.gnupg/</homedir>
<keyname>0x27FAE7D2</keyname>
<keyname></keyname>
</configuration>
</execution>
</executions>
Expand Down
17 changes: 17 additions & 0 deletions raydium/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>solanaj-programs</artifactId>
<groupId>com.mmorrell</groupId>
<version>1.30.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>raydium</artifactId>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.mmorrell.raydium.manager;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mmorrell.raydium.model.LiquidityState;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.p2p.solanaj.core.PublicKey;
import org.p2p.solanaj.rpc.RpcClient;
import org.p2p.solanaj.rpc.RpcException;
import org.p2p.solanaj.rpc.types.AccountInfo;
import org.p2p.solanaj.rpc.types.TokenResultObjects;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URISyntaxException;

public class RaydiumLPManager {
private static final Logger LOGGER = LogManager.getLogger(RaydiumLPManager.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
public static final CloseableHttpClient httpClient = HttpClients.createDefault();

private final RpcClient client;

public RaydiumLPManager(final RpcClient client) {
this.client = client;
}

public boolean isLpLockedGivenTokenAddress(String tokenAddress) {
String lpMarketAddress = getRaydiumLpMarketPublicKey(tokenAddress);
return isLpLockedGivenLpMarketAddress(lpMarketAddress);
}

public boolean isLpLockedGivenLpMarketAddress(String lpMarketAddress) {
BigDecimal burnPercent = checkRaydiumLpBurnPercentageGivenLpMarketAddress(lpMarketAddress);
return burnPercent.compareTo(BigDecimal.valueOf(95)) > 0;
}

public BigDecimal checkRaydiumLpBurnPercentageGivenTokenAddress(String tokenAddress) {
String lpMarketAddress = getRaydiumLpMarketPublicKey(tokenAddress);
return checkRaydiumLpBurnPercentageGivenLpMarketAddress(lpMarketAddress);
}

public BigDecimal checkRaydiumLpBurnPercentageGivenLpMarketAddress(String lpMarketAddress) {
PublicKey lpMarketPublicKey = new PublicKey(lpMarketAddress);
AccountInfo lpMarketAddressInfo;
try {
lpMarketAddressInfo = client.getApi().getAccountInfo(lpMarketPublicKey);
} catch (RpcException e) {
LOGGER.error("RPC request issue getting lp market address account info: {}", lpMarketPublicKey);
throw new RuntimeException(e);
}

assert lpMarketAddressInfo != null;
LiquidityState liquidityState = LiquidityState.decode(lpMarketAddressInfo.getDecodedData());

PublicKey lpMint = liquidityState.lpMint();
BigInteger lpReserveBigInteger = liquidityState.u64LpReserve();
BigDecimal lpReserve = new BigDecimal(lpReserveBigInteger);

TokenResultObjects.TokenInfo info;
try {
info = client.getApi().getSplTokenAccountInfo(lpMint)
.getValue()
.getData()
.getParsed()
.getInfo();
} catch (RpcException e) {
LOGGER.error("Failed: Issue getting account info for lp mint: {}", lpMint);
throw new RuntimeException(e);
}

Integer decimals = info.getDecimals();
lpReserve = lpReserve.divide(BigDecimal.valueOf(10).pow(decimals), 2, RoundingMode.HALF_UP).subtract(BigDecimal.ONE);
BigDecimal actualSupply = new BigDecimal(info.getSupply()).divide(BigDecimal.valueOf(10).pow(decimals), 2, RoundingMode.HALF_UP);
BigDecimal maxLpSupply = actualSupply.max(lpReserve);

BigDecimal burnAmt = maxLpSupply.subtract(actualSupply);
try {
return burnAmt.divide(maxLpSupply, 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
} catch (Exception e) {
if (e.getMessage().equals("/ by zero")) {
return BigDecimal.ZERO;
}
throw new RuntimeException(String.format("Failed to divide burnAmt %s by maxLpSupply %s: ", burnAmt, maxLpSupply), e);
}
}

public String getRaydiumLpMarketPublicKey(String tokenAddress) {
HttpGet tokenLpMarketGetHttpRequest = createGetHttpRequest(tokenAddress);
JsonNode tokenInfoJsonNode;
try {
tokenInfoJsonNode = httpClient.execute(tokenLpMarketGetHttpRequest, getHttpRequestHandler());
} catch (IOException e) {
LOGGER.error("Failed: Issue getting from geckoTerminal for token: {}", tokenAddress);
throw new RuntimeException(e);
}
return tokenInfoJsonNode.get("data")
.get("relationships")
.get("top_pools")
.withArrayProperty("data")
.get(0)
.get("id")
.asText()
.replace("solana_", "");
}

public HttpGet createGetHttpRequest(String tokenAddress) {
String endpoint = "https://api.geckoterminal.com/api/v2/networks/solana/tokens/" + tokenAddress;
URI tokenInfoURI;
try {
tokenInfoURI = new URIBuilder(endpoint)
.build();
LOGGER.info("Creating uri for GeckoTerminal endpoint: {}", endpoint);
} catch (URISyntaxException e) {
LOGGER.error("Failed: Issue creating URI from geckoTerminal for token: {}", tokenAddress);
throw new RuntimeException("Failed to send get request to geckoTerminal, ", e);
}

HttpGet tokenInfoGetRequest = new HttpGet(tokenInfoURI);
tokenInfoGetRequest.setHeader("accept", "application/json");

return tokenInfoGetRequest;
}

private static HttpClientResponseHandler<JsonNode> getHttpRequestHandler() {
return response -> {
int status = response.getCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
return entity != null ? objectMapper.readTree(EntityUtils.toString(entity)) : null;
} else {
LOGGER.error("Failed: Issue getting http request handler");
throw new RuntimeException("Failed: Unexpected response status: " + status);
}
};
}
}
126 changes: 126 additions & 0 deletions raydium/src/main/java/com/mmorrell/raydium/model/LiquidityState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.mmorrell.raydium.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.p2p.solanaj.core.PublicKey;
import org.p2p.solanaj.utils.ByteUtils;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public record LiquidityState(BigInteger u64Status, BigInteger u64Nonce, BigInteger u64MaxOrder, BigInteger u64Depth,
BigInteger u64BaseDecimal, BigInteger u64QuoteDecimal, BigInteger u64State,
BigInteger u64ResetFlag, BigInteger u64MinSize, BigInteger u64VolMaxCutRatio,
BigInteger u64AmountWaveRatio, BigInteger u64BaseLotSize, BigInteger u64QuoteLotSize,
BigInteger u64MinPriceMultiplier, BigInteger u64MaxPriceMultiplier,
BigInteger u64SystemDecimalValue, BigInteger u64MinSeparateNumerator,
BigInteger u64MinSeparateDenominator, BigInteger u64TradeFeeNumerator,
BigInteger u64TradeFeeDenominator, BigInteger u64PnlNumerator,
BigInteger u64PnlDenominator, BigInteger u64SwapFeeNumerator,
BigInteger u64SwapFeeDenominator, BigInteger u64BaseNeedTakePnl,
BigInteger u64QuoteNeedTakePnl, BigInteger u64QuoteTotalPnl, BigInteger u64BaseTotalPnl,
BigInteger u64PoolOpenTime, BigInteger u64PunishPcAmount, BigInteger u64PunishCoinAmount,
BigInteger u64OrderbookToInitTime, BigInteger u128SwapBaseInAmount,
BigInteger u128SwapQuoteOutAmount, BigInteger u64SwapBase2QuoteFee,
BigInteger u128SwapQuoteInAmount, BigInteger u128SwapBaseOutAmount,
BigInteger u64SwapQuote2BaseFee, PublicKey baseVault, PublicKey quoteVault,
PublicKey baseMint, PublicKey quoteMint, PublicKey lpMint, PublicKey openOrders,
PublicKey marketId, PublicKey marketProgramId, PublicKey targetOrders,
PublicKey withdrawQueue, PublicKey lpVault, PublicKey owner, BigInteger u64LpReserve,
List<BigInteger> u64Padding) {
public static LiquidityState decode(byte[] data) {
MutableInt offset = new MutableInt(0);
return new LiquidityState(
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint64(data, offset),
readUint128(data, offset),
readUint128(data, offset),
readUint64(data, offset),
readUint128(data, offset),
readUint128(data, offset),
readUint64(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readPubkey(data, offset),
readUint64(data, offset),
readU64PaddingList(data, offset)
);
}

private static BigInteger readUint64(byte[] data, MutableInt offset) {
BigInteger value = ByteUtils.readUint64(data, offset.getValue());
offset.setValue(offset.getValue() + ByteUtils.UINT_64_LENGTH);
return value;
}

private static BigInteger readUint128(byte[] data, MutableInt offset) {
BigInteger value = ByteUtils.readUint128(data, offset.getValue());
offset.setValue(offset.getValue() + ByteUtils.UINT_128_LENGTH);
return value;
}

private static PublicKey readPubkey(byte[] data, MutableInt offset) {
PublicKey publicKey = PublicKey.readPubkey(data, offset.getValue());
offset.setValue(offset.getValue() + PublicKey.PUBLIC_KEY_LENGTH);
return publicKey;
}

private static List<BigInteger> readU64PaddingList(byte[] data, MutableInt offset) {
int u64PaddingStartingOffset = offset.getValue();
List<BigInteger> u64PaddingList = new ArrayList<>();
while (u64PaddingStartingOffset < data.length) {
u64PaddingList.add(ByteUtils.readUint64(data, u64PaddingStartingOffset));
u64PaddingStartingOffset += 8;
}
return u64PaddingList;
}

// Wrapper class to hold an integer value
@Setter
@Getter
@AllArgsConstructor
private static class MutableInt {
private int value;
}
}

0 comments on commit c21c093

Please sign in to comment.