49: Bind Jupiter position services to wallets

This commit is contained in:
2026-08-05 10:12:39 +02:00
parent 8189b16516
commit a65a9ed043
4 changed files with 10 additions and 147 deletions
@@ -9,11 +9,11 @@ import java.math.BigDecimal;
import java.util.Set;
/**
* Service for reading Jupiter Perps data and constructing and executing
* Jupiter Perps transactions.
* Service for reading Jupiter Perps data and executing complete position
* operations.
*
* <p>This service does not sign transactions. A compatible wallet is
* required to sign a constructed transaction before it can be executed.</p>
* <p>For position mutations, the service constructs the required transaction
* and delegates signing and submission to the supplied wallet.</p>
*/
public interface JupiterPerpsService {
/**
@@ -49,8 +49,8 @@ public interface JupiterPerpsService {
/**
* Opens or increases a Jupiter Perps position.
*
* <p>The service constructs the transaction, asks the supplied wallet to
* sign it, and submits it to Jupiter.</p>
* <p>The service constructs the transaction and asks the supplied wallet
* to sign and submit it.</p>
*
* @param wallet the wallet that owns or will own the position
* @param tradedTokenMint the mint address of the asset being traded
@@ -76,8 +76,8 @@ public interface JupiterPerpsService {
/**
* Decreases an existing Jupiter Perps position.
*
* <p>The service constructs the transaction, asks the supplied wallet to
* sign it, and submits it to Jupiter.</p>
* <p>The service constructs the transaction and asks the supplied wallet
* to sign and submit it.</p>
*
* @param wallet the wallet that owns the position
* @param positionAccount the Jupiter Perps position to decrease
@@ -7,15 +7,12 @@ import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.protocol.DecreasePositionRequest;
import com.r35157.libs.jupiter.perps.protocol.DecreasePositionResponse;
import com.r35157.libs.jupiter.perps.protocol.ExecuteTransactionRequest;
import com.r35157.libs.jupiter.perps.protocol.ExecuteTransactionResponse;
import com.r35157.libs.jupiter.perps.protocol.IncreasePositionRequest;
import com.r35157.libs.jupiter.perps.protocol.IncreasePositionResponse;
import com.r35157.libs.jupiter.perps.protocol.TransactionMetadata;
import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes;
@@ -133,10 +130,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
maxSlippageBps
);
SolanaSignedTransaction signedTransaction =
wallet.signTransaction(unsignedTransaction);
return executePositionIncreaseTransaction(signedTransaction);
return wallet.signAndSendTransaction(unsignedTransaction);
}
private @NotNull SolanaUnsignedTransaction buildPositionIncreaseTransaction(
@@ -189,36 +183,6 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
}
}
private @NotNull ΩSolanaTransactionSignatureΩ
executePositionIncreaseTransaction(
@NotNull SolanaSignedTransaction transaction
) throws IOException, InterruptedException {
if (transaction.serializedTransaction() == null
|| transaction.serializedTransaction().isBlank()) {
throw new IllegalArgumentException(
"Signed transaction must not be blank"
);
}
ExecuteTransactionRequest request =
new ExecuteTransactionRequest(
"increase-position",
transaction.serializedTransaction()
);
ExecuteTransactionResponse response =
requestTransactionExecution(request);
if (response.txid() == null || response.txid().isBlank()) {
throw new IOException(
"Jupiter transaction execution response did not "
+ "contain a transaction signature"
);
}
return response.txid();
}
@Override
public @NotNull ΩSolanaTransactionSignatureΩ executePositionDecrease(
@NotNull SolanaWallet wallet,
@@ -235,10 +199,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
maxSlippageBps
);
SolanaSignedTransaction signedTransaction =
wallet.signTransaction(unsignedTransaction);
return executePositionDecreaseTransaction(signedTransaction);
return wallet.signAndSendTransaction(unsignedTransaction);
}
private @NotNull SolanaUnsignedTransaction buildPositionDecreaseTransaction(
@@ -287,36 +248,6 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
}
}
private @NotNull ΩSolanaTransactionSignatureΩ
executePositionDecreaseTransaction(
@NotNull SolanaSignedTransaction transaction
) throws IOException, InterruptedException {
if (transaction.serializedTransaction() == null
|| transaction.serializedTransaction().isBlank()) {
throw new IllegalArgumentException(
"Signed transaction must not be blank"
);
}
ExecuteTransactionRequest request =
new ExecuteTransactionRequest(
"decrease-position",
transaction.serializedTransaction()
);
ExecuteTransactionResponse response =
requestTransactionExecution(request);
if (response.txid() == null || response.txid().isBlank()) {
throw new IOException(
"Jupiter transaction execution response did not "
+ "contain a transaction signature"
);
}
return response.txid();
}
private static DecreasePositionRequest buildDecreasePositionRequest(
ΩJupiterPerpsPositionAccountΩ positionAccount,
ΩSPLMintAddressΩ receiveTokenMint,
@@ -743,51 +674,11 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
return response;
}
private static ExecuteTransactionResponse requestTransactionExecution(
ExecuteTransactionRequest request
) throws IOException, InterruptedException {
String requestJson =
OBJECT_MAPPER.writeValueAsString(request);
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(EXECUTE_TRANSACTION_ENDPOINT)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("x-client-platform", "nenjim-hub")
.POST(HttpRequest.BodyPublishers.ofString(requestJson))
.build();
HttpResponse<String> httpResponse = HTTP_CLIENT.send(
httpRequest,
HttpResponse.BodyHandlers.ofString()
);
if (httpResponse.statusCode() < 200
|| httpResponse.statusCode() >= 300) {
throw new IOException(
"Jupiter transaction execution failed with HTTP "
+ httpResponse.statusCode()
+ ": "
+ httpResponse.body()
);
}
return OBJECT_MAPPER.readValue(
httpResponse.body(),
ExecuteTransactionResponse.class
);
}
private static final ΩSPLMintAddressΩ BTC_MINT = "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh";
private static final ΩSPLMintAddressΩ ETH_MINT = "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs";
private static final ΩSPLMintAddressΩ SOL_MINT = "So11111111111111111111111111111111111111112";
private static final ΩSPLMintAddressΩ USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
private static final URI EXECUTE_TRANSACTION_ENDPOINT =
URI.create(
"https://perps-api.jup.ag/v2/transaction/execute"
);
private static final URI DECREASE_POSITION_ENDPOINT =
URI.create(
"https://perps-api.jup.ag/v2/positions/decrease"
@@ -1,14 +0,0 @@
package com.r35157.libs.jupiter.perps.protocol;
/**
* Jupiter Perps protocol request for executing a signed transaction.
*
* @param action the Jupiter transaction action
* @param serializedTxBase64 the complete signed Solana transaction,
* encoded as Base64
*/
public record ExecuteTransactionRequest(
String action,
String serializedTxBase64
) {
}
@@ -1,14 +0,0 @@
package com.r35157.libs.jupiter.perps.protocol;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* Relevant fields returned by Jupiter after executing a transaction.
*
* @param txid the Solana transaction signature
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ExecuteTransactionResponse(
String txid
) {
}