30: Submit signed transactions through the Solana API
This commit is contained in:
@@ -10,7 +10,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides read-oriented access to the Solana blockchain.
|
||||
* Provides generic access to the Solana blockchain.
|
||||
*
|
||||
* <p>This interface exposes the Solana operations needed by higher-level
|
||||
* integrations. It can fetch native SOL balances, SPL token holdings, NFT-like
|
||||
@@ -164,4 +164,22 @@ public interface SolanaBlockChain {
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||
) throws IOException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Submits a complete signed transaction to the Solana blockchain.
|
||||
*
|
||||
* <p>The transaction must already contain all required signatures.
|
||||
* This method does not sign or modify the transaction.</p>
|
||||
*
|
||||
* @param transaction the complete signed transaction to submit
|
||||
* @return the transaction signature returned by Solana RPC
|
||||
* @throws IllegalArgumentException if the serialized transaction is blank
|
||||
* @throws IOException if the transaction cannot be submitted or Solana
|
||||
* returns an RPC error
|
||||
* @throws InterruptedException if the calling thread is interrupted while
|
||||
* submitting the transaction
|
||||
*/
|
||||
ΩSolanaTransactionSignatureΩ sendTransaction(
|
||||
SolanaSignedTransaction transaction
|
||||
) throws IOException, InterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.r35157.libs.solana;
|
||||
|
||||
/**
|
||||
* A complete serialized Solana transaction containing all required signatures.
|
||||
*
|
||||
* @param serializedTransaction the signed transaction encoded as Base64
|
||||
*/
|
||||
public record SolanaSignedTransaction(
|
||||
ΩBase64StringΩ serializedTransaction
|
||||
) {
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.r35157.libs.solana.SolanaAccountInfo;
|
||||
import com.r35157.libs.solana.SolanaBlockChain;
|
||||
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
||||
import com.r35157.libs.solana.SolanaProgramAddressSeed;
|
||||
import com.r35157.libs.solana.SolanaSignedTransaction;
|
||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
@@ -163,6 +164,13 @@ public final class CachedSolanaBlockChain implements SolanaBlockChain {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ΩSolanaTransactionSignatureΩ sendTransaction(
|
||||
SolanaSignedTransaction transaction
|
||||
) throws IOException, InterruptedException {
|
||||
return delegate.sendTransaction(transaction);
|
||||
}
|
||||
|
||||
private synchronized Set<SolanaAccountInfo> loadProgramAccounts(
|
||||
ObjectCacheKey key,
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.r35157.libs.solana.impl.ref;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.r35157.libs.solana.*;
|
||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.CurrencyType;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -392,6 +394,91 @@ public class SolanaBlockChainImpl implements SolanaBlockChain {
|
||||
return Set.copyOf(accountInfos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ΩSolanaTransactionSignatureΩ sendTransaction(
|
||||
SolanaSignedTransaction transaction
|
||||
) throws IOException, InterruptedException {
|
||||
String jsonBody = createSendTransactionBody(transaction);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(RPC_URL))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = sendThrottled(request);
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
throw new IOException(
|
||||
"Solana sendTransaction RPC call failed: HTTP "
|
||||
+ response.statusCode()
|
||||
+ "\n"
|
||||
+ response.body()
|
||||
);
|
||||
}
|
||||
|
||||
JsonNode root = objectMapper.readTree(response.body());
|
||||
|
||||
JsonNode errorNode = root.get("error");
|
||||
|
||||
if (errorNode != null && !errorNode.isNull()) {
|
||||
throw new IOException(
|
||||
"Solana sendTransaction RPC error: "
|
||||
+ errorNode.toPrettyString()
|
||||
);
|
||||
}
|
||||
|
||||
JsonNode resultNode = root.get("result");
|
||||
|
||||
if (resultNode == null
|
||||
|| !resultNode.isTextual()
|
||||
|| resultNode.asText().isBlank()) {
|
||||
throw new IOException(
|
||||
"Solana sendTransaction response did not contain "
|
||||
+ "a transaction signature: "
|
||||
+ response.body()
|
||||
);
|
||||
}
|
||||
|
||||
ΩSolanaTransactionSignatureΩ signature = resultNode.asText();
|
||||
return signature;
|
||||
}
|
||||
|
||||
private String createSendTransactionBody(
|
||||
SolanaSignedTransaction transaction
|
||||
) throws IOException {
|
||||
if (transaction == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Signed transaction must not be null"
|
||||
);
|
||||
}
|
||||
|
||||
ΩBase64StringΩ serializedTransaction =
|
||||
transaction.serializedTransaction();
|
||||
|
||||
if (serializedTransaction == null
|
||||
|| serializedTransaction.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Serialized signed transaction must not be blank"
|
||||
);
|
||||
}
|
||||
|
||||
ObjectNode request = objectMapper.createObjectNode();
|
||||
request.put("jsonrpc", "2.0");
|
||||
request.put("id", 1);
|
||||
request.put("method", "sendTransaction");
|
||||
|
||||
ArrayNode params = request.putArray("params");
|
||||
params.add(serializedTransaction);
|
||||
|
||||
ObjectNode configuration = params.addObject();
|
||||
configuration.put("encoding", "base64");
|
||||
configuration.put("skipPreflight", false);
|
||||
configuration.put("preflightCommitment", "confirmed");
|
||||
|
||||
return objectMapper.writeValueAsString(request);
|
||||
}
|
||||
|
||||
private String createGetProgramAccountsBody(
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||
|
||||
Reference in New Issue
Block a user