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> * position.</p>
* *
* @param positionAccount the Solana account address of the Jupiter Perps position * @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 tradedTokenMint the mint address of the token being traded
* @param sizeUsd the size of this position in USD * @param direction whether the position is long or short
* @param collateralUsd the amount of USD representing the collateral for this position * @param value the amount the position is worth if closed now
* @oaram borrowFeeUsd TODO * @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( public record JupiterPerpsPosition(
ΩJupiterPerpsPositionAccountΩ positionAccount, ΩJupiterPerpsPositionAccountΩ positionAccount,
ΩUSDCPriceΩ entryPrice,
JupiterPerpsPositionDirection direction,
ΩSPLMintAddressΩ tradedTokenMint, ΩSPLMintAddressΩ tradedTokenMint,
ΩUSDCAmountΩ sizeUsd, JupiterPerpsPositionDirection direction,
ΩUSDCAmountΩ collateralUsd, ΩUSDCAmountΩ value,
ΩUSDCAmountΩ borrowFeeUsd Ω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; package com.r35157.libs.jupiter.perps;
import com.r35157.libs.solana.SolanaAccountInfo;
import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.Set; import java.util.Set;
@@ -25,9 +28,12 @@ public interface JupiterPerpsService {
* @throws IOException if the position account could not be fetched or decoded * @throws IOException if the position account could not be fetched or decoded
* @throws InterruptedException if the calling thread is interrupted while fetching * @throws InterruptedException if the calling thread is interrupted while fetching
* the position account * the position account
*/ JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo)
throws IOException, InterruptedException
{
JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount) JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount)
throws IOException, InterruptedException; throws IOException, InterruptedException;
*/
/** /**
* Finds open Jupiter Perps positions owned by a wallet. * 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.math.BigInteger;
import java.util.Base64; import java.util.Base64;
import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*;
public class AnchorIdlJupiterPerpsCustodyDecoder { public class AnchorIdlJupiterPerpsCustodyDecoder {
public ΩSPLMintAddressΩ decodeMint( public CustodyAccountInfo decode(SolanaAccountInfo custodyAccountInfo) {
SolanaAccountInfo custodyAccountInfo byte[] data = decodeIdl(custodyAccountInfo);
) {
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
if (data.length < MINT_OFFSET + PUBLIC_KEY_LENGTH) { CustodyAccountInfo cai = new CustodyAccountInfo(
throw new IllegalArgumentException( decodeMintAddress(data),
"Jupiter Perps custody account data is too short: " + data.length decodeCumulativeInterestRate(data)
); );
return cai;
} }
return readPublicKey(data, MINT_OFFSET); private byte[] decodeIdl(SolanaAccountInfo custodyAccountInfo) {
} byte[] data = base64.decode(custodyAccountInfo.dataBase64());
public BigInteger decodeCurrentCumulativeInterestRate(
SolanaAccountInfo custodyAccountInfo
) {
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
if (data.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) { if (data.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) {
throw new IllegalArgumentException( String errMsg = "Jupiter Perps custody account data is too short: " + data.length;
"Jupiter Perps custody account data is too short: " + data.length throw new IllegalArgumentException(errMsg);
);
} }
return readU128( return data;
data,
CUMULATIVE_INTEREST_RATE_OFFSET
);
} }
private ΩSPLMintAddressΩ readPublicKey( private ΩSPLMintAddressΩ decodeMintAddress(byte[] data) {
byte[] data, byte[] mintAddressBytes = readPublicKey(data, MINT_OFFSET);
int offset ΩSPLMintAddressΩ mintAddress = base58.encode(mintAddressBytes);
) {
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
System.arraycopy( return mintAddress;
data,
offset,
publicKeyBytes,
0,
PUBLIC_KEY_LENGTH
);
return base58.encode(publicKeyBytes);
} }
private static BigInteger readU128( private BigInteger decodeCumulativeInterestRate(byte[] data) {
byte[] data, byte[] bytes = readU128(data, CUMULATIVE_INTEREST_RATE_OFFSET);
int offset BigInteger cumulativeInterestRate = new BigInteger(1, bytes);
) {
byte[] bytes = new byte[U128_LENGTH];
for (int i = 0; i < U128_LENGTH; i++) { return cumulativeInterestRate;
bytes[i] = data[offset + U128_LENGTH - 1 - i];
} }
return new BigInteger(
1,
bytes
);
}
private static final int U128_LENGTH = 16;
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
private static final int PUBLIC_KEY_LENGTH = 32;
// Offsets: // Offsets:
// 8 discriminator // 8 discriminator
// +32 pool // +32 pool
@@ -90,9 +60,11 @@ public class AnchorIdlJupiterPerpsCustodyDecoder {
// +8 target_ratio_bps // +8 target_ratio_bps
// +48 assets // +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 MINT_OFFSET = ANCHOR_DISCRIMINATOR_LENGTH + PUBLIC_KEY_LENGTH;
private static final int FUNDING_RATE_STATE_OFFSET = 262; 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 CUMULATIVE_INTEREST_RATE_OFFSET = FUNDING_RATE_STATE_OFFSET;
private static final Base58Codec base58 = new Base58CodecImpl(); 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 { public class AnchorIdlJupiterPerpsPositionDecoder {
JupiterPerpsPosition decode( public JupiterPerpsPositionInfo decode(SolanaAccountInfo accountInfo) {
ΩJupiterPerpsPositionAccountΩ positionAccount,
SolanaAccountInfo accountInfo,
ΩSPLMintAddressΩ tradedTokenMint
) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
if (data.length < PRICE_OFFSET + U64_LENGTH) { if (data.length < PRICE_OFFSET + U64_LENGTH) {
@@ -58,29 +54,17 @@ public class AnchorIdlJupiterPerpsPositionDecoder {
.valueOf(rawSizeUsd) .valueOf(rawSizeUsd)
.movePointLeft(6); .movePointLeft(6);
AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder(); JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(
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,
entryPrice, entryPrice,
direction, direction,
tradedTokenMint,
sizeUsd, sizeUsd,
collateralUsd, collateralUsd
borrowFeeUsd
); );
return pos; return posInfo;
} }
public BigInteger decodeCumulativeInterestSnapshot(SolanaAccountInfo accountInfo) { public BigInteger decodeCumulativeInterestSnapshot(SolanaAccountInfo accountInfo) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); 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.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter; import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -20,26 +24,47 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
this.custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder(); this.custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
} }
/*
@Override @Override
public JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount) public JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo)
throws IOException, InterruptedException { throws IOException, InterruptedException
SolanaAccountInfo accountInfo = solanaBlockChain.getAccountInfo(positionAccount); {
guard(accountInfo);
if (accountInfo == null) { JupiterPerpsPositionInfo posInfo = positionDecoder.decode(accountInfo);
throw new IllegalArgumentException("Jupiter Perps position account does not exist: " + positionAccount); ΩUSDCAmountΩ sizeUsd = posInfo.sizeUsd();
}
if (!JUPITER_PERPS_PROGRAM_ID.equals(accountInfo.owner())) { CustodyAccountInfo cai = custodyDecoder.decode(custodyAccountInfo);
throw new IllegalArgumentException( BigInteger cumulativeInterestSnapshot = positionDecoder.decodeCumulativeInterestSnapshot(positionAccountInfo);
"Account is not owned by Jupiter Perps program: " + positionAccount ΩUSDCAmountΩ borrowFeePendingUsd = calculateBorrowFeeUsd(
cumulativeInterestSnapshot,
sizeUsd
); );
}
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo); JupiterPerpsPosition pos = new JupiterPerpsPosition(
JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo, tradedTokenMint); accountInfo,
posInfo.entryPrice(),
posInfo.direction(),
sizeUsd,
posInfo.collateralUsd(),
cai.mintAddress(),
borrowFeePendingUsd
);
return pos; 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 @Override
public Set<JupiterPerpsPosition> getOpenPositions(ΩSolanaWalletIdΩ owner) public Set<JupiterPerpsPosition> getOpenPositions(ΩSolanaWalletIdΩ owner)
@@ -73,7 +98,20 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
return Set.copyOf(positions); 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 throws IOException, InterruptedException
{ {
ΩSolanaAddressΩ custodyAccount = positionDecoder.decodeCustodyAccount(positionAccountInfo); ΩSolanaAddressΩ custodyAccount = positionDecoder.decodeCustodyAccount(positionAccountInfo);
@@ -87,7 +125,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
ΩSPLMintAddressΩ mintAddress = custodyDecoder.decodeMint(custodyAccountInfo); ΩSPLMintAddressΩ mintAddress = custodyDecoder.decodeMint(custodyAccountInfo);
return mintAddress; return mintAddress;
} }*/
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID = private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu"; "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.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsService; 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.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl; 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.Logger;
import org.slf4j.LoggerFactory; 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; import java.util.Set;
public class Main { public class Main {
@@ -38,28 +22,21 @@ public class Main {
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId); Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
AnchorIdlJupiterPerpsPositionDecoder positionDecoder = new AnchorIdlJupiterPerpsPositionDecoder();
AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
for (JupiterPerpsPosition position : positions) { for (JupiterPerpsPosition position : positions) {
SolanaAccountInfo positionAccountInfo = sbc.getAccountInfo(position.positionAccount()); System.out.println(" PositionAccount: " + position.positionAccount());
ΩSolanaAddressΩ collateralCustodyAccount = positionDecoder.decodeCollateralCustodyAccount(positionAccountInfo); System.out.println(" Token mint: " + position.tradedTokenMint());
SolanaAccountInfo collateralCustodyAccountInfo = sbc.getAccountInfo(collateralCustodyAccount); System.out.println(" Direction: " + position.direction());
BigInteger currentCumulativeInterestRate =custodyDecoder.decodeCurrentCumulativeInterestRate(collateralCustodyAccountInfo); System.out.println(" Value: " + position.value());
BigInteger cumulativeInterestSnapshot = positionDecoder.decodeCumulativeInterestSnapshot(positionAccountInfo); System.out.println(" Size $: " + position.size());
BigInteger difference = currentCumulativeInterestRate.subtract(cumulativeInterestSnapshot); System.out.println(" PnL: " + position.pnl() + " " + position.pnlPercent());
System.out.println(" Leverage: " + position.leverage() + "x");
System.out.println("positionAccount: " + position.positionAccount()); System.out.println(" Entry price: " + position.entryPrice());
System.out.println("currentCumulativeInterestRate: " + currentCumulativeInterestRate); System.out.println(" Market price: " + position.marketPrice());
System.out.println("cumulativeInterestSnapshot: " + cumulativeInterestSnapshot); System.out.println(" Collateral: " + position.collateral());
System.out.println("difference: " + difference); System.out.println(" Totals fees: " + position.totalFees());
System.out.println(" Borrow Fees Due: " + position.borrowFeesDue());
BigDecimal borrowFeeUsdCandidate = System.out.println(" Close Fees Pending: " + position.closeFeePending());
new BigDecimal(difference) System.out.println("Amount locked for account rent (SOL): " + position.accountRent());
.multiply(position.sizeUsd())
.divide(BigDecimal.valueOf(1_000_000_000L), 6, RoundingMode.CEILING);
System.out.println("borrowFeeUsdCandidate: " + borrowFeeUsdCandidate);
System.out.println(); System.out.println();
} }