Skip to content

Commit

Permalink
Add Jupiter DCA model and enhance related functionalities
Browse files Browse the repository at this point in the history
Introduce the `JupiterDca` model to represent Dollar-Cost Averaging accounts. Added methods for deserialization and retrieving all DCA accounts. Updated Maven version across various modules to 1.33.0-SNAPSHOT. Enhanced `OpenBookManager` and `JupiterManager` with new methods and constants for improved functionality.
  • Loading branch information
skynetcap committed Oct 2, 2024
1 parent 3f492ff commit 59d378d
Show file tree
Hide file tree
Showing 16 changed files with 270 additions and 40 deletions.
2 changes: 1 addition & 1 deletion bonfida/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>solanaj-programs</artifactId>
<groupId>com.mmorrell</groupId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
4 changes: 2 additions & 2 deletions jupiter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.mmorrell</groupId>
<artifactId>solanaj-programs</artifactId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
</parent>

<artifactId>jupiter</artifactId>
Expand All @@ -20,7 +20,7 @@
<dependency>
<groupId>com.mmorrell</groupId>
<artifactId>openbook</artifactId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package com.mmorrell.jupiter.manager;

import com.mmorrell.jupiter.model.*;
import com.mmorrell.jupiter.util.JupiterUtil;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Base58;
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.util.Optional;
import java.util.*;

@Slf4j
public class JupiterManager {

private final RpcClient client;
private static final PublicKey JUPITER_PROGRAM_ID = new PublicKey("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu");
private static final String DCA_PROGRAM_ID = "DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M"; // Replace with actual DCA Program ID
private static final int DCA_ACCOUNT_SIZE = 289; // Updated based on JupiterDca structure

public JupiterManager() {
this.client = new RpcClient(Cluster.MAINNET);
Expand Down Expand Up @@ -94,4 +99,34 @@ public Optional<JupiterPerpetuals> getPerpetuals(PublicKey perpetualsPublicKey)
return Optional.empty();
}
}
}

/**
* Retrieves all Jupiter DCA accounts.
*
* @return a list of JupiterDca objects.
* @throws RpcException if the RPC call fails.
*/
public List<JupiterDca> getAllDcaAccounts() throws RpcException {
PublicKey programId = new PublicKey(DCA_PROGRAM_ID);

byte[] dcaDiscriminator = JupiterUtil.getAccountDiscriminator("Dca");

// Create a memcmp filter for the discriminator at offset 0
Memcmp memCmpFilter = new Memcmp(0, Base58.encode(dcaDiscriminator));

List<ProgramAccount> accounts = client.getApi().getProgramAccounts(
programId,
List.of(memCmpFilter),
DCA_ACCOUNT_SIZE
);

List<JupiterDca> dcaAccounts = new ArrayList<>();
for (ProgramAccount account : accounts) {
byte[] data = account.getAccount().getDecodedData();
JupiterDca dca = JupiterDca.fromByteArray(data);
dcaAccounts.add(dca);
}

return dcaAccounts;
}
}
149 changes: 149 additions & 0 deletions jupiter/src/main/java/com/mmorrell/jupiter/model/JupiterDca.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
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 DCA (Dollar-Cost Averaging) account.
*/
@Data
@Builder
public class JupiterDca {
private PublicKey user;
private PublicKey inputMint;
private PublicKey outputMint;
private long idx;
private long nextCycleAt;
private long inDeposited;
private long inWithdrawn;
private long outWithdrawn;
private long inUsed;
private long outReceived;
private long inAmountPerCycle;
private long cycleFrequency;
private long nextCycleAmountLeft;
private PublicKey inAccount;
private PublicKey outAccount;
private long minOutAmount;
private long maxOutAmount;
private long keeperInBalanceBeforeBorrow;
private long dcaOutBalanceBeforeSwap;
private long createdAt;
private byte bump;

/**
* Deserializes a byte array into a JupiterDca object.
*
* @param data the byte array to deserialize.
* @return the deserialized JupiterDca object.
*/
public static JupiterDca fromByteArray(byte[] data) {
int offset = 8;

PublicKey user = PublicKey.readPubkey(data, offset);
offset += 32;

PublicKey inputMint = PublicKey.readPubkey(data, offset);
offset += 32;

PublicKey outputMint = PublicKey.readPubkey(data, offset);
offset += 32;

long idx = readLong(data, offset);
offset += 8;

long nextCycleAt = readLong(data, offset);
offset += 8;

long inDeposited = readLong(data, offset);
offset += 8;

long inWithdrawn = readLong(data, offset);
offset += 8;

long outWithdrawn = readLong(data, offset);
offset += 8;

long inUsed = readLong(data, offset);
offset += 8;

long outReceived = readLong(data, offset);
offset += 8;

long inAmountPerCycle = readLong(data, offset);
offset += 8;

long cycleFrequency = readLong(data, offset);
offset += 8;

long nextCycleAmountLeft = readLong(data, offset);
offset += 8;

PublicKey inAccount = PublicKey.readPubkey(data, offset);
offset += 32;

PublicKey outAccount = PublicKey.readPubkey(data, offset);
offset += 32;

long minOutAmount = readLong(data, offset);
offset += 8;

long maxOutAmount = readLong(data, offset);
offset += 8;

long keeperInBalanceBeforeBorrow = readLong(data, offset);
offset += 8;

long dcaOutBalanceBeforeSwap = readLong(data, offset);
offset += 8;

long createdAt = readLong(data, offset);
offset += 8;

byte bump = data[offset];

return JupiterDca.builder()
.user(user)
.inputMint(inputMint)
.outputMint(outputMint)
.idx(idx)
.nextCycleAt(nextCycleAt)
.inDeposited(inDeposited)
.inWithdrawn(inWithdrawn)
.outWithdrawn(outWithdrawn)
.inUsed(inUsed)
.outReceived(outReceived)
.inAmountPerCycle(inAmountPerCycle)
.cycleFrequency(cycleFrequency)
.nextCycleAmountLeft(nextCycleAmountLeft)
.inAccount(inAccount)
.outAccount(outAccount)
.minOutAmount(minOutAmount)
.maxOutAmount(maxOutAmount)
.keeperInBalanceBeforeBorrow(keeperInBalanceBeforeBorrow)
.dcaOutBalanceBeforeSwap(dcaOutBalanceBeforeSwap)
.createdAt(createdAt)
.bump(bump)
.build();
}

/**
* Reads a long value from the byte array at the specified offset.
*
* @param data the byte array.
* @param offset the offset to start reading from.
* @return the long value.
*/
private static long readLong(byte[] data, int offset) {
return ((long) (data[offset] & 0xFF)) |
(((long) (data[offset + 1] & 0xFF)) << 8) |
(((long) (data[offset + 2] & 0xFF)) << 16) |
(((long) (data[offset + 3] & 0xFF)) << 24) |
(((long) (data[offset + 4] & 0xFF)) << 32) |
(((long) (data[offset + 5] & 0xFF)) << 40) |
(((long) (data[offset + 6] & 0xFF)) << 48) |
(((long) (data[offset + 7] & 0xFF)) << 56);
}
}
22 changes: 22 additions & 0 deletions jupiter/src/main/java/com/mmorrell/jupiter/util/JupiterUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import org.p2p.solanaj.core.PublicKey;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class JupiterUtil {
public static int readUint32(byte[] data, int offset) {
return (data[offset] & 0xFF) |
Expand Down Expand Up @@ -32,4 +37,21 @@ public static PublicKey readOptionalPublicKey(byte[] data, int offset) {
boolean hasValue = data[offset] != 0;
return hasValue ? PublicKey.readPubkey(data, offset + 1) : null;
}

/**
* 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:<accountName>".
*/
public static 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.");
}
}
}
58 changes: 38 additions & 20 deletions jupiter/src/test/java/JupiterTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import com.google.common.io.Files;
import com.mmorrell.jupiter.manager.JupiterManager;
import com.mmorrell.jupiter.model.*;
import com.mmorrell.jupiter.util.JupiterUtil;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Base58;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -23,7 +24,7 @@
import static org.junit.jupiter.api.Assertions.*;

/**
* Test class for Jupiter Perpetuals positions.
* Test class for Jupiter Perpetuals positions and DCA accounts.
*/
@Slf4j
public class JupiterTest {
Expand Down Expand Up @@ -79,7 +80,7 @@ public void testGetAllJupiterPerpPositions() throws RpcException {
PublicKey programId = new PublicKey("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu");

// Get the discriminator for the Position account
byte[] positionDiscriminator = getAccountDiscriminator("Position");
byte[] positionDiscriminator = JupiterUtil.getAccountDiscriminator("Position");

// Create a memcmp filter for the discriminator at offset 0
Memcmp memcmpFilter = new Memcmp(0, Base58.encode(positionDiscriminator));
Expand Down Expand Up @@ -130,23 +131,6 @@ public void testGetAllJupiterPerpPositions() throws RpcException {
}
}

/**
* 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:<accountName>".
*/
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");
Expand Down Expand Up @@ -336,4 +320,38 @@ public void testJupiterManagerWithInvalidPublicKeys() {
assertFalse(manager.getPositionRequest(invalidPublicKey).isPresent());
assertFalse(manager.getPerpetuals(invalidPublicKey).isPresent());
}
}

@Test
public void testGetAllJupiterDcaAccounts() {
JupiterManager manager = new JupiterManager(client);
try {
List<JupiterDca> dcaAccounts = manager.getAllDcaAccounts();
assertNotNull(dcaAccounts, "DCA accounts list should not be null");
assertTrue(dcaAccounts.size() > 0, "DCA accounts list should contain at least one account");

for (JupiterDca dca : dcaAccounts) {
assertNotNull(dca.getUser(), "DCA user should not be null");
assertNotNull(dca.getInputMint(), "DCA inputMint should not be null");
assertNotNull(dca.getOutputMint(), "DCA outputMint should not be null");
assertTrue(dca.getNextCycleAt() > 0, "DCA nextCycleAt should be greater than 0");
assertTrue(dca.getOutWithdrawn() >= 0, "DCA outWithdrawn should be non-negative");
assertTrue(dca.getInUsed() >= 0, "DCA inUsed should be non-negative");
assertTrue(dca.getOutReceived() >= 0, "DCA outReceived should be non-negative");
assertTrue(dca.getInAmountPerCycle() > 0, "DCA inAmountPerCycle should be greater than 0");
assertTrue(dca.getCycleFrequency() > 0, "DCA cycleFrequency should be greater than 0");
assertTrue(dca.getNextCycleAmountLeft() >= 0, "DCA nextCycleAmountLeft should be non-negative");
assertNotNull(dca.getInAccount(), "DCA inAccount should not be null");
assertNotNull(dca.getOutAccount(), "DCA outAccount should not be null");
assertTrue(dca.getCreatedAt() > 0, "DCA createdAt should be greater than 0");
}

// Log the retrieved DCA accounts
for (JupiterDca dca : dcaAccounts) {
log.info("JupiterDca: {}", dca);
}

} catch (RpcException e) {
fail("RPC Exception occurred: " + e.getMessage());
}
}
}
2 changes: 1 addition & 1 deletion magiceden/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>solanaj-programs</artifactId>
<groupId>com.mmorrell</groupId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion mango/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>solanaj-programs</artifactId>
<groupId>com.mmorrell</groupId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion metaplex/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>solanaj-programs</artifactId>
<groupId>com.mmorrell</groupId>
<version>1.32.0-SNAPSHOT</version>
<version>1.33.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Loading

0 comments on commit 59d378d

Please sign in to comment.