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; import java.util.Set;
/** /**
* Service for reading Jupiter Perps data and constructing and executing * Service for reading Jupiter Perps data and executing complete position
* Jupiter Perps transactions. * operations.
* *
* <p>This service does not sign transactions. A compatible wallet is * <p>For position mutations, the service constructs the required transaction
* required to sign a constructed transaction before it can be executed.</p> * and delegates signing and submission to the supplied wallet.</p>
*/ */
public interface JupiterPerpsService { public interface JupiterPerpsService {
/** /**
@@ -49,8 +49,8 @@ public interface JupiterPerpsService {
/** /**
* Opens or increases a Jupiter Perps position. * Opens or increases a Jupiter Perps position.
* *
* <p>The service constructs the transaction, asks the supplied wallet to * <p>The service constructs the transaction and asks the supplied wallet
* sign it, and submits it to Jupiter.</p> * to sign and submit it.</p>
* *
* @param wallet the wallet that owns or will own the position * @param wallet the wallet that owns or will own the position
* @param tradedTokenMint the mint address of the asset being traded * @param tradedTokenMint the mint address of the asset being traded
@@ -76,8 +76,8 @@ public interface JupiterPerpsService {
/** /**
* Decreases an existing Jupiter Perps position. * Decreases an existing Jupiter Perps position.
* *
* <p>The service constructs the transaction, asks the supplied wallet to * <p>The service constructs the transaction and asks the supplied wallet
* sign it, and submits it to Jupiter.</p> * to sign and submit it.</p>
* *
* @param wallet the wallet that owns the position * @param wallet the wallet that owns the position
* @param positionAccount the Jupiter Perps position to decrease * @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.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.protocol.DecreasePositionRequest; import com.r35157.libs.jupiter.perps.protocol.DecreasePositionRequest;
import com.r35157.libs.jupiter.perps.protocol.DecreasePositionResponse; 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.IncreasePositionRequest;
import com.r35157.libs.jupiter.perps.protocol.IncreasePositionResponse; import com.r35157.libs.jupiter.perps.protocol.IncreasePositionResponse;
import com.r35157.libs.jupiter.perps.protocol.TransactionMetadata; import com.r35157.libs.jupiter.perps.protocol.TransactionMetadata;
import com.r35157.libs.solana.SolanaAccountInfo; import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter; import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction; import com.r35157.libs.solana.SolanaUnsignedTransaction;
import com.r35157.libs.valuetypes.basic.MoneyAmount; import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes; import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes;
@@ -133,10 +130,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
maxSlippageBps maxSlippageBps
); );
SolanaSignedTransaction signedTransaction = return wallet.signAndSendTransaction(unsignedTransaction);
wallet.signTransaction(unsignedTransaction);
return executePositionIncreaseTransaction(signedTransaction);
} }
private @NotNull SolanaUnsignedTransaction buildPositionIncreaseTransaction( 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 @Override
public @NotNull ΩSolanaTransactionSignatureΩ executePositionDecrease( public @NotNull ΩSolanaTransactionSignatureΩ executePositionDecrease(
@NotNull SolanaWallet wallet, @NotNull SolanaWallet wallet,
@@ -235,10 +199,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
maxSlippageBps maxSlippageBps
); );
SolanaSignedTransaction signedTransaction = return wallet.signAndSendTransaction(unsignedTransaction);
wallet.signTransaction(unsignedTransaction);
return executePositionDecreaseTransaction(signedTransaction);
} }
private @NotNull SolanaUnsignedTransaction buildPositionDecreaseTransaction( 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( private static DecreasePositionRequest buildDecreasePositionRequest(
ΩJupiterPerpsPositionAccountΩ positionAccount, ΩJupiterPerpsPositionAccountΩ positionAccount,
ΩSPLMintAddressΩ receiveTokenMint, ΩSPLMintAddressΩ receiveTokenMint,
@@ -743,51 +674,11 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
return response; 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Ω BTC_MINT = "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh";
private static final ΩSPLMintAddressΩ ETH_MINT = "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"; private static final ΩSPLMintAddressΩ ETH_MINT = "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs";
private static final ΩSPLMintAddressΩ SOL_MINT = "So11111111111111111111111111111111111111112"; private static final ΩSPLMintAddressΩ SOL_MINT = "So11111111111111111111111111111111111111112";
private static final ΩSPLMintAddressΩ USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; 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 = private static final URI DECREASE_POSITION_ENDPOINT =
URI.create( URI.create(
"https://perps-api.jup.ag/v2/positions/decrease" "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
) {
}