diff --git a/bonfida/pom.xml b/bonfida/pom.xml
index 733b8c9..ad234f0 100644
--- a/bonfida/pom.xml
+++ b/bonfida/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/jupiter/pom.xml b/jupiter/pom.xml
new file mode 100644
index 0000000..8c7abeb
--- /dev/null
+++ b/jupiter/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ com.mmorrell
+ solanaj-programs
+ 1.32.0-SNAPSHOT
+
+
+ jupiter
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+ com.mmorrell
+ openbook
+ 1.32.0-SNAPSHOT
+ compile
+
+
+
+
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/manager/JupiterManager.java b/jupiter/src/main/java/com/mmorrell/jupiter/manager/JupiterManager.java
new file mode 100644
index 0000000..b38256a
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/manager/JupiterManager.java
@@ -0,0 +1,97 @@
+package com.mmorrell.jupiter.manager;
+
+import com.mmorrell.jupiter.model.*;
+import lombok.extern.slf4j.Slf4j;
+import org.p2p.solanaj.core.PublicKey;
+import org.p2p.solanaj.rpc.Cluster;
+import org.p2p.solanaj.rpc.RpcClient;
+import org.p2p.solanaj.rpc.RpcException;
+import org.p2p.solanaj.rpc.types.AccountInfo;
+
+import java.util.Base64;
+import java.util.Optional;
+
+@Slf4j
+public class JupiterManager {
+
+ private final RpcClient client;
+ private static final PublicKey JUPITER_PROGRAM_ID = new PublicKey("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu");
+
+ public JupiterManager() {
+ this.client = new RpcClient(Cluster.MAINNET);
+ }
+
+ public JupiterManager(RpcClient client) {
+ this.client = client;
+ }
+
+ public Optional getPosition(PublicKey positionPublicKey) {
+ try {
+ AccountInfo accountInfo = client.getApi().getAccountInfo(positionPublicKey);
+ if (accountInfo == null || accountInfo.getValue() == null) {
+ return Optional.empty();
+ }
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ return Optional.of(JupiterPerpPosition.fromByteArray(data));
+ } catch (RpcException e) {
+ log.warn("Error fetching position: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ public Optional getPool(PublicKey poolPublicKey) {
+ try {
+ AccountInfo accountInfo = client.getApi().getAccountInfo(poolPublicKey);
+ if (accountInfo == null || accountInfo.getValue() == null) {
+ return Optional.empty();
+ }
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ return Optional.of(JupiterPool.fromByteArray(data));
+ } catch (RpcException e) {
+ log.warn("Error fetching pool: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ public Optional getCustody(PublicKey custodyPublicKey) {
+ try {
+ AccountInfo accountInfo = client.getApi().getAccountInfo(custodyPublicKey);
+ if (accountInfo == null || accountInfo.getValue() == null) {
+ return Optional.empty();
+ }
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ return Optional.of(JupiterCustody.fromByteArray(data));
+ } catch (RpcException e) {
+ log.warn("Error fetching custody: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ public Optional getPositionRequest(PublicKey positionRequestPublicKey) {
+ try {
+ AccountInfo accountInfo = client.getApi().getAccountInfo(positionRequestPublicKey);
+ if (accountInfo == null || accountInfo.getValue() == null) {
+ return Optional.empty();
+ }
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ return Optional.of(JupiterPositionRequest.fromByteArray(data));
+ } catch (RpcException e) {
+ log.warn("Error fetching position request: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ public Optional getPerpetuals(PublicKey perpetualsPublicKey) {
+ try {
+ AccountInfo accountInfo = client.getApi().getAccountInfo(perpetualsPublicKey);
+ if (accountInfo == null || accountInfo.getValue() == null) {
+ return Optional.empty();
+ }
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ return Optional.of(JupiterPerpetuals.fromByteArray(data));
+ } catch (RpcException e) {
+ log.warn("Error fetching perpetuals: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterCustody.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterCustody.java
new file mode 100644
index 0000000..f3798d9
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterCustody.java
@@ -0,0 +1,227 @@
+package com.mmorrell.jupiter.model;
+
+import com.mmorrell.jupiter.util.JupiterUtil;
+import com.mmorrell.openbook.OpenBookUtil;
+
+import lombok.Builder;
+import lombok.Data;
+import org.p2p.solanaj.core.PublicKey;
+
+/**
+ * Represents a Jupiter Custody account in Jupiter Perpetuals.
+ */
+@Data
+@Builder
+public class JupiterCustody {
+ private PublicKey pool;
+ private PublicKey mint;
+ private PublicKey tokenAccount;
+ private byte decimals;
+ private boolean isStable;
+ private OracleParams oracle;
+ private PricingParams pricing;
+ private Permissions permissions;
+ private long targetRatioBps;
+ private Assets assets;
+ private FundingRateState fundingRateState;
+ private byte bump;
+ private byte tokenAccountBump;
+
+ @Data
+ @Builder
+ public static class OracleParams {
+ private PublicKey oracleAccount;
+ private byte oracleType;
+ private long maxPriceError;
+ private int maxPriceAgeSec;
+ }
+
+ @Data
+ @Builder
+ public static class PricingParams {
+ private long tradeImpactFeeScalar;
+ private long buffer;
+ private long swapSpread;
+ private long maxLeverage;
+ private long maxGlobalLongSizes;
+ private long maxGlobalShortSizes;
+ }
+
+ @Data
+ @Builder
+ public static class Permissions {
+ private boolean allowDeposit;
+ private boolean allowWithdraw;
+ private boolean allowTrade;
+ private boolean allowSwap;
+ private boolean allowAddLiquidity;
+ private boolean allowRemoveLiquidity;
+ private boolean allowUseAsCollateral;
+ }
+
+ @Data
+ @Builder
+ public static class Assets {
+ private long feesReserves;
+ private long owned;
+ private long locked;
+ private long guaranteedUsd;
+ private long globalShortSizes;
+ private long globalShortAveragePrices;
+ }
+
+ @Data
+ @Builder
+ public static class FundingRateState {
+ private long cumulativeInterestRate;
+ private long lastUpdate;
+ private long hourlyFundingDbps;
+ }
+
+ /**
+ * Deserializes a byte array into a JupiterCustody object.
+ *
+ * @param data the byte array representing the account data.
+ * @return a JupiterCustody object.
+ */
+ public static JupiterCustody fromByteArray(byte[] data) {
+ int offset = 8; // Skip discriminator
+
+ PublicKey pool = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey mint = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey tokenAccount = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ byte decimals = data[offset++];
+ boolean isStable = data[offset++] != 0;
+
+ OracleParams oracle = readOracleParams(data, offset);
+ offset += 45; // 32 (publicKey) + 1 (oracleType) + 8 (maxPriceError) + 4 (maxPriceAgeSec)
+
+ PricingParams pricing = readPricingParams(data, offset);
+ offset += 48; // Adjust based on actual size
+
+ Permissions permissions = readPermissions(data, offset);
+ offset += 7; // Adjust based on actual size
+
+ long targetRatioBps = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+
+ Assets assets = readAssets(data, offset);
+ offset += 48; // 6 fields * 8 bytes each
+
+ FundingRateState fundingRateState = readFundingRateState(data, offset);
+ offset += 32; // 16 (cumulativeInterestRate) + 8 (lastUpdate) + 8 (hourlyFundingDbps)
+
+ byte bump = data[offset++];
+ byte tokenAccountBump = data[offset];
+
+ return JupiterCustody.builder()
+ .pool(pool)
+ .mint(mint)
+ .tokenAccount(tokenAccount)
+ .decimals(decimals)
+ .isStable(isStable)
+ .oracle(oracle)
+ .pricing(pricing)
+ .permissions(permissions)
+ .targetRatioBps(targetRatioBps)
+ .assets(assets)
+ .fundingRateState(fundingRateState)
+ .bump(bump)
+ .tokenAccountBump(tokenAccountBump)
+ .build();
+ }
+
+ private static OracleParams readOracleParams(byte[] data, int offset) {
+ PublicKey oracleAccount = PublicKey.readPubkey(data, offset);
+ offset += 32;
+ byte oracleType = data[offset++];
+ long maxPriceError = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ int maxPriceAgeSec = OpenBookUtil.readInt32(data, offset);
+
+ return OracleParams.builder()
+ .oracleAccount(oracleAccount)
+ .oracleType(oracleType)
+ .maxPriceError(maxPriceError)
+ .maxPriceAgeSec(maxPriceAgeSec)
+ .build();
+ }
+
+ private static PricingParams readPricingParams(byte[] data, int offset) {
+ long tradeImpactFeeScalar = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long buffer = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long swapSpread = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long maxLeverage = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long maxGlobalLongSizes = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long maxGlobalShortSizes = JupiterUtil.readUint64(data, offset);
+
+ return PricingParams.builder()
+ .tradeImpactFeeScalar(tradeImpactFeeScalar)
+ .buffer(buffer)
+ .swapSpread(swapSpread)
+ .maxLeverage(maxLeverage)
+ .maxGlobalLongSizes(maxGlobalLongSizes)
+ .maxGlobalShortSizes(maxGlobalShortSizes)
+ .build();
+ }
+
+ private static Permissions readPermissions(byte[] data, int offset) {
+ return Permissions.builder()
+ .allowDeposit(data[offset++] != 0)
+ .allowWithdraw(data[offset++] != 0)
+ .allowTrade(data[offset++] != 0)
+ .allowSwap(data[offset++] != 0)
+ .allowAddLiquidity(data[offset++] != 0)
+ .allowRemoveLiquidity(data[offset++] != 0)
+ .allowUseAsCollateral(data[offset] != 0)
+ .build();
+ }
+
+ private static Assets readAssets(byte[] data, int offset) {
+ long feesReserves = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long owned = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long locked = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long guaranteedUsd = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long globalShortSizes = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+ long globalShortAveragePrices = JupiterUtil.readUint64(data, offset);
+
+ return Assets.builder()
+ .feesReserves(feesReserves)
+ .owned(owned)
+ .locked(locked)
+ .guaranteedUsd(guaranteedUsd)
+ .globalShortSizes(globalShortSizes)
+ .globalShortAveragePrices(globalShortAveragePrices)
+ .build();
+ }
+
+ private static FundingRateState readFundingRateState(byte[] data, int offset) {
+ long cumulativeInterestRate = OpenBookUtil.readUint128(data, offset).longValue();
+ offset += 16;
+ long lastUpdate = JupiterUtil.readInt64(data, offset);
+ offset += 8;
+ long hourlyFundingDbps = JupiterUtil.readUint64(data, offset);
+
+ return FundingRateState.builder()
+ .cumulativeInterestRate(cumulativeInterestRate)
+ .lastUpdate(lastUpdate)
+ .hourlyFundingDbps(hourlyFundingDbps)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java
new file mode 100644
index 0000000..d15ad51
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java
@@ -0,0 +1,78 @@
+package com.mmorrell.jupiter.model;
+
+import lombok.Builder;
+import lombok.Data;
+import org.p2p.solanaj.core.PublicKey;
+
+/**
+ * 8 (padding) +
+ * 32 (owner) +
+ * 32 (pool) +
+ * 32 (custody) +
+ * 32 (collateralCustody) +
+ * 8 (openTime) +
+ * 8 (updateTime) +
+ * 4 (side) +
+ * 8 (price) +
+ * 8 (sizeUsd) +
+ * 8 (collateralUsd) +
+ * 8 (realisedPnlUsd) +
+ * 8 (cumulativeInterestSnapshot) +
+ * 8 (lockedAmount) +
+ * 4 (bump) = 8 + 128 + 64 + 4 + 4 = 216 bytes
+ */
+@Data
+@Builder
+public class JupiterPerpPosition {
+ private PublicKey owner;
+ private PublicKey pool;
+ private PublicKey custody;
+ private PublicKey collateralCustody;
+ private long openTime;
+ private long updateTime;
+ private Side side;
+ private long price;
+ private long sizeUsd;
+ private long collateralUsd;
+ private long realisedPnlUsd;
+ private long cumulativeInterestSnapshot;
+ private long lockedAmount;
+ private byte bump;
+
+ public enum Side {
+ LONG,
+ SHORT
+ }
+
+ public static JupiterPerpPosition fromByteArray(byte[] data) {
+ int offset = 8; // Start at offset 8 to skip the padding
+ return JupiterPerpPosition.builder()
+ .owner(PublicKey.readPubkey(data, offset))
+ .pool(PublicKey.readPubkey(data, offset += 32))
+ .custody(PublicKey.readPubkey(data, offset += 32))
+ .collateralCustody(PublicKey.readPubkey(data, offset += 32))
+ .openTime(readInt64(data, offset += 32))
+ .updateTime(readInt64(data, offset += 8))
+ .side(data[offset += 8] == 1 ? Side.LONG : Side.SHORT)
+ .price(readUint64(data, offset += 1))
+ .sizeUsd(readUint64(data, offset += 8))
+ .collateralUsd(readUint64(data, offset += 8))
+ .realisedPnlUsd(readInt64(data, offset += 8))
+ .cumulativeInterestSnapshot(readUint128(data, offset += 8))
+ .lockedAmount(readUint64(data, offset += 16))
+ .bump(data[offset += 8])
+ .build();
+ }
+
+ private static long readInt64(byte[] data, int offset) {
+ return org.bitcoinj.core.Utils.readInt64(data, offset);
+ }
+
+ private static long readUint64(byte[] data, int offset) {
+ return org.bitcoinj.core.Utils.readInt64(data, offset);
+ }
+
+ private static long readUint128(byte[] data, int offset) {
+ return org.bitcoinj.core.Utils.readInt64(data, offset);
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpetuals.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpetuals.java
new file mode 100644
index 0000000..6c7a732
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpetuals.java
@@ -0,0 +1,81 @@
+package com.mmorrell.jupiter.model;
+
+import com.mmorrell.jupiter.util.JupiterUtil;
+import lombok.Builder;
+import lombok.Data;
+import org.p2p.solanaj.core.PublicKey;
+
+/**
+ * Represents a Jupiter Perpetuals account in Jupiter Perpetuals.
+ */
+@Data
+@Builder
+public class JupiterPerpetuals {
+ private Permissions permissions;
+ private PublicKey pool; // Changed from List to PublicKey
+ private PublicKey admin;
+ private byte transferAuthorityBump;
+ private byte perpetualsBump;
+ private long inceptionTime;
+
+ @Data
+ @Builder
+ public static class Permissions {
+ private boolean allowSwap;
+ private boolean allowAddLiquidity;
+ private boolean allowRemoveLiquidity;
+ private boolean allowIncreasePosition; // New field
+ private boolean allowDecreasePosition; // New field
+ private boolean allowCollateralWithdrawal;
+ private boolean allowLiquidatePosition; // New field
+ }
+
+ /**
+ * Deserializes a byte array into a JupiterPerpetuals object.
+ *
+ * @param data the byte array representing the account data.
+ * @return a JupiterPerpetuals object.
+ */
+ public static JupiterPerpetuals fromByteArray(byte[] data) {
+ int offset = 8; // Skip discriminator
+
+ Permissions permissions = readPermissions(data, offset);
+ offset += 8; // Adjust based on actual size
+
+ // Hardcoded offsets based on research
+ offset = 19; // Set offset to the found pool offset
+ PublicKey pool = PublicKey.readPubkey(data, offset);
+ offset += 32; // Move to the next field
+
+ offset = 51; // Set offset to the found admin offset
+ PublicKey admin = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ byte transferAuthorityBump = data[offset++];
+ byte perpetualsBump = data[offset++];
+
+ long inceptionTime = JupiterUtil.readInt64(data, offset);
+
+ return JupiterPerpetuals.builder()
+ .permissions(permissions)
+ .pool(pool) // Set the single pool PublicKey
+ .admin(admin)
+ .transferAuthorityBump(transferAuthorityBump)
+ .perpetualsBump(perpetualsBump)
+ .inceptionTime(inceptionTime)
+ .build();
+ }
+
+ // Add private static methods to read Permissions and PublicKey
+ private static Permissions readPermissions(byte[] data, int offset) {
+ return Permissions.builder()
+ .allowSwap(data[offset++] != 0)
+ .allowAddLiquidity(data[offset++] != 0)
+ .allowRemoveLiquidity(data[offset++] != 0)
+ .allowIncreasePosition(data[offset++] != 0) // New field
+ .allowDecreasePosition(data[offset++] != 0) // New field
+ .allowCollateralWithdrawal(data[offset++] != 0)
+ .allowLiquidatePosition(data[offset] != 0) // New field
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPool.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPool.java
new file mode 100644
index 0000000..c72cfb7
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPool.java
@@ -0,0 +1,172 @@
+package com.mmorrell.jupiter.model;
+
+import com.mmorrell.openbook.OpenBookUtil;
+import lombok.Builder;
+import lombok.Data;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.p2p.solanaj.core.PublicKey;
+
+import static com.mmorrell.openbook.OpenBookUtil.readInt32;
+
+/**
+ * Represents a Jupiter Perpetuals Pool account.
+ */
+@Data
+@Builder
+public class JupiterPool {
+ private String name;
+ private List custodies;
+ private long aumUsd;
+ private Limit limit;
+ private Fees fees;
+ private PoolApr poolApr;
+ private long maxRequestExecutionSec;
+ private byte bump;
+ private byte lpTokenBump;
+ private long inceptionTime;
+
+ @Data
+ @Builder
+ public static class Limit {
+ private long maxAumUsd;
+ private long tokenWeightageBufferBps;
+ private long maxPositionUsd;
+ }
+
+ @Data
+ @Builder
+ public static class Fees {
+ private long increasePositionBps;
+ private long decreasePositionBps;
+ private long addRemoveLiquidityBps;
+ private long swapBps;
+ private long taxBps;
+ private long stableSwapBps;
+ private long stableSwapTaxBps;
+ private long liquidationRewardBps;
+ private long protocolShareBps;
+ }
+
+ @Data
+ @Builder
+ public static class PoolApr {
+ private long lastUpdated;
+ private long feeAprBps;
+ private long realizedFeeUsd;
+ }
+
+ /**
+ * Deserializes a byte array into a JupiterPool object.
+ *
+ * @param data the byte array representing the account data.
+ * @return a JupiterPool object.
+ */
+ public static JupiterPool fromByteArray(byte[] data) {
+ int offset = 8; // Skip discriminator
+
+ String name = readString(data, offset);
+ offset += 4 + name.length(); // 4 bytes for length + actual string length
+
+ List custodies = readPublicKeyList(data, offset);
+ offset += 4 + (custodies.size() * 32); // 4 bytes for vector length + 32 bytes per PublicKey
+
+ long aumUsd = readUint128(data, offset);
+ offset += 16;
+
+ Limit limit = readLimit(data, offset);
+ offset += 40; // 16 + 16 + 8 bytes each for maxAumUsd, tokenWeightageBufferBps, maxPositionUsd
+
+ Fees fees = readFees(data, offset);
+ offset += 72; // 8 bytes each for 9 fee fields
+
+ PoolApr poolApr = readPoolApr(data, offset);
+ offset += 24;
+
+ long maxRequestExecutionSec = readInt64(data, offset);
+ offset += 8;
+
+ byte bump = data[offset++];
+ byte lpTokenBump = data[offset++];
+
+ long inceptionTime = readInt64(data, offset);
+
+ return JupiterPool.builder()
+ .name(name)
+ .custodies(custodies)
+ .aumUsd(aumUsd)
+ .limit(limit)
+ .fees(fees)
+ .poolApr(poolApr)
+ .maxRequestExecutionSec(maxRequestExecutionSec)
+ .bump(bump)
+ .lpTokenBump(lpTokenBump)
+ .inceptionTime(inceptionTime)
+ .build();
+ }
+
+ private static String readString(byte[] data, int offset) {
+ int length = readInt32(data, offset);
+ offset += 4;
+ byte[] stringBytes = Arrays.copyOfRange(data, offset, offset + length);
+ return new String(stringBytes, StandardCharsets.UTF_8);
+ }
+
+ private static List readPublicKeyList(byte[] data, int offset) {
+ List custodies = new ArrayList<>();
+ int vectorLength = readInt32(data, offset);
+ offset += 4; // Move past the vector length
+ for (int i = 0; i < vectorLength; i++) {
+ PublicKey custody = PublicKey.readPubkey(data, offset);
+ custodies.add(custody);
+ offset += 32; // Move to the next PublicKey
+ }
+ return custodies;
+ }
+
+ private static long readUint128(byte[] data, int offset) {
+ return OpenBookUtil.readUint128(data, offset).longValue();
+ }
+
+ private static Limit readLimit(byte[] data, int offset) {
+ return Limit.builder()
+ .maxAumUsd(readUint128(data, offset))
+ .tokenWeightageBufferBps(readUint128(data, offset + 16))
+ .maxPositionUsd(readUint64(data, offset + 32))
+ .build();
+ }
+
+ private static Fees readFees(byte[] data, int offset) {
+ return Fees.builder()
+ .increasePositionBps(readUint64(data, offset))
+ .decreasePositionBps(readUint64(data, offset + 8))
+ .addRemoveLiquidityBps(readUint64(data, offset + 16))
+ .swapBps(readUint64(data, offset + 24))
+ .taxBps(readUint64(data, offset + 32))
+ .stableSwapBps(readUint64(data, offset + 40))
+ .stableSwapTaxBps(readUint64(data, offset + 48))
+ .liquidationRewardBps(readUint64(data, offset + 56))
+ .protocolShareBps(readUint64(data, offset + 64))
+ .build();
+ }
+
+ private static PoolApr readPoolApr(byte[] data, int offset) {
+ return PoolApr.builder()
+ .lastUpdated(readInt64(data, offset))
+ .feeAprBps(readInt64(data, offset + 8))
+ .realizedFeeUsd(readInt64(data, offset + 16))
+ .build();
+ }
+
+ private static long readInt64(byte[] data, int offset) {
+ return org.bitcoinj.core.Utils.readInt64(data, offset);
+ }
+
+ private static long readUint64(byte[] data, int offset) {
+ return Long.parseUnsignedLong(Long.toUnsignedString(org.bitcoinj.core.Utils.readInt64(data, offset)));
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPositionRequest.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPositionRequest.java
new file mode 100644
index 0000000..5fafee3
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPositionRequest.java
@@ -0,0 +1,209 @@
+package com.mmorrell.jupiter.model;
+
+import com.mmorrell.jupiter.util.JupiterUtil;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.p2p.solanaj.core.PublicKey;
+
+import java.util.Arrays;
+
+/**
+ * Represents a Jupiter PositionRequest account in Jupiter Perpetuals.
+ */
+@Data
+@Builder
+@Slf4j
+public class JupiterPositionRequest {
+ private PublicKey owner;
+ private PublicKey pool;
+ private PublicKey custody;
+ private PublicKey position;
+ private PublicKey mint;
+ private long openTime;
+ private long updateTime;
+ private long sizeUsdDelta;
+ private long collateralDelta;
+ private RequestChange requestChange;
+ private RequestType requestType;
+ private Side side;
+ private Long priceSlippage;
+ private Long jupiterMinimumOut;
+ private Long preSwapAmount;
+ private Long triggerPrice;
+ private Boolean triggerAboveThreshold;
+ private Boolean entirePosition;
+ private boolean executed;
+ private long counter;
+ private byte bump;
+ private PublicKey referral;
+
+ /**
+ * Enum representing the type of request change.
+ */
+ public enum RequestChange {
+ None,
+ Increase,
+ Decrease
+ }
+
+ /**
+ * Enum representing the type of request.
+ */
+ public enum RequestType {
+ Market,
+ Trigger
+ }
+
+ /**
+ * Enum representing the side of the request.
+ */
+ public enum Side {
+ None,
+ Long,
+ Short
+ }
+
+ /**
+ * Deserializes a byte array into a JupiterPositionRequest object.
+ *
+ * @param data the byte array representing the account data.
+ * @return a JupiterPositionRequest object.
+ */
+ public static JupiterPositionRequest fromByteArray(byte[] data) {
+ int offset = 8; // Skip discriminator
+
+ PublicKey owner = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey pool = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey custody = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey position = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ PublicKey mint = PublicKey.readPubkey(data, offset);
+ offset += 32;
+
+ long openTime = JupiterUtil.readInt64(data, offset);
+ offset += 8;
+
+ long updateTime = JupiterUtil.readInt64(data, offset);
+ offset += 8;
+
+ long sizeUsdDelta = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+
+ long collateralDelta = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+
+ RequestChange requestChange = RequestChange.values()[data[offset++]];
+ RequestType requestType = RequestType.values()[data[offset++]];
+ Side side = Side.values()[data[offset++]];
+
+ // Read priceSlippage (Optional u64)
+ boolean hasPriceSlippage = data[offset] != 0;
+ Long priceSlippage = null;
+ if (hasPriceSlippage) {
+ priceSlippage = JupiterUtil.readUint64(data, offset + 1);
+ offset += 9; // 1 byte for option + 8 bytes for uint64
+ } else {
+ offset += 1; // Only 1 byte for option
+ }
+
+ // Read jupiterMinimumOut (Optional u64)
+ boolean hasJupiterMinimumOut = data[offset] != 0;
+ Long jupiterMinimumOut = null;
+ if (hasJupiterMinimumOut) {
+ jupiterMinimumOut = JupiterUtil.readUint64(data, offset + 1);
+ offset += 9;
+ } else {
+ offset += 1;
+ }
+
+ // Read preSwapAmount (Optional u64)
+ boolean hasPreSwapAmount = data[offset] != 0;
+ Long preSwapAmount = null;
+ if (hasPreSwapAmount) {
+ preSwapAmount = JupiterUtil.readUint64(data, offset + 1);
+ offset += 9;
+ } else {
+ offset += 1;
+ }
+
+ // Read triggerPrice (Optional u64)
+ boolean hasTriggerPrice = data[offset] != 0;
+ Long triggerPrice = null;
+ if (hasTriggerPrice) {
+ triggerPrice = JupiterUtil.readUint64(data, offset + 1);
+ offset += 9;
+ } else {
+ offset += 1;
+ }
+
+ // Read triggerAboveThreshold (Optional boolean)
+ boolean hasTriggerAboveThreshold = data[offset] != 0;
+ Boolean triggerAboveThreshold = null;
+ if (hasTriggerAboveThreshold) {
+ triggerAboveThreshold = data[offset + 1] != 0;
+ offset += 2; // 1 byte for option + 1 byte for boolean
+ } else {
+ offset += 1;
+ }
+
+ // Read entirePosition (Optional boolean)
+ boolean hasEntirePosition = data[offset] != 0;
+ Boolean entirePosition = null;
+ if (hasEntirePosition) {
+ entirePosition = data[offset + 1] != 0;
+ offset += 2;
+ } else {
+ offset += 1;
+ }
+
+ boolean executed = data[offset++] != 0;
+
+ long counter = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+
+ byte bump = data[offset++];
+
+ // Read referral (Optional PublicKey)
+ boolean hasReferral = data[offset] != 0;
+ PublicKey referral = null;
+ if (hasReferral) {
+ referral = PublicKey.readPubkey(data, offset + 1);
+ offset += 33; // 1 byte for option + 32 bytes for PublicKey
+ } else {
+ offset += 1;
+ }
+
+ return JupiterPositionRequest.builder()
+ .owner(owner)
+ .pool(pool)
+ .custody(custody)
+ .position(position)
+ .mint(mint)
+ .openTime(openTime)
+ .updateTime(updateTime)
+ .sizeUsdDelta(sizeUsdDelta)
+ .collateralDelta(collateralDelta)
+ .requestChange(requestChange)
+ .requestType(requestType)
+ .side(side)
+ .priceSlippage(priceSlippage)
+ .jupiterMinimumOut(jupiterMinimumOut)
+ .preSwapAmount(preSwapAmount)
+ .triggerPrice(triggerPrice)
+ .triggerAboveThreshold(triggerAboveThreshold)
+ .entirePosition(entirePosition)
+ .executed(executed)
+ .counter(counter)
+ .bump(bump)
+ .referral(referral)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterTestOracle.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterTestOracle.java
new file mode 100644
index 0000000..434ca9a
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterTestOracle.java
@@ -0,0 +1,46 @@
+package com.mmorrell.jupiter.model;
+
+import com.mmorrell.jupiter.util.JupiterUtil;
+import com.mmorrell.openbook.OpenBookUtil;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Represents a Jupiter TestOracle account in Jupiter Perpetuals.
+ */
+@Data
+@Builder
+public class JupiterTestOracle {
+ private long price;
+ private int expo;
+ private long conf;
+ private long publishTime;
+
+ /**
+ * Deserializes a byte array into a JupiterTestOracle object.
+ *
+ * @param data the byte array representing the account data.
+ * @return a JupiterTestOracle object.
+ */
+ public static JupiterTestOracle fromByteArray(byte[] data) {
+ int offset = 8; // Skip discriminator
+
+ long price = JupiterUtil.readInt64(data, offset);
+ offset += 8;
+
+ int expo = OpenBookUtil.readInt32(data, offset);
+ offset += 4;
+
+ long conf = JupiterUtil.readUint64(data, offset);
+ offset += 8;
+
+ long publishTime = JupiterUtil.readInt64(data, offset);
+
+ return JupiterTestOracle.builder()
+ .price(price)
+ .expo(expo)
+ .conf(conf)
+ .publishTime(publishTime)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/util/JupiterUtil.java b/jupiter/src/main/java/com/mmorrell/jupiter/util/JupiterUtil.java
new file mode 100644
index 0000000..4417ba3
--- /dev/null
+++ b/jupiter/src/main/java/com/mmorrell/jupiter/util/JupiterUtil.java
@@ -0,0 +1,35 @@
+package com.mmorrell.jupiter.util;
+
+import org.p2p.solanaj.core.PublicKey;
+
+public class JupiterUtil {
+ public static int readUint32(byte[] data, int offset) {
+ return (data[offset] & 0xFF) |
+ ((data[offset + 1] & 0xFF) << 8) |
+ ((data[offset + 2] & 0xFF) << 16) |
+ ((data[offset + 3] & 0xFF) << 24);
+ }
+
+ public static long readUint64(byte[] data, int offset) {
+ return Long.parseUnsignedLong(Long.toUnsignedString(org.bitcoinj.core.Utils.readInt64(data, offset)));
+ }
+
+ public static long readInt64(byte[] data, int offset) {
+ return org.bitcoinj.core.Utils.readInt64(data, offset);
+ }
+
+ public static Long readOptionalUint64(byte[] data, int offset) {
+ boolean hasValue = data[offset] != 0;
+ return hasValue ? readUint64(data, offset + 1) : null;
+ }
+
+ public static Boolean readOptionalBoolean(byte[] data, int offset) {
+ boolean hasValue = data[offset] != 0;
+ return hasValue ? data[offset + 1] != 0 : null;
+ }
+
+ public static PublicKey readOptionalPublicKey(byte[] data, int offset) {
+ boolean hasValue = data[offset] != 0;
+ return hasValue ? PublicKey.readPubkey(data, offset + 1) : null;
+ }
+}
\ No newline at end of file
diff --git a/jupiter/src/test/java/JupiterTest.java b/jupiter/src/test/java/JupiterTest.java
new file mode 100644
index 0000000..55cdc8a
--- /dev/null
+++ b/jupiter/src/test/java/JupiterTest.java
@@ -0,0 +1,324 @@
+import com.google.common.io.Files;
+import com.mmorrell.jupiter.manager.JupiterManager;
+import com.mmorrell.jupiter.model.*;
+import lombok.extern.slf4j.Slf4j;
+import org.bitcoinj.core.Base58;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+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.Memcmp;
+import org.p2p.solanaj.rpc.types.ProgramAccount;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test class for Jupiter Perpetuals positions.
+ */
+@Slf4j
+public class JupiterTest {
+
+ private final RpcClient client = new RpcClient("https://mainnet.helius-rpc.com/?api-key=a778b653-bdd6-41bc-8cda-0c7377faf1dd");
+
+ @BeforeEach
+ public void setup() {
+ try {
+ Thread.sleep(1001L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testJupiterPerpPositionDeserialization() throws RpcException {
+ PublicKey positionPublicKey = new PublicKey("FdqbJAvADUJzZsBFK1ArhV79vXLmpKUMB4oXSrW8rSE");
+ PublicKey positionPublicKeyOwner = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(positionPublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+
+ // Deserialize the data into JupiterPerpPosition
+ JupiterPerpPosition position = JupiterPerpPosition.fromByteArray(data);
+
+ // Log the deserialized position
+ log.info("Deserialized JupiterPerpPosition: {}", position);
+
+ // Assertions
+ assertNotNull(position);
+ assertEquals(positionPublicKeyOwner, position.getOwner());
+
+ // Add more specific assertions based on expected values
+ assertNotNull(position.getPool());
+ assertNotNull(position.getCustody());
+ assertNotNull(position.getCollateralCustody());
+ assertTrue(position.getOpenTime() > 0);
+ assertTrue(position.getUpdateTime() > 0);
+ assertNotNull(position.getSide());
+ assertTrue(position.getPrice() > 0);
+ assertTrue(position.getSizeUsd() > 0);
+ assertTrue(position.getCollateralUsd() > 0);
+ // Add more assertions as needed
+ }
+
+ @Test
+ @Disabled
+ public void testGetAllJupiterPerpPositions() throws RpcException {
+ PublicKey programId = new PublicKey("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu");
+
+ // Get the discriminator for the Position account
+ byte[] positionDiscriminator = getAccountDiscriminator("Position");
+
+ // Create a memcmp filter for the discriminator at offset 0
+ Memcmp memcmpFilter = new Memcmp(0, Base58.encode(positionDiscriminator));
+
+ // Get all program accounts matching the filters
+ List positionAccounts = client.getApi().getProgramAccounts(
+ programId,
+ Collections.singletonList(memcmpFilter),
+ 216
+ );
+
+ List positions = new ArrayList<>();
+ for (ProgramAccount account : positionAccounts) {
+ // Decode the account data
+ byte[] data = account.getAccount().getDecodedData();
+
+ // Deserialize the data into JupiterPerpPosition
+ JupiterPerpPosition position = JupiterPerpPosition.fromByteArray(data);
+
+ if (position.getSizeUsd() > 0) {
+ // Add to the list
+ positions.add(position);
+ }
+ }
+
+ positions.sort(Comparator.comparingLong(JupiterPerpPosition::getSizeUsd));
+
+ // Log the positions
+ for (JupiterPerpPosition position : positions) {
+ double leverage = (double) position.getSizeUsd() / position.getCollateralUsd();
+ log.info("Owner: {}, Size USD: {}, Leverage: {}", position.getOwner().toBase58(), position.getSizeUsd(), leverage);
+ }
+ }
+
+ /**
+ * Calculates the account discriminator for a given account name.
+ *
+ * @param accountName the name of the account.
+ * @return the first 8 bytes of the SHA-256 hash of "account:".
+ */
+ private byte[] getAccountDiscriminator(String accountName) {
+ String preimage = "account:" + accountName;
+ try {
+ MessageDigest hasher = MessageDigest.getInstance("SHA-256");
+ hasher.update(preimage.getBytes(StandardCharsets.UTF_8));
+ return Arrays.copyOfRange(hasher.digest(), 0, 8);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("SHA-256 algorithm not found for discriminator calculation.");
+ }
+ }
+
+ @Test
+ public void testJupiterPoolDeserialization() throws RpcException {
+ PublicKey poolPublicKey = new PublicKey("5BUwFW4nRbftYTDMbgxykoFWqWHPzahFSNAaaaJtVKsq");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(poolPublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+
+ // Deserialize the data into JupiterPool
+ JupiterPool pool = JupiterPool.fromByteArray(data);
+
+ log.info("Deserialized JupiterPool: {}", pool);
+
+ // Assertions
+ assertNotNull(pool);
+ assertEquals("Pool", pool.getName());
+ assertEquals(5, pool.getCustodies().size());
+ assertTrue(pool.getAumUsd() > 0);
+ assertNotNull(pool.getLimit());
+ assertNotNull(pool.getFees());
+ assertNotNull(pool.getPoolApr());
+ assertEquals(45, pool.getMaxRequestExecutionSec());
+ assertEquals((byte) 252, pool.getBump());
+ assertEquals((byte) 254, pool.getLpTokenBump());
+ assertEquals(1689677832, pool.getInceptionTime());
+ }
+
+ @Test
+ public void testJupiterCustodyDeserialization() throws RpcException {
+ PublicKey custodyPublicKey = new PublicKey("7xS2gz2bTp3fwCC7knJvUWTEU9Tycczu6VhJYKgi1wdz");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(custodyPublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+
+ // Deserialize the data into JupiterCustody
+ JupiterCustody custody = JupiterCustody.fromByteArray(data);
+
+ // Assertions
+ assertNotNull(custody);
+ assertNotNull(custody.getPool());
+ assertNotNull(custody.getMint());
+ assertNotNull(custody.getTokenAccount());
+
+ log.info("Deserialized JupiterCustody: {}", custody);
+ }
+
+ @Test
+ public void testJupiterPerpetualsDeserialization() throws RpcException, IOException {
+ PublicKey perpetualsPublicKey = new PublicKey("H4ND9aYttUVLFmNypZqLjZ52FYiGvdEB45GmwNoKEjTj");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(perpetualsPublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+
+ // Deserialize the data into JupiterPerpetuals
+ JupiterPerpetuals perpetuals = JupiterPerpetuals.fromByteArray(data);
+
+ // Assertions
+ assertNotNull(perpetuals);
+ assertNotNull(perpetuals.getPermissions());
+ assertNotNull(perpetuals.getPool());
+ assertNotNull(perpetuals.getAdmin());
+
+ for (int i = 0; i < data.length; i++) {
+ try {
+ PublicKey pk = new PublicKey(Arrays.copyOfRange(data, i, i + 32));
+ if (pk.toBase58().equalsIgnoreCase("5BUwFW4nRbftYTDMbgxykoFWqWHPzahFSNAaaaJtVKsq")) {
+ log.info("FOUND OFFSET 1 (POOL): {}", i);
+ }
+ if (pk.toBase58().equalsIgnoreCase("9hdBK7FUzv4NjZbtYfm39F5utJyFsmCwbF9Mow5Pr1sN")) {
+ log.info("FOUND OFFSET 2 (ADMIN): {}", i);
+ }
+ } catch (Exception ex) {
+ log.error(ex.getMessage());
+ }
+ }
+
+ log.info("Deserialized JupiterPerpetuals: {}", perpetuals);
+
+ // Assuming one pool for now. The vector deserialization seemed off.
+ assertEquals("5BUwFW4nRbftYTDMbgxykoFWqWHPzahFSNAaaaJtVKsq", perpetuals.getPool().toBase58());
+
+ }
+
+ @Test
+ public void testJupiterPositionRequestDeserialization() throws RpcException, IOException {
+ PublicKey positionRequestPublicKey = new PublicKey("APYrGNtsTTMpNNBQBpALxYnwYfKDQyFocf3d1j6jkuzf");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(positionRequestPublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+ Files.write(data, new File("jupiterPositionRequest.bin"));
+
+ // Deserialize the data into JupiterPositionRequest
+ JupiterPositionRequest positionRequest = JupiterPositionRequest.fromByteArray(data);
+
+ // Assertions
+ assertNotNull(positionRequest);
+ assertNotNull(positionRequest.getOwner());
+ assertNotNull(positionRequest.getPool());
+ assertNotNull(positionRequest.getCustody());
+ assertTrue(positionRequest.getOpenTime() > 0);
+ assertTrue(positionRequest.getUpdateTime() > 0);
+
+ log.info("Deserialized JupiterPositionRequest: {}", positionRequest);
+ }
+
+ @Test
+ @Disabled
+ public void testJupiterTestOracleDeserialization() throws RpcException {
+ PublicKey testOraclePublicKey = new PublicKey("YourTestOraclePublicKeyHere");
+
+ // Fetch the account data
+ AccountInfo accountInfo = client.getApi().getAccountInfo(testOraclePublicKey);
+
+ assertNotNull(accountInfo, "Account info should not be null");
+
+ byte[] data = Base64.getDecoder().decode(accountInfo.getValue().getData().get(0));
+
+ // Deserialize the data into JupiterTestOracle
+ JupiterTestOracle testOracle = JupiterTestOracle.fromByteArray(data);
+
+ // Assertions
+ assertNotNull(testOracle);
+ assertTrue(testOracle.getPrice() != 0);
+ assertTrue(testOracle.getPublishTime() > 0);
+ // Add more assertions as needed
+ }
+
+ @Test
+ public void testJupiterManager() {
+ JupiterManager manager = new JupiterManager(client);
+
+ // Test getPosition
+ PublicKey positionPublicKey = new PublicKey("FdqbJAvADUJzZsBFK1ArhV79vXLmpKUMB4oXSrW8rSE");
+ Optional position = manager.getPosition(positionPublicKey);
+ assertTrue(position.isPresent());
+ assertEquals(new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq"), position.get().getOwner());
+
+ // Test getPool
+ PublicKey poolPublicKey = new PublicKey("5BUwFW4nRbftYTDMbgxykoFWqWHPzahFSNAaaaJtVKsq");
+ Optional pool = manager.getPool(poolPublicKey);
+ assertTrue(pool.isPresent());
+ assertEquals("Pool", pool.get().getName());
+
+ // Test getCustody
+ PublicKey custodyPublicKey = new PublicKey("7xS2gz2bTp3fwCC7knJvUWTEU9Tycczu6VhJYKgi1wdz");
+ Optional custody = manager.getCustody(custodyPublicKey);
+ assertTrue(custody.isPresent());
+ assertNotNull(custody.get().getPool());
+
+ // Test getPositionRequest
+ PublicKey positionRequestPublicKey = new PublicKey("APYrGNtsTTMpNNBQBpALxYnwYfKDQyFocf3d1j6jkuzf");
+ Optional positionRequest = manager.getPositionRequest(positionRequestPublicKey);
+ assertTrue(positionRequest.isPresent());
+ assertNotNull(positionRequest.get().getOwner());
+
+ // Test getPerpetuals
+ PublicKey perpetualsPublicKey = new PublicKey("H4ND9aYttUVLFmNypZqLjZ52FYiGvdEB45GmwNoKEjTj");
+ Optional perpetuals = manager.getPerpetuals(perpetualsPublicKey);
+ assertTrue(perpetuals.isPresent());
+ assertNotNull(perpetuals.get().getPool());
+ }
+
+ @Test
+ public void testJupiterManagerWithInvalidPublicKeys() {
+ JupiterManager manager = new JupiterManager(client);
+ PublicKey invalidPublicKey = new PublicKey("1111111111111111111111111111111111111111111");
+
+ // Test all methods with invalid public key
+ assertFalse(manager.getPosition(invalidPublicKey).isPresent());
+ assertFalse(manager.getPool(invalidPublicKey).isPresent());
+ assertFalse(manager.getCustody(invalidPublicKey).isPresent());
+ assertFalse(manager.getPositionRequest(invalidPublicKey).isPresent());
+ assertFalse(manager.getPerpetuals(invalidPublicKey).isPresent());
+ }
+}
diff --git a/magiceden/pom.xml b/magiceden/pom.xml
index 29cc2c9..05a60b1 100644
--- a/magiceden/pom.xml
+++ b/magiceden/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/mango/pom.xml b/mango/pom.xml
index 43615fc..7ceff70 100644
--- a/mango/pom.xml
+++ b/mango/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/metaplex/pom.xml b/metaplex/pom.xml
index eb03e8f..d7cf57f 100644
--- a/metaplex/pom.xml
+++ b/metaplex/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/openbook/pom.xml b/openbook/pom.xml
index 91d5574..8aae976 100644
--- a/openbook/pom.xml
+++ b/openbook/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/phoenix/pom.xml b/phoenix/pom.xml
index 074d3b6..192ac16 100644
--- a/phoenix/pom.xml
+++ b/phoenix/pom.xml
@@ -6,7 +6,7 @@
com.mmorrell
solanaj-programs
- 1.31.0
+ 1.32.0-SNAPSHOT
phoenix
@@ -20,13 +20,13 @@
com.mmorrell
serum
- 1.31.0
+ 1.32.0-SNAPSHOT
compile
com.mmorrell
metaplex
- 1.31.0
+ 1.32.0-SNAPSHOT
test
diff --git a/pom.xml b/pom.xml
index 2a9ae6a..9febe09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.mmorrell
solanaj-programs
pom
- 1.31.0
+ 1.32.0-SNAPSHOT
${project.groupId}:${project.artifactId}
Program libraries for SolanaJ, a library for Solana RPC
https://github.com/skynetcap/solanaj-programs
@@ -37,6 +37,7 @@
openbook
phoenix
metaplex
+ jupiter
diff --git a/pyth/pom.xml b/pyth/pom.xml
index 4c1fe29..1c7cc3c 100644
--- a/pyth/pom.xml
+++ b/pyth/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/serum/pom.xml b/serum/pom.xml
index 01ee8be..4b23d8b 100644
--- a/serum/pom.xml
+++ b/serum/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0
diff --git a/zeta/pom.xml b/zeta/pom.xml
index 121772a..cb5964a 100644
--- a/zeta/pom.xml
+++ b/zeta/pom.xml
@@ -5,7 +5,7 @@
solanaj-programs
com.mmorrell
- 1.31.0
+ 1.32.0-SNAPSHOT
4.0.0