Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Jupiter DCA support #20

Merged
merged 8 commits into from
Oct 16, 2024
2 changes: 1 addition & 1 deletion bonfida/pom.xml
Original file line number Diff line number Diff line change
@@ -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>

4 changes: 2 additions & 2 deletions jupiter/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
@@ -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>
189 changes: 186 additions & 3 deletions jupiter/src/main/java/com/mmorrell/jupiter/manager/JupiterManager.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
package com.mmorrell.jupiter.manager;

import com.mmorrell.jupiter.model.*;
import com.mmorrell.jupiter.util.JupiterUtil;
import java.util.Collections;

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.*;
import java.time.Instant;
import java.util.stream.Collectors;

@Slf4j
public class JupiterManager {

private final RpcClient client;
private static final PublicKey JUPITER_PROGRAM_ID = new PublicKey("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu");
private static final PublicKey DCA_PROGRAM_ID = new PublicKey("DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M");
private static final int DCA_ACCOUNT_SIZE = 289; // Updated based on JupiterDca structure

public JupiterManager() {
this.client = new RpcClient(Cluster.MAINNET);
}


public JupiterManager(RpcClient client) {
this.client = client;
}
@@ -94,4 +104,177 @@ 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> getDcaAccounts() {
byte[] dcaDiscriminator = JupiterUtil.getAccountDiscriminator("Dca");

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

try {
List<ProgramAccount> accounts = client.getApi().getProgramAccounts(
DCA_PROGRAM_ID,
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;
} catch (RpcException ex) {
log.warn("Error fetching DCA accounts: {}", ex.getMessage());
return Collections.emptyList();
}
}

public List<JupiterDca> getDcaAccounts(PublicKey user) {
byte[] dcaDiscriminator = JupiterUtil.getAccountDiscriminator("Dca");

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

try {
List<ProgramAccount> accounts = client.getApi().getProgramAccounts(
DCA_PROGRAM_ID,
List.of(memCmpFilter, memCmpFilterUser),
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;
} catch (RpcException ex) {
log.warn("Error fetching DCA accounts: {}", ex.getMessage());
return Collections.emptyList();
}
}

public List<JupiterDca> getActiveDcaOrders() {
long now = Instant.now().getEpochSecond();
return getDcaAccounts().stream()
.filter(dca -> dca.getNextCycleAt() > now && dca.getInUsed() < dca.getInDeposited())
.collect(Collectors.toList());
}

public List<JupiterDca> getDcaOrdersByTokenPair(PublicKey inputMint, PublicKey outputMint) {
return getDcaAccounts().stream()
.filter(dca -> dca.getInputMint().equals(inputMint) && dca.getOutputMint().equals(outputMint))
.collect(Collectors.toList());
}

public double getAggregatedDcaVolume(PublicKey inputMint, PublicKey outputMint, long startTime, long endTime) {
return getDcaAccounts().stream()
.filter(dca -> dca.getInputMint().equals(inputMint)
&& dca.getOutputMint().equals(outputMint)
&& dca.getCreatedAt() >= startTime
&& dca.getCreatedAt() <= endTime)
.mapToDouble(dca -> (double) dca.getInDeposited() / Math.pow(10, 6))
.sum();
}

public List<Map.Entry<Map.Entry<PublicKey, PublicKey>, Long>> getMostPopularDcaPairs(int limit) {
Map<Map.Entry<PublicKey, PublicKey>, Long> pairCounts = getDcaAccounts().stream()
.collect(Collectors.groupingBy(
dca -> new AbstractMap.SimpleEntry<>(dca.getInputMint(), dca.getOutputMint()),
Collectors.counting()
));

return pairCounts.entrySet().stream()
.sorted(Map.Entry.<Map.Entry<PublicKey, PublicKey>, Long>comparingByValue().reversed())
.limit(limit)
.collect(Collectors.toList());
}

/**
* Retrieves completed Jupiter DCA orders.
* A DCA order is considered completed if it has fully utilized its deposited amount or has expired.
*
* @return a list of completed JupiterDca objects.
*/
public List<JupiterDca> getCompletedDcaOrders() {
long now = Instant.now().getEpochSecond();
return getDcaAccounts().stream()
.filter(dca -> dca.getInUsed() >= dca.getInDeposited() || dca.getNextCycleAt() <= now)
.collect(Collectors.toList());
}

/**
* Retrieves Jupiter DCA orders created within a specific time range.
*
* @param startTime the start epoch time.
* @param endTime the end epoch time.
* @return a list of JupiterDca objects within the specified time range.
*/
public List<JupiterDca> getDcaOrdersByTimeRange(long startTime, long endTime) {
return getDcaAccounts().stream()
.filter(dca -> dca.getCreatedAt() >= startTime && dca.getCreatedAt() <= endTime)
.collect(Collectors.toList());
}

/**
* Retrieves Jupiter DCA orders sorted by the total deposited amount in descending order.
*
* @return a list of JupiterDca objects sorted by volume.
*/
public List<JupiterDca> getDcaOrdersSortedByVolume() {
return getDcaAccounts().stream()
.sorted(Comparator.comparingLong(JupiterDca::getInDeposited).reversed())
.collect(Collectors.toList());
}

/**
* Retrieves aggregated statistics for a specific user's DCA activities.
*
* @param user the PublicKey of the user.
* @return a UserDcaStats object containing aggregated statistics.
*/
public JupiterUserDcaStats getUserDcaStatistics(PublicKey user) {
List<JupiterDca> userDcas = getDcaAccounts(user);

long totalOrders = userDcas.size();
double totalVolume = userDcas.stream()
.mapToDouble(dca -> (double) dca.getInDeposited() / Math.pow(10, 6))
.sum();
Set<PublicKey> uniqueInputTokens = userDcas.stream()
.map(JupiterDca::getInputMint)
.collect(Collectors.toSet());
Set<PublicKey> uniqueOutputTokens = userDcas.stream()
.map(JupiterDca::getOutputMint)
.collect(Collectors.toSet());

return new JupiterUserDcaStats(totalOrders, totalVolume, uniqueInputTokens, uniqueOutputTokens);
}


/**
* Retrieves the most recent Jupiter DCA orders up to the specified limit.
*
* @param limit the maximum number of recent orders to retrieve.
* @return a list of recent JupiterDca objects.
*/
public List<JupiterDca> getRecentDcaOrders(int limit) {
return getDcaAccounts().stream()
.sorted(Comparator.comparingLong(JupiterDca::getCreatedAt).reversed())
.limit(limit)
.collect(Collectors.toList());
}

// Additional methods can be added here as needed for other use cases.
}
Loading