X: Moved all API files into Implementation package

Until Nenjim works better navigation in the code is just too annoying.
This commit is contained in:
2026-07-02 19:42:29 +02:00
parent 5adebf0f49
commit bb6ab57236
12 changed files with 193 additions and 136 deletions
@@ -10,20 +10,36 @@ import java.math.BigDecimal;
* position.</p>
*
* @param positionAccount the Solana account address of the Jupiter Perps position
* @param entryPrice the entry price of the position, denominated in USDC
* @param direction whether the position is long or short
* @param tradedTokenMint the mint address of the token being traded
* @param sizeUsd the size of this position in USD
* @param collateralUsd the amount of USD representing the collateral for this position
* @oaram borrowFeeUsd TODO
* @param direction whether the position is long or short
* @param value the amount the position is worth if closed now
* @param size the leveraged amount used to open the contracts
* @param pnl the amount in usd in profit or loss on this position
* @param pnlPercent the profit and loss represented as a percentage
* @param leverage
* @param entryPrice the entry price of the position, denominated in USDC
* @param marketPrice the current spot price of the token
* @param collateral the amount of USD representing the collateral for this position
* @param totalFees the total amount of fees (TODO: is that including pending/due fees)
* @param borrowFeesDue the amount of USD that is currently outstanding
* @param closeFeePending the fee in USD for closing the account (TODO: multiple accounts - when adding collateral?)
* @param accountRent refundable amount locked for Solana account renting
*/
public record JupiterPerpsPosition(
ΩJupiterPerpsPositionAccountΩ positionAccount,
ΩUSDCPriceΩ entryPrice,
JupiterPerpsPositionDirection direction,
ΩSPLMintAddressΩ tradedTokenMint,
ΩUSDCAmountΩ sizeUsd,
ΩUSDCAmountΩ collateralUsd,
ΩUSDCAmountΩ borrowFeeUsd
JupiterPerpsPositionDirection direction,
ΩUSDCAmountΩ value,
ΩUSDCAmountΩ size,
ΩUSDCAmountΩ pnl,
BigDecimal pnlPercent,
BigDecimal leverage,
ΩUSDCPriceΩ entryPrice,
ΩUSDCPriceΩ marketPrice,
ΩUSDCAmountΩ collateral,
ΩUSDCAmountΩ totalFees,
ΩUSDCAmountΩ borrowFeesDue,
ΩUSDCAmountΩ closeFeePending,
ΩSolanaAmountΩ accountRent
) {
}
@@ -1,5 +1,8 @@
package com.r35157.libs.jupiter.perps;
import com.r35157.libs.solana.SolanaAccountInfo;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Set;
@@ -25,9 +28,12 @@ public interface JupiterPerpsService {
* @throws IOException if the position account could not be fetched or decoded
* @throws InterruptedException if the calling thread is interrupted while fetching
* the position account
*/
JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo)
throws IOException, InterruptedException
{
JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount)
throws IOException, InterruptedException;
*/
/**
* Finds open Jupiter Perps positions owned by a wallet.
@@ -7,76 +7,46 @@ import com.r35157.libs.solana.SolanaAccountInfo;
import java.math.BigInteger;
import java.util.Base64;
import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*;
public class AnchorIdlJupiterPerpsCustodyDecoder {
public ΩSPLMintAddressΩ decodeMint(
SolanaAccountInfo custodyAccountInfo
) {
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
public CustodyAccountInfo decode(SolanaAccountInfo custodyAccountInfo) {
byte[] data = decodeIdl(custodyAccountInfo);
if (data.length < MINT_OFFSET + PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps custody account data is too short: " + data.length
);
}
CustodyAccountInfo cai = new CustodyAccountInfo(
decodeMintAddress(data),
decodeCumulativeInterestRate(data)
);
return readPublicKey(data, MINT_OFFSET);
return cai;
}
public BigInteger decodeCurrentCumulativeInterestRate(
SolanaAccountInfo custodyAccountInfo
) {
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
private byte[] decodeIdl(SolanaAccountInfo custodyAccountInfo) {
byte[] data = base64.decode(custodyAccountInfo.dataBase64());
if (data.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps custody account data is too short: " + data.length
);
String errMsg = "Jupiter Perps custody account data is too short: " + data.length;
throw new IllegalArgumentException(errMsg);
}
return readU128(
data,
CUMULATIVE_INTEREST_RATE_OFFSET
);
return data;
}
private ΩSPLMintAddressΩ readPublicKey(
byte[] data,
int offset
) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
private ΩSPLMintAddressΩ decodeMintAddress(byte[] data) {
byte[] mintAddressBytes = readPublicKey(data, MINT_OFFSET);
ΩSPLMintAddressΩ mintAddress = base58.encode(mintAddressBytes);
System.arraycopy(
data,
offset,
publicKeyBytes,
0,
PUBLIC_KEY_LENGTH
);
return base58.encode(publicKeyBytes);
return mintAddress;
}
private static BigInteger readU128(
byte[] data,
int offset
) {
byte[] bytes = new byte[U128_LENGTH];
private BigInteger decodeCumulativeInterestRate(byte[] data) {
byte[] bytes = readU128(data, CUMULATIVE_INTEREST_RATE_OFFSET);
BigInteger cumulativeInterestRate = new BigInteger(1, bytes);
for (int i = 0; i < U128_LENGTH; i++) {
bytes[i] = data[offset + U128_LENGTH - 1 - i];
}
return new BigInteger(
1,
bytes
);
return cumulativeInterestRate;
}
private static final int U128_LENGTH = 16;
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
private static final int PUBLIC_KEY_LENGTH = 32;
// Offsets:
// 8 discriminator
// +32 pool
@@ -90,9 +60,11 @@ public class AnchorIdlJupiterPerpsCustodyDecoder {
// +8 target_ratio_bps
// +48 assets
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
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 Base58Codec base58 = new Base58CodecImpl();
private static final Base64.Decoder base64 = Base64.getDecoder();
}
@@ -15,11 +15,7 @@ import java.util.Base64;
public class AnchorIdlJupiterPerpsPositionDecoder {
JupiterPerpsPosition decode(
ΩJupiterPerpsPositionAccountΩ positionAccount,
SolanaAccountInfo accountInfo,
ΩSPLMintAddressΩ tradedTokenMint
) {
public JupiterPerpsPositionInfo decode(SolanaAccountInfo accountInfo) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
if (data.length < PRICE_OFFSET + U64_LENGTH) {
@@ -58,29 +54,17 @@ public class AnchorIdlJupiterPerpsPositionDecoder {
.valueOf(rawSizeUsd)
.movePointLeft(6);
AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
BigInteger currentCumulativeInterestRate = custodyDecoder.decodeCurrentCumulativeInterestRate(collateralCustodyAccountInfo);
BigInteger cumulativeInterestSnapshot = decodeCumulativeInterestSnapshot(positionAccountInfo);
BigInteger difference = currentCumulativeInterestRate.subtract(cumulativeInterestSnapshot);
ΩUSDCAmountΩ borrowFeeUsd =
new BigDecimal(difference)
.multiply(sizeUsd)
.divide(BigDecimal.valueOf(1_000_000_000L), 6, RoundingMode.CEILING);
JupiterPerpsPosition pos = new JupiterPerpsPosition(
positionAccount,
JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(
entryPrice,
direction,
tradedTokenMint,
sizeUsd,
collateralUsd,
borrowFeeUsd
collateralUsd
);
return pos;
return posInfo;
}
public BigInteger decodeCumulativeInterestSnapshot(SolanaAccountInfo accountInfo) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
@@ -5,8 +5,12 @@ import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Set;
@@ -20,26 +24,47 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
this.custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
}
/*
@Override
public JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount)
throws IOException, InterruptedException {
SolanaAccountInfo accountInfo = solanaBlockChain.getAccountInfo(positionAccount);
public JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo)
throws IOException, InterruptedException
{
guard(accountInfo);
if (accountInfo == null) {
throw new IllegalArgumentException("Jupiter Perps position account does not exist: " + positionAccount);
}
JupiterPerpsPositionInfo posInfo = positionDecoder.decode(accountInfo);
ΩUSDCAmountΩ sizeUsd = posInfo.sizeUsd();
if (!JUPITER_PERPS_PROGRAM_ID.equals(accountInfo.owner())) {
throw new IllegalArgumentException(
"Account is not owned by Jupiter Perps program: " + positionAccount
);
}
CustodyAccountInfo cai = custodyDecoder.decode(custodyAccountInfo);
BigInteger cumulativeInterestSnapshot = positionDecoder.decodeCumulativeInterestSnapshot(positionAccountInfo);
ΩUSDCAmountΩ borrowFeePendingUsd = calculateBorrowFeeUsd(
cumulativeInterestSnapshot,
sizeUsd
);
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo);
JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo, tradedTokenMint);
JupiterPerpsPosition pos = new JupiterPerpsPosition(
accountInfo,
posInfo.entryPrice(),
posInfo.direction(),
sizeUsd,
posInfo.collateralUsd(),
cai.mintAddress(),
borrowFeePendingUsd
);
return pos;
}
*/
private void guard(SolanaAccountInfo accountInfo) {
ΩSolanaProgramIdΩ owner = accountInfo.owner();
if (!JUPITER_PERPS_PROGRAM_ID.equals(owner)) {
String errorMsg = "Account '" + accountInfo.address() + "' is not owned by Jupiter Perps program ("
+ JUPITER_PERPS_PROGRAM_ID + "), instead it is owned by '"
+ owner
+ "'";
throw new IllegalArgumentException(errorMsg);
}
}
@Override
public Set<JupiterPerpsPosition> getOpenPositions(ΩSolanaWalletIdΩ owner)
@@ -73,7 +98,20 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
return Set.copyOf(positions);
}
private ΩSPLMintAddressΩ getTradedTokenMint(SolanaAccountInfo positionAccountInfo)
private BigDecimal calculateBorrowFeeUsd(
BigInteger cumulativeInterestSnapshot,
ΩUSDCAmountΩ sizeUsd
) {
BigInteger difference = currentCumulativeInterestRate.subtract(cumulativeInterestSnapshot);
BigDecimal borrowFeeUsd =
new BigDecimal(difference)
.multiply(sizeUsd)
.divide(BigDecimal.valueOf(1_000_000_000L), 6, RoundingMode.CEILING);
return borrowFeeUsd;
}
/*private ΩSPLMintAddressΩ getTradedTokenMint(SolanaAccountInfo positionAccountInfo)
throws IOException, InterruptedException
{
ΩSolanaAddressΩ custodyAccount = positionDecoder.decodeCustodyAccount(positionAccountInfo);
@@ -87,7 +125,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
ΩSPLMintAddressΩ mintAddress = custodyDecoder.decodeMint(custodyAccountInfo);
return mintAddress;
}
}*/
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
@@ -0,0 +1,4 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
public record CustodyAccountInfo() {
}
@@ -0,0 +1,9 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import java.math.BigInteger;
public record CustodyAccountInfo(
ΩSPLMintAddressΩ mintAddress,
BigInteger currentCumulativeInterestRate
) {
}
@@ -0,0 +1,4 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
public class DecodingPrimitives {
}
@@ -0,0 +1,31 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
public class DecodingPrimitives {
public static byte[] readU128(byte[] data, int offset) {
byte[] bytes = new byte[U128_LENGTH];
for (int i = 0; i < U128_LENGTH; i++) {
bytes[i] = data[offset + U128_LENGTH - 1 - i];
}
return bytes;
}
public static byte[] readPublicKey(byte[] data, int offset) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
System.arraycopy(
data,
offset,
publicKeyBytes,
0,
PUBLIC_KEY_LENGTH
);
return publicKeyBytes;
}
public static final int U128_LENGTH = 16;
public static final int PUBLIC_KEY_LENGTH = 32;
}
@@ -0,0 +1,4 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
public class JupiterPerpsPositionInfo {
}
@@ -0,0 +1,12 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
import java.math.BigDecimal;
public record JupiterPerpsPositionInfo(
ΩUSDCPriceΩ entryPrice,
JupiterPerpsPositionDirection direction,
ΩUSDCAmountΩ sizeUsd,
ΩUSDCAmountΩ collateralUsd
) {}
@@ -2,28 +2,12 @@ package com.r35157.nenjim.hubd.impl.ref;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsPositionDecoder;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import com.r35157.nenjim.hubd.ctx.Context;
import com.r35157.nenjim.hubd.NenjimHub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsCustodyDecoder;
import com.r35157.libs.solana.SolanaAccountInfo;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.r35157.nenjim.hubd.ctx.ContextManager;
import com.r35157.nenjim.hubd.journal.JournalManager;
import com.r35157.nenjim.hubd.impl.ref.JournalManagerImpl;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
public class Main {
@@ -38,28 +22,21 @@ public class Main {
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
AnchorIdlJupiterPerpsPositionDecoder positionDecoder = new AnchorIdlJupiterPerpsPositionDecoder();
AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
for (JupiterPerpsPosition position : positions) {
SolanaAccountInfo positionAccountInfo = sbc.getAccountInfo(position.positionAccount());
ΩSolanaAddressΩ collateralCustodyAccount = positionDecoder.decodeCollateralCustodyAccount(positionAccountInfo);
SolanaAccountInfo collateralCustodyAccountInfo = sbc.getAccountInfo(collateralCustodyAccount);
BigInteger currentCumulativeInterestRate =custodyDecoder.decodeCurrentCumulativeInterestRate(collateralCustodyAccountInfo);
BigInteger cumulativeInterestSnapshot = positionDecoder.decodeCumulativeInterestSnapshot(positionAccountInfo);
BigInteger difference = currentCumulativeInterestRate.subtract(cumulativeInterestSnapshot);
System.out.println("positionAccount: " + position.positionAccount());
System.out.println("currentCumulativeInterestRate: " + currentCumulativeInterestRate);
System.out.println("cumulativeInterestSnapshot: " + cumulativeInterestSnapshot);
System.out.println("difference: " + difference);
BigDecimal borrowFeeUsdCandidate =
new BigDecimal(difference)
.multiply(position.sizeUsd())
.divide(BigDecimal.valueOf(1_000_000_000L), 6, RoundingMode.CEILING);
System.out.println("borrowFeeUsdCandidate: " + borrowFeeUsdCandidate);
System.out.println(" PositionAccount: " + position.positionAccount());
System.out.println(" Token mint: " + position.tradedTokenMint());
System.out.println(" Direction: " + position.direction());
System.out.println(" Value: " + position.value());
System.out.println(" Size $: " + position.size());
System.out.println(" PnL: " + position.pnl() + " " + position.pnlPercent());
System.out.println(" Leverage: " + position.leverage() + "x");
System.out.println(" Entry price: " + position.entryPrice());
System.out.println(" Market price: " + position.marketPrice());
System.out.println(" Collateral: " + position.collateral());
System.out.println(" Totals fees: " + position.totalFees());
System.out.println(" Borrow Fees Due: " + position.borrowFeesDue());
System.out.println(" Close Fees Pending: " + position.closeFeePending());
System.out.println("Amount locked for account rent (SOL): " + position.accountRent());
System.out.println();
}