-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added an ability to find if the raydium liquidity pool has been burne…
…d and locked
- Loading branch information
Chintan Patel
authored and
Chintan Patel
committed
Aug 18, 2024
1 parent
ab636e2
commit c21c093
Showing
4 changed files
with
311 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
148 changes: 148 additions & 0 deletions
148
raydium/src/main/java/com/mmorrell/raydium/manager/RaydiumLPManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
126
raydium/src/main/java/com/mmorrell/raydium/model/LiquidityState.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |