diff --git a/pom.xml b/pom.xml index 23c3d14d..2ee91ad7 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ scm:git:git://github.com/skynetcap/solanaj.git scm:git:ssh://github.com/skynetcap/solanaj.git https://github.com/skynetcap/solanaj/tree/main - + Michael Morrell diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index 1c5bbda0..12b655a2 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -41,7 +41,7 @@ int getLength() { private String recentBlockhash; private AccountKeysList accountKeys; private List instructions; - private Account feePayer; + private PublicKey feePayerPublicKey; public Message() { this.accountKeys = new AccountKeysList(); @@ -143,8 +143,8 @@ public byte[] serialize() { return out.array(); } - protected void setFeePayer(Account feePayer) { - this.feePayer = feePayer; + protected void setFeePayerPublicKey(PublicKey feePayerPublicKey) { + this.feePayerPublicKey = feePayerPublicKey; } private List getAccountKeys() { @@ -159,7 +159,7 @@ private List getAccountKeys() { .collect(Collectors.toList()); } - int feePayerIndex = findAccountIndex(keysList, feePayer.getPublicKey()); + int feePayerIndex = findAccountIndex(keysList, feePayerPublicKey); List newList = new ArrayList(); AccountMeta feePayerMeta = keysList.get(feePayerIndex); newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index bab10710..4f03dce1 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.function.Function; import org.bitcoinj.core.Base58; import org.p2p.solanaj.utils.ShortvecEncoding; @@ -76,8 +77,7 @@ public void sign(List signers) { } Account feePayer = signers.get(0); - message.setFeePayer(feePayer); - + message.setFeePayerPublicKey(feePayer.getPublicKey()); serializedMessage = message.serialize(); for (Account signer : signers) { @@ -91,6 +91,29 @@ public void sign(List signers) { } } + /** + * Signs the transaction with external signer. + * + * @param feePayerPublicKey - The public key of the signer's account. + * @param externalSigner - Function for external sign. + * @throws IllegalArgumentException if no signers are provided + */ + public void signByExternalSigner(PublicKey feePayerPublicKey, Function externalSigner) { + if (externalSigner == null) { + throw new IllegalArgumentException("No external signer provided"); + } + + message.setFeePayerPublicKey(feePayerPublicKey); + serializedMessage = message.serialize(); + + try { + byte[] signature = externalSigner.apply(message.serialize()); + signatures.add(Base58.encode(signature)); + } catch (Exception e) { + throw new RuntimeException("Error signing transaction", e); // Improve exception handling + } + } + /** * Serializes the transaction into a byte array. * diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java index 71b06b76..4363f5a3 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java @@ -13,6 +13,7 @@ import org.p2p.solanaj.ws.listeners.NotificationEventListener; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; public class RpcApi { @@ -91,6 +92,37 @@ public String sendTransaction(Transaction transaction, List signers, St return client.call("sendTransaction", params, String.class); } + /** + * Sends a transaction to the RPC server with external signer + * + * @param transaction - The transaction to send. + * @param feePayerPublicKey - The public key of the signer's account. + * @param externalSigner - Function for external sign. + * @param recentBlockHash - The recent block hash. If null, it will be obtained from the RPC server. + * @param rpcSendTransactionConfig - The configuration object for sending transactions via RPC. + * @return The transaction ID as a string. + * @throws RpcException If an error occurs during the RPC call. + */ + public String sendTransaction(Transaction transaction, PublicKey feePayerPublicKey, + Function externalSigner, String recentBlockHash, + RpcSendTransactionConfig rpcSendTransactionConfig) throws RpcException { + if (recentBlockHash == null) { + recentBlockHash = getLatestBlockhash().getValue().getBlockhash(); + } + transaction.setRecentBlockHash(recentBlockHash); + transaction.signByExternalSigner(feePayerPublicKey, externalSigner); + byte[] serializedTransaction = transaction.serialize(); + + String base64Trx = Base64.getEncoder().encodeToString(serializedTransaction); + + List params = new ArrayList<>(); + + params.add(base64Trx); + params.add(rpcSendTransactionConfig); + + return client.call("sendTransaction", params, String.class); + } + /** * Sends a transaction to the network for processing. * A default RpcSendTransactionConfig is used. diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index 05db0fda..fc108813 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -19,7 +19,7 @@ public void serializeMessage() { Message message = new Message(); message.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); message.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - message.setFeePayer(signer); + message.setFeePayerPublicKey(signer.getPublicKey()); assertArrayEquals(new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117,