X: A lot of cleanup and refacorings

This commit is contained in:
2026-08-05 10:12:38 +02:00
parent 397028fe37
commit 3e82b49f0d
9 changed files with 147 additions and 127 deletions
@@ -1,50 +1,64 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.codec.Base58Codec;
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
import com.r35157.libs.solana.SolanaAccountInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigInteger;
import java.util.Base64;
import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*;
class AnchorIdlJupiterPerpsCustodyDecoder {
ΩSPLMintAddressΩ decodeMint(
SolanaAccountInfo custodyAccountInfo
@Nullable JupiterCustodyAccountInfo decode(
@NotNull ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo
) {
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
ΩJupiterPerpsPostionIdlΩ idl = decodeIdl(encodedCustodyAccountInfo);
if (data.length < MINT_OFFSET + PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps custody account data is too short: " + data.length
);
}
return readPublicKey(data, MINT_OFFSET);
}
private ΩSPLMintAddressΩ readPublicKey(
byte[] data,
int offset
) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
System.arraycopy(
data,
offset,
publicKeyBytes,
0,
PUBLIC_KEY_LENGTH
JupiterCustodyAccountInfo cai = new JupiterCustodyAccountInfo(
readPublicKey(idl, MINT_OFFSET),
decodeCumulativeInterestRate(idl)
);
return base58.encode(publicKeyBytes);
return cai;
}
private ΩJupiterPerpsCustodyAccountIdlΩ decodeIdl(ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo) {
ΩJupiterPerpsCustodyAccountIdlΩ idl = base64.decode(encodedCustodyAccountInfo.data());
if (idl.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) {
String errMsg = "Jupiter Perps custody account data is too short: " + idl.length;
throw new IllegalArgumentException(errMsg);
}
return idl;
}
private BigInteger decodeCumulativeInterestRate(byte[] data) {
byte[] bytes = readU128(data, CUMULATIVE_INTEREST_RATE_OFFSET);
BigInteger cumulativeInterestRate = new BigInteger(1, bytes);
return cumulativeInterestRate;
}
// Offsets:
// 8 discriminator
// +32 pool
// +32 mint
// +32 token_account
// +1 decimals
// +1 is_stable
// +45 oracle
// +48 pricing
// +7 permissions
// +8 target_ratio_bps
// +48 assets
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
private static final int PUBLIC_KEY_LENGTH = 32;
private static final int MINT_OFFSET = ANCHOR_DISCRIMINATOR_LENGTH + PUBLIC_KEY_LENGTH;
private static final int FUNDING_RATE_STATE_OFFSET = 262;
private static final int CUMULATIVE_INTEREST_RATE_OFFSET = FUNDING_RATE_STATE_OFFSET;
private static final int MINT_OFFSET =
ANCHOR_DISCRIMINATOR_LENGTH
+ PUBLIC_KEY_LENGTH; // pool
private static final Base58Codec base58 = new Base58CodecImpl();
private static final Base64.Decoder base64 = Base64.getDecoder();
}
@@ -1,7 +1,5 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.codec.Base58Codec;
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
import com.r35157.libs.solana.SolanaAccountInfo;
import org.jetbrains.annotations.NotNull;
@@ -12,24 +10,19 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Base64;
import static java.math.BigDecimal.ZERO;
import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*;
class AnchorIdlJupiterPerpsPositionDecoder {
@Nullable JupiterPerpsPositionInfo decode(@NotNull SolanaAccountInfo accountInfo) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
@Nullable JupiterPerpsPositionInfo decode(
@NotNull ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo
) {
ΩJupiterPerpsPostionIdlΩ idl = decodeIdl(encodedPerpsPositionInfo);
if (data.length < PRICE_OFFSET + U64_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps position account data is too short: " + data.length
);
}
JupiterPerpsPositionDirection direction =
decodeDirection(data[SIDE_OFFSET]);
JupiterPerpsPositionDirection direction = decodeDirection(idl);
long rawEntryPrice = ByteBuffer
.wrap(data, PRICE_OFFSET, U64_LENGTH)
.wrap(idl, PRICE_OFFSET, U64_LENGTH)
.order(ByteOrder.LITTLE_ENDIAN)
.getLong();
@@ -38,7 +31,7 @@ class AnchorIdlJupiterPerpsPositionDecoder {
.movePointLeft(6);
long rawCollateralUsd = ByteBuffer
.wrap(data, COLLATERAL_USD_OFFSET, U64_LENGTH)
.wrap(idl, COLLATERAL_USD_OFFSET, U64_LENGTH)
.order(ByteOrder.LITTLE_ENDIAN)
.getLong();
@@ -47,7 +40,7 @@ class AnchorIdlJupiterPerpsPositionDecoder {
.movePointLeft(6);
long rawSizeUsd = ByteBuffer
.wrap(data, SIZE_USD_OFFSET, U64_LENGTH)
.wrap(idl, SIZE_USD_OFFSET, U64_LENGTH)
.order(ByteOrder.LITTLE_ENDIAN)
.getLong();
@@ -55,29 +48,47 @@ class AnchorIdlJupiterPerpsPositionDecoder {
.valueOf(rawSizeUsd)
.movePointLeft(6);
JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(entryPrice, direction, sizeUsd, collateralUsd);
ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = readPublicKey(idl, CUSTODY_OFFSET);
JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(
entryPrice,
direction,
sizeUsd,
collateralUsd,
custodyAccountAddress
);
return posInfo;
}
ΩSolanaAddressΩ decodeCustodyAccount(SolanaAccountInfo accountInfo) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
ΩJupiterCustodyAccountAddressΩ decodeCustodyAccountAddress(SolanaAccountInfo accountInfo) {
byte[] data = base64.decode(accountInfo.data());
if (data.length < CUSTODY_OFFSET + PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps position account data is too short: " + data.length
);
String errorMsg = "Jupiter Perps position account data is too short: " + data.length;
throw new IllegalArgumentException(errorMsg);
}
return readPublicKey(
data,
CUSTODY_OFFSET
);
ΩJupiterCustodyAccountAddressΩ addr = readPublicKey(data, CUSTODY_OFFSET);
return addr;
}
private JupiterPerpsPositionDirection decodeDirection(
byte rawSide
) {
private ΩJupiterPerpsPostionIdlΩ decodeIdl(ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo) {
ΩJupiterPerpsPostionIdlΩ idl = base64.decode(encodedPerpsPositionInfo.data());
// TODO: Check size correctly, as in AnchorIdlJupiterPerpsCustodyDecoder.decodeIdl()
if (idl.length == 0) {
String errorMsg = "Jupiter Perps position account data is too short: " + idl.length;
throw new IllegalArgumentException(errorMsg);
}
return idl;
}
private JupiterPerpsPositionDirection decodeDirection(ΩJupiterPerpsPostionIdlΩ idl) {
byte rawSide = idl[SIDE_OFFSET];
// Jupiter Perps position side values are encoded as 1 = LONG, 2 = SHORT.
JupiterPerpsPositionDirection direction = switch (rawSide) {
case 1 -> JupiterPerpsPositionDirection.LONG;
@@ -90,23 +101,6 @@ class AnchorIdlJupiterPerpsPositionDecoder {
return direction;
}
private ΩSolanaAddressΩ readPublicKey(
byte[] data,
int offset
) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
System.arraycopy(
data,
offset,
publicKeyBytes,
0,
PUBLIC_KEY_LENGTH
);
return base58.encode(publicKeyBytes);
}
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
private static final int PUBLIC_KEY_LENGTH = 32;
private static final int I64_LENGTH = 8;
@@ -124,5 +118,5 @@ class AnchorIdlJupiterPerpsPositionDecoder {
private static final int SIZE_USD_OFFSET = PRICE_OFFSET + U64_LENGTH;
private static final int COLLATERAL_USD_OFFSET = SIZE_USD_OFFSET + U64_LENGTH;
private static final Base58Codec base58 = new Base58CodecImpl();
private static final Base64.Decoder base64 = Base64.getDecoder();
}
@@ -42,14 +42,23 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
);
}
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo);
JupiterPerpsPositionInfo info = positionDecoder.decode(accountInfo);
JupiterPerpsPositionInfo perpsPositionInfo = positionDecoder.decode(accountInfo);
JupiterPerpsPosition pos;
if(info == null) {
if(perpsPositionInfo == null) {
pos = null;
} else {
ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = positionDecoder.decodeCustodyAccountAddress(accountInfo);
ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccountAddress);
if (encodedCustodyAccountInfo == null) {
String errorMsg = "Jupiter Perps custody account does not exist: " + custodyAccountAddress;
throw new IllegalArgumentException(errorMsg);
}
JupiterCustodyAccountInfo custodyAccountInfo = custodyDecoder.decode(encodedCustodyAccountInfo);
ΩSPLMintAddressΩ mintAddress = custodyAccountInfo.mintAddress();
ΩUSDCAmountΩ value = ZERO; // TODO - Dummy
ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy
BigDecimal pnlPercent = ZERO; // TODO - Dummy
@@ -62,16 +71,16 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
pos = new JupiterPerpsPosition(
positionAccount,
tradedTokenMint,
info.direction(),
mintAddress,
perpsPositionInfo.direction(),
value,
info.sizeUsd(),
perpsPositionInfo.size(),
pnl,
pnlPercent,
leverage,
info.entryPrice(),
perpsPositionInfo.entryPrice(),
marketPrice,
info.collateralUsd(),
perpsPositionInfo.collateral(),
totalFees,
borrowFeesDue,
closeFeePending,
@@ -105,12 +114,21 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
throw new IllegalArgumentException(errorMsg);
}
JupiterPerpsPositionInfo info = positionDecoder.decode(accountInfo);
if(info == null || info.sizeUsd().compareTo(ZERO) == 0) {
JupiterPerpsPositionInfo perpsPositionInfo = positionDecoder.decode(accountInfo);
if(perpsPositionInfo == null || perpsPositionInfo.size().compareTo(ZERO) == 0) {
continue;
}
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo);
ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = perpsPositionInfo.custodyAccountAddress();
ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccountAddress);
if (encodedCustodyAccountInfo == null) {
String errorMsg = "Jupiter Perps custody account does not exist: " + custodyAccountAddress;
throw new IllegalArgumentException(errorMsg);
}
JupiterCustodyAccountInfo custodyAccountInfo = custodyDecoder.decode(encodedCustodyAccountInfo);
ΩSPLMintAddressΩ mintAddress = custodyAccountInfo.mintAddress();
ΩUSDCAmountΩ value = ZERO; // TODO - Dummy
ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy
@@ -124,16 +142,16 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
JupiterPerpsPosition pos = new JupiterPerpsPosition(
positionAccount,
tradedTokenMint,
info.direction(),
mintAddress,
perpsPositionInfo.direction(),
value,
info.sizeUsd(),
perpsPositionInfo.size(),
pnl,
pnlPercent,
leverage,
info.entryPrice(),
perpsPositionInfo.entryPrice(),
marketPrice,
info.collateralUsd(),
perpsPositionInfo.collateral(),
totalFees,
borrowFeesDue,
closeFeePending,
@@ -146,22 +164,6 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
return Set.copyOf(positions);
}
private ΩSPLMintAddressΩ getTradedTokenMint(SolanaAccountInfo positionAccountInfo)
throws IOException, InterruptedException
{
ΩSolanaAddressΩ custodyAccount = positionDecoder.decodeCustodyAccount(positionAccountInfo);
SolanaAccountInfo custodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccount);
if (custodyAccountInfo == null) {
throw new IllegalArgumentException(
"Jupiter Perps custody account does not exist: " + custodyAccount
);
}
ΩSPLMintAddressΩ mintAddress = custodyDecoder.decodeMint(custodyAccountInfo);
return mintAddress;
}
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
private static final int POSITION_OWNER_OFFSET = 8;
@@ -1,5 +1,8 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.codec.Base58Codec;
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
public class DecodingPrimitives {
public static byte[] readU128(byte[] data, int offset) {
@@ -12,20 +15,23 @@ public class DecodingPrimitives {
return bytes;
}
public static byte[] readPublicKey(byte[] data, int offset) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
public static ΩSolanaAddressΩ readPublicKey(byte[] data, int offset) {
byte[] bytes = new byte[PUBLIC_KEY_LENGTH];
System.arraycopy(
data,
offset,
publicKeyBytes,
bytes,
0,
PUBLIC_KEY_LENGTH
);
return publicKeyBytes;
ΩSolanaAddressΩ addr = base58.encode(bytes);
return addr;
}
public static final int U128_LENGTH = 16;
public static final int PUBLIC_KEY_LENGTH = 32;
private static final Base58Codec base58 = new Base58CodecImpl();
}
@@ -2,7 +2,7 @@ package com.r35157.libs.jupiter.perps.impl.anchoridl;
import java.math.BigInteger;
public record CustodyAccountInfo(
public record JupiterCustodyAccountInfo(
ΩSPLMintAddressΩ mintAddress,
BigInteger currentCumulativeInterestRate
) {
@@ -7,7 +7,8 @@ import java.math.BigDecimal;
public record JupiterPerpsPositionInfo(
ΩUSDCPriceΩ entryPrice,
JupiterPerpsPositionDirection direction,
ΩUSDCAmountΩ sizeUsd,
ΩUSDCAmountΩ collateralUsd
ΩUSDCAmountΩ size,
ΩUSDCAmountΩ collateral,
ΩJupiterCustodyAccountAddressΩ custodyAccountAddress
) {
}
@@ -747,7 +747,7 @@ public class RaydiumImpl implements Raydium {
private byte[] decodeBase64AccountData(SolanaAccountInfo accountInfo) throws IOException {
try {
return Base64.getDecoder().decode(accountInfo.dataBase64());
return base64.decode(accountInfo.data());
} catch (IllegalArgumentException e) {
throw new IOException("Could not decode Solana account data as Base64 for account: " + accountInfo.address(), e);
}
@@ -954,6 +954,8 @@ public class RaydiumImpl implements Raydium {
private static final int RAYDIUM_POOL_STATE_TICK_CURRENT_OFFSET =
RAYDIUM_POOL_STATE_SQRT_PRICE_X64_OFFSET + RAYDIUM_U128_LENGTH;
private static final Base64.Decoder base64 = Base64.getDecoder();
private final SolanaBlockChain solanaBlockChain;
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
@@ -1,5 +1,7 @@
package com.r35157.libs.solana;
import org.jetbrains.annotations.NotNull;
/**
* Represents basic information about a Solana account.
*
@@ -14,11 +16,11 @@ package com.r35157.libs.solana;
*
* @param address the Solana account address
* @param owner the Solana program id that owns the account
* @param dataBase64 the account data encoded as Base64, or an empty string if the account has no data
* @param data the account data encoded as Base64, or null if the account has no data
*/
public record SolanaAccountInfo(
ΩSolanaAddressΩ address,
ΩSolanaProgramIdΩ owner,
String dataBase64
@NotNull ΩSolanaAddressΩ address,
@NotNull ΩSolanaProgramIdΩ owner,
@NotNull ΩBase64StringΩ data
) {
}
@@ -25,13 +25,12 @@ public class Main {
static void main(String[] args) throws Exception {
NenjimHubImpl nenjimHub = new NenjimHubImpl();
/*
SolanaBlockChain sbc = new SolanaBlockChainImpl();
JupiterPerpsService jupiter = new AnchorIdlJupiterPerpsServiceImpl(sbc);
ΩSolanaWalletIdΩ walletId = "vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf";
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
int a=0;
*/
nenjimHub.awaitShutdown();
/* try {