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