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;
|
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
|
* <p>This interface exposes the Solana operations needed by higher-level
|
||||||
* integrations. It can fetch native SOL balances, SPL token holdings, NFT-like
|
* integrations. It can fetch native SOL balances, SPL token holdings, NFT-like
|
||||||
@@ -164,4 +164,22 @@ public interface SolanaBlockChain {
|
|||||||
ΩSolanaProgramIdΩ programId,
|
ΩSolanaProgramIdΩ programId,
|
||||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||||
) throws IOException, InterruptedException;
|
) 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.SolanaBlockChain;
|
||||||
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
||||||
import com.r35157.libs.solana.SolanaProgramAddressSeed;
|
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.SolanaProgramDerivedAddress;
|
||||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||||
@@ -163,6 +164,13 @@ public final class CachedSolanaBlockChain implements SolanaBlockChain {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ΩSolanaTransactionSignatureΩ sendTransaction(
|
||||||
|
SolanaSignedTransaction transaction
|
||||||
|
) throws IOException, InterruptedException {
|
||||||
|
return delegate.sendTransaction(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized Set<SolanaAccountInfo> loadProgramAccounts(
|
private synchronized Set<SolanaAccountInfo> loadProgramAccounts(
|
||||||
ObjectCacheKey key,
|
ObjectCacheKey key,
|
||||||
ΩSolanaProgramIdΩ programId,
|
ΩSolanaProgramIdΩ programId,
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package com.r35157.libs.solana.impl.ref;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.*;
|
||||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
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.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||||
import com.r35157.libs.valuetypes.basic.CurrencyType;
|
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.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -392,6 +394,91 @@ public class SolanaBlockChainImpl implements SolanaBlockChain {
|
|||||||
return Set.copyOf(accountInfos);
|
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(
|
private String createGetProgramAccountsBody(
|
||||||
ΩSolanaProgramIdΩ programId,
|
ΩSolanaProgramIdΩ programId,
|
||||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||||
|
|||||||
Reference in New Issue
Block a user