diff --git a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java index eb669b7..dc450e3 100644 --- a/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java +++ b/jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterPerpPosition.java @@ -4,6 +4,23 @@ 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 { diff --git a/jupiter/src/test/java/JupiterTest.java b/jupiter/src/test/java/JupiterTest.java index 86d77e9..2fd1274 100644 --- a/jupiter/src/test/java/JupiterTest.java +++ b/jupiter/src/test/java/JupiterTest.java @@ -1,25 +1,29 @@ import com.mmorrell.jupiter.model.JupiterPerpPosition; import lombok.extern.slf4j.Slf4j; +import org.bitcoinj.core.Base58; import org.junit.jupiter.api.Test; 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 org.p2p.solanaj.rpc.types.Memcmp; +import org.p2p.solanaj.rpc.types.ProgramAccount; -import java.util.Base64; +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(Cluster.MAINNET); - - @Test - public void testProofOfConcept() { - log.info("Testing proof of concept"); - } + private final RpcClient client = new RpcClient("https://mainnet.helius-rpc.com/?api-key=a778b653-bdd6-41bc-8cda-0c7377faf1dd"); @Test public void testJupiterPerpPositionDeserialization() throws RpcException { @@ -28,21 +32,21 @@ public void testJupiterPerpPositionDeserialization() throws RpcException { // 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()); @@ -55,4 +59,61 @@ public void testJupiterPerpPositionDeserialization() throws RpcException { assertTrue(position.getCollateralUsd() > 0); // Add more assertions as needed } + + @Test + 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."); + } + } }