Issue with Performing Token Swap via Serialized Transaction in Solana4J
Opened this issue · 0 comments
I am attempting to implement a token swap on Solana using the Jupiter API for quotes and serialized transactions. After receiving a base64-encoded transaction from Jupiter's API, I decode, sign, and try to send the transaction using Solana4J. However, the transaction fails, and I need assistance to ensure compatibility with Solana4J's transaction handling methods.
Steps Taken:
- Get Quote: Fetch a quote from Jupiter's /quote endpoint for swapping SOL to a specific SPL token.
- Serialized Transaction: Use the Jupiter /swap endpoint to obtain a base64-encoded transaction.
- Deserialize, Sign, and Send: Decode the base64 transaction.
- Create a new Transaction object in Solana4J.
- Sign the transaction using the user's private key.
- Attempt to send the transaction using sendTransaction.
Problem: The transaction consistently fails during deserialization and signing, with issues around setting the byte array and handling token mint authority.
Code Snippet: Below is the code segment used for deserializing, signing, and sending the transaction:
package com.tradingbot.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.p2p.solanaj.core.Account;
import org.p2p.solanaj.core.PublicKey;
import org.p2p.solanaj.core.Transaction;
import org.p2p.solanaj.programs.TokenProgram;
import org.p2p.solanaj.rpc.RpcClient;
import org.p2p.solanaj.rpc.types.config.Commitment;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
public class JupiterSwapService {
private static final String JUPITER_QUOTE_URL = "https://quote-api.jup.ag/v6/quote";
private static final String JUPITER_SWAP_URL = "https://quote-api.jup.ag/v6/swap";
private static final String INPUT_SOL_MINT = "So11111111111111111111111111111111111111112"; // SOL mint address
private final RpcClient solanaClient;
private final ObjectMapper objectMapper = new ObjectMapper();
private final HttpClient httpClient = HttpClient.newHttpClient();
public JupiterSwapService(RpcClient solanaClient) {
this.solanaClient = solanaClient;
}
// Step 1: Get a quote for the swap
private String getQuote(final double amountInSol, final double slippageBps, final String outputTokenMint) throws Exception {
long amountInLamports = (long) (amountInSol * 1e9); // SOL to lamports
String requestUrl = String.format(
"%s?inputMint=%s&outputMint=%s&amount=%d&slippageBps=%d",
JUPITER_QUOTE_URL, INPUT_SOL_MINT, outputTokenMint, amountInLamports, (int) (slippageBps * 100)
);
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(requestUrl))
.GET()
.header("Accept", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode responseJson = objectMapper.readTree(response.body());
return responseJson.toString(); // Return the full quote response as JSON string
} else {
throw new RuntimeException(STR."Failed to get quote: \{response.body()}");
}
}
// Step 2: Get the serialized transaction from Jupiter using the quote
private String getSerializedSwapTransaction(String quoteResponse, String userPublicKey) throws Exception {
String jsonPayload = String.format(
"{\"quoteResponse\": %s, \"userPublicKey\": \"%s\", \"wrapAndUnwrapSol\": true}",
quoteResponse, userPublicKey
);
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(JUPITER_SWAP_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode responseJson = objectMapper.readTree(response.body());
return responseJson.path("swapTransaction").asText(); // Get the base64-encoded transaction
} else {
throw new RuntimeException(STR."Failed to fetch swap transaction: \{response.body()}");
}
}
// Step 2: Deserialize, sign, and send the transaction
private String signAndSendTransaction(String base64Transaction, Account userAccount, String outputTokenMint) throws Exception {
// Step 2.2: Create a new Transaction object and set the bytes directly
Transaction transaction = new Transaction();
transaction.addInstruction(TokenProgram.transfer(PublicKey.valueOf(outputTokenMint), userAccount.getPublicKey(), 1000000, PublicKey.valueOf(userAccount.getPrivateKeyBase58())));
// Step 2.3: Set the recent blockhash
String latestBlockhash = solanaClient.getApi().getLatestBlockhash().getValue().getBlockhash();
transaction.setRecentBlockHash(latestBlockhash);
// Step 2.4: Sign the transaction with the user's account
transaction.sign(userAccount);
// Step 2.5: Serialize the signed transaction to base64 for sending
String base64SignedTransaction = Base64.getEncoder().encodeToString(transaction.serialize());
// Step 3: Send the signed transaction to Solana
String transactionSignature = solanaClient.getApi().sendTransaction(transaction, userAccount);
// Step 4: Confirm the transaction
solanaClient.getApi().getTransaction(transactionSignature, Commitment.CONFIRMED);
return transactionSignature;
}
// Main function to perform the swap
public String performSwap(double amountInSol, Account userAccount, final String outputTokenMint) throws Exception {
// Step 1: Get the quote
String quoteResponse = getQuote(amountInSol, 0.5, outputTokenMint); // 0.5% slippage
if (quoteResponse == null) {
throw new RuntimeException("Failed to get a valid quote from Jupiter.");
}
// Step 2: Get the serialized transaction using the quote
String userPublicKey = userAccount.getPublicKey().toBase58();
String serializedTransaction = getSerializedSwapTransaction(quoteResponse, userPublicKey);
if (serializedTransaction == null) {
throw new RuntimeException("Failed to get serialized swap transaction from Jupiter.");
}
// Step 3: Sign and send the transaction
return signAndSendTransaction(serializedTransaction, userAccount, outputTokenMint);
}
}
I am new in crypto so, please, don't judge me. The example provided by Jupiter which I follow:
https://station.jup.ag/docs/apis/swap-api