From 500c98a42428f1036fbdecdc82c0ec93003c8c8d Mon Sep 17 00:00:00 2001 From: Chintan Patel Date: Fri, 16 Aug 2024 18:28:05 -0500 Subject: [PATCH] =?UTF-8?q?JUP-setup=20jupiter=20program=20to=20swap=20sol?= =?UTF-8?q?ana=20tokens=20and=20added=20functionali=E2=80=A6=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JUP-setup jupiter program to swap solana tokens and added functionality to deserialize transactions * Adding a capability to call getSignaturesForAddress rpc call with before as the parameter --------- Co-authored-by: Chintan Patel --- docs/README.md | 14 +- pom.xml | 14 +- .../java/org/p2p/solanaj/core/Account.java | 2 +- .../org/p2p/solanaj/core/AccountKeysList.java | 36 +-- .../org/p2p/solanaj/core/AccountMeta.java | 9 + .../org/p2p/solanaj/core/LegacyMessage.java | 170 ++++++++++++ .../p2p/solanaj/core/LegacyTransaction.java | 76 ++++++ .../core/LegacyTransactionBuilder.java | 35 +++ .../java/org/p2p/solanaj/core/Message.java | 251 ++++++++++++++---- .../java/org/p2p/solanaj/core/PublicKey.java | 19 +- .../org/p2p/solanaj/core/Transaction.java | 58 +++- .../p2p/solanaj/core/TransactionBuilder.java | 35 --- .../solanaj/programs/JupiterSwapProgram.java | 110 ++++++++ .../p2p/solanaj/programs/SystemProgram.java | 6 +- .../anchor/AnchorBasicTutorialProgram.java | 2 +- .../java/org/p2p/solanaj/rpc/Cluster.java | 8 +- .../p2p/solanaj/rpc/LoggingInterceptor.java | 2 + src/main/java/org/p2p/solanaj/rpc/RpcApi.java | 130 +++++++-- .../java/org/p2p/solanaj/rpc/RpcClient.java | 1 - .../org/p2p/solanaj/rpc/RpcException.java | 3 + .../solanaj/rpc/types/BlockProduction.java | 5 +- .../p2p/solanaj/rpc/types/ConfirmedBlock.java | 1 - .../rpc/types/ConfirmedSignFAddr2.java | 10 +- .../p2p/solanaj/rpc/types/ProgramAccount.java | 14 +- .../org/p2p/solanaj/rpc/types/RpcRequest.java | 8 +- .../org/p2p/solanaj/rpc/types/Supply.java | 1 - .../solanaj/rpc/types/config/Commitment.java | 6 +- .../types/config/ProgramAccountConfig.java | 12 +- .../config/RpcSendTransactionConfig.java | 4 +- .../org/p2p/solanaj/token/TokenManager.java | 30 +-- .../java/org/p2p/solanaj/utils/ByteUtils.java | 18 ++ .../p2p/solanaj/utils/GuardedArrayUtils.java | 42 +++ .../{ShortvecEncoding.java => Shortvec.java} | 18 +- .../org/p2p/solanaj/utils/TweetNaclFast.java | 56 ++-- .../solanaj/utils/bip32/wallet/HdAddress.java | 15 +- .../utils/bip32/wallet/SolanaBip44.java | 13 +- .../utils/bip32/wallet/SolanaCoin.java | 40 ++- .../solanaj/utils/bip32/wallet/key/HdKey.java | 46 +--- .../utils/bip32/wallet/key/HdPrivateKey.java | 12 +- .../utils/bip32/wallet/key/HdPublicKey.java | 12 +- .../utils/bip32/wallet/key/SolanaCurve.java | 6 +- .../p2p/solanaj/ws/SignatureNotification.java | 9 +- .../ws/SubscriptionWebSocketClient.java | 16 +- .../AccountNotificationEventListener.java | 1 - .../LogNotificationEventListener.java | 3 +- .../java/org/p2p/solanaj/core/AnchorTest.java | 8 +- ...essageTest.java => LegacyMessageTest.java} | 12 +- ...onTest.java => LegacyTransactionTest.java} | 16 +- .../org/p2p/solanaj/core/MainnetTest.java | 12 +- .../p2p/solanaj/programs/BPFLoaderTest.java | 12 +- .../solanaj/utils/ShortvecEncodingTest.java | 21 -- .../org/p2p/solanaj/utils/ShortvecTest.java | 21 ++ 52 files changed, 1061 insertions(+), 420 deletions(-) create mode 100644 src/main/java/org/p2p/solanaj/core/LegacyMessage.java create mode 100644 src/main/java/org/p2p/solanaj/core/LegacyTransaction.java create mode 100644 src/main/java/org/p2p/solanaj/core/LegacyTransactionBuilder.java delete mode 100644 src/main/java/org/p2p/solanaj/core/TransactionBuilder.java create mode 100644 src/main/java/org/p2p/solanaj/programs/JupiterSwapProgram.java create mode 100644 src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java rename src/main/java/org/p2p/solanaj/utils/{ShortvecEncoding.java => Shortvec.java} (62%) rename src/test/java/org/p2p/solanaj/core/{MessageTest.java => LegacyMessageTest.java} (79%) rename src/test/java/org/p2p/solanaj/core/{TransactionTest.java => LegacyTransactionTest.java} (76%) delete mode 100644 src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java create mode 100644 src/test/java/org/p2p/solanaj/utils/ShortvecTest.java diff --git a/docs/README.md b/docs/README.md index 67d288be..bc166229 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,10 +51,10 @@ int lamports = 3000; Account signer = new Account(secret_key); -Transaction transaction = new Transaction(); -transaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); +Transaction legacyTransaction = new Transaction(); +legacyTransaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); -String signature = client.getApi().sendTransaction(transaction, signer); +String signature = client.getApi().sendTransaction(legacyTransaction, signer); ``` ##### Get balance @@ -77,18 +77,18 @@ final Market solUsdcMarket = new MarketBuilder() final OrderBook bids = solUsdcMarket.getBidOrderBook(); ``` -##### Send a transaction with call to the "Memo" program +##### Send a legacyTransaction with call to the "Memo" program ```java // Create account from private key final Account feePayer = new Account(Base58.decode(new String(data))); -final Transaction transaction = new Transaction(); +final Transaction legacyTransaction = new Transaction(); // Add instruction to write memo -transaction.addInstruction( +legacyTransaction.addInstruction( MemoProgram.writeUtf8(feePayer.getPublicKey(),"Hello from SolanaJ :)") ); -String response = result = client.getApi().sendTransaction(transaction, feePayer); +String response = result = client.getApi().sendTransaction(legacyTransaction, feePayer); ``` ## License diff --git a/pom.xml b/pom.xml index 66d2d5d2..ce09ae83 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,18 @@ jackson-databind 2.17.2 + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2.1 + + + + org.apache.commons + commons-lang3 + 3.15.0 + @@ -186,7 +198,7 @@ sign - c:/Users/Michael/.gnupg/ + /Users/chintan_mbp/.gnupg/ 0x27FAE7D2 diff --git a/src/main/java/org/p2p/solanaj/core/Account.java b/src/main/java/org/p2p/solanaj/core/Account.java index cc41eb67..54095897 100644 --- a/src/main/java/org/p2p/solanaj/core/Account.java +++ b/src/main/java/org/p2p/solanaj/core/Account.java @@ -10,7 +10,7 @@ import org.p2p.solanaj.utils.bip32.wallet.DerivableType; public class Account { - private TweetNaclFast.Signature.KeyPair keyPair; + private final TweetNaclFast.Signature.KeyPair keyPair; public Account() { this.keyPair = TweetNaclFast.Signature.keyPair(); diff --git a/src/main/java/org/p2p/solanaj/core/AccountKeysList.java b/src/main/java/org/p2p/solanaj/core/AccountKeysList.java index d4608a36..8dc448f1 100644 --- a/src/main/java/org/p2p/solanaj/core/AccountKeysList.java +++ b/src/main/java/org/p2p/solanaj/core/AccountKeysList.java @@ -6,10 +6,10 @@ import java.util.HashMap; public class AccountKeysList { - private HashMap accounts; + private final HashMap accounts; public AccountKeysList() { - accounts = new HashMap(); + accounts = new HashMap<>(); } public void add(AccountMeta accountMeta) { @@ -31,29 +31,31 @@ public void addAll(Collection metas) { } public ArrayList getList() { - ArrayList accountKeysList = new ArrayList(accounts.values()); + ArrayList accountKeysList = new ArrayList<>(accounts.values()); accountKeysList.sort(metaComparator); return accountKeysList; } - private static final Comparator metaComparator = new Comparator() { + private static final Comparator metaComparator = (am1, am2) -> { - @Override - public int compare(AccountMeta am1, AccountMeta am2) { - - int cmpSigner = am1.isSigner() == am2.isSigner() ? 0 : am1.isSigner() ? -1 : 1; - if (cmpSigner != 0) { - return cmpSigner; - } - - int cmpkWritable = am1.isWritable() == am2.isWritable() ? 0 : am1.isWritable() ? -1 : 1; - if (cmpkWritable != 0) { - return cmpkWritable; - } + int cmpSigner = am1.isSigner() == am2.isSigner() ? 0 : am1.isSigner() ? -1 : 1; + if (cmpSigner != 0) { + return cmpSigner; + } - return Integer.compare(cmpSigner, cmpkWritable); + int cmpkWritable = am1.isWritable() == am2.isWritable() ? 0 : am1.isWritable() ? -1 : 1; + if (cmpkWritable != 0) { + return cmpkWritable; } + + return Integer.compare(cmpSigner, cmpkWritable); }; + @Override + public String toString() { + return "AccountKeysList{" + + "accounts=" + accounts + + '}'; + } } diff --git a/src/main/java/org/p2p/solanaj/core/AccountMeta.java b/src/main/java/org/p2p/solanaj/core/AccountMeta.java index 58d47146..18cd0720 100644 --- a/src/main/java/org/p2p/solanaj/core/AccountMeta.java +++ b/src/main/java/org/p2p/solanaj/core/AccountMeta.java @@ -12,4 +12,13 @@ public class AccountMeta { private boolean isSigner; private boolean isWritable; + + @Override + public String toString() { + return "AccountMeta{" + + "publicKey=" + publicKey + + ", isSigner=" + isSigner + + ", isWritable=" + isWritable + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/core/LegacyMessage.java b/src/main/java/org/p2p/solanaj/core/LegacyMessage.java new file mode 100644 index 00000000..43ccacee --- /dev/null +++ b/src/main/java/org/p2p/solanaj/core/LegacyMessage.java @@ -0,0 +1,170 @@ +package org.p2p.solanaj.core; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.bitcoinj.core.Base58; + +import org.p2p.solanaj.utils.Shortvec; + +public class LegacyMessage { + private static class MessageHeader { + static final int HEADER_LENGTH = 3; + + byte numRequiredSignatures = 0; + byte numReadonlySignedAccounts = 0; + byte numReadonlyUnsignedAccounts = 0; + + byte[] toByteArray() { + return new byte[] { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts }; + } + } + + private static class CompiledInstruction { + byte programIdIndex; + byte[] keyIndicesCount; + byte[] keyIndices; + byte[] dataLength; + byte[] data; + + int getLength() { + // 1 = programIdIndex length + return 1 + keyIndicesCount.length + keyIndices.length + dataLength.length + data.length; + } + } + + private static final int RECENT_BLOCK_HASH_LENGTH = 32; + + private String recentBlockhash; + private final AccountKeysList accountKeys; + private final List instructions; + private Account feePayer; + + public LegacyMessage() { + this.accountKeys = new AccountKeysList(); + this.instructions = new ArrayList<>(); + } + + public LegacyMessage addInstruction(TransactionInstruction instruction) { + accountKeys.addAll(instruction.getKeys()); + accountKeys.add(new AccountMeta(instruction.getProgramId(), false, false)); + instructions.add(instruction); + + return this; + } + + public void setRecentBlockHash(String recentBlockhash) { + this.recentBlockhash = recentBlockhash; + } + + public byte[] serialize() { + + if (recentBlockhash == null) { + throw new IllegalArgumentException("recentBlockhash required"); + } + + if (instructions.isEmpty()) { + throw new IllegalArgumentException("No instructions provided"); + } + + MessageHeader messageHeader = new MessageHeader(); + + List keysList = getAccountKeys(); + int accountKeysSize = keysList.size(); + + byte[] accountAddressesLength = Shortvec.encodeLength(accountKeysSize); + + int compiledInstructionsLength = 0; + List compiledInstructions = new ArrayList<>(); + + for (TransactionInstruction instruction : instructions) { + int keysSize = instruction.getKeys().size(); + + byte[] keyIndices = new byte[keysSize]; + for (int i = 0; i < keysSize; i++) { + keyIndices[i] = (byte) findAccountIndex(keysList, instruction.getKeys().get(i).getPublicKey()); + } + + CompiledInstruction compiledInstruction = new CompiledInstruction(); + compiledInstruction.programIdIndex = (byte) findAccountIndex(keysList, instruction.getProgramId()); + compiledInstruction.keyIndicesCount = Shortvec.encodeLength(keysSize); + compiledInstruction.keyIndices = keyIndices; + compiledInstruction.dataLength = Shortvec.encodeLength(instruction.getData().length); + compiledInstruction.data = instruction.getData(); + + compiledInstructions.add(compiledInstruction); + + compiledInstructionsLength += compiledInstruction.getLength(); + } + + byte[] instructionsLength = Shortvec.encodeLength(compiledInstructions.size()); + + int bufferSize = MessageHeader.HEADER_LENGTH + RECENT_BLOCK_HASH_LENGTH + accountAddressesLength.length + + (accountKeysSize * PublicKey.PUBLIC_KEY_LENGTH) + instructionsLength.length + + compiledInstructionsLength; + + ByteBuffer out = ByteBuffer.allocate(bufferSize); + + ByteBuffer accountKeysBuff = ByteBuffer.allocate(accountKeysSize * PublicKey.PUBLIC_KEY_LENGTH); + for (AccountMeta accountMeta : keysList) { + accountKeysBuff.put(accountMeta.getPublicKey().toByteArray()); + + if (accountMeta.isSigner()) { + messageHeader.numRequiredSignatures += 1; + if (!accountMeta.isWritable()) { + messageHeader.numReadonlySignedAccounts += 1; + } + } else { + if (!accountMeta.isWritable()) { + messageHeader.numReadonlyUnsignedAccounts += 1; + } + } + } + + out.put(messageHeader.toByteArray()); + + out.put(accountAddressesLength); + out.put(accountKeysBuff.array()); + + out.put(Base58.decode(recentBlockhash)); + + out.put(instructionsLength); + for (CompiledInstruction compiledInstruction : compiledInstructions) { + out.put(compiledInstruction.programIdIndex); + out.put(compiledInstruction.keyIndicesCount); + out.put(compiledInstruction.keyIndices); + out.put(compiledInstruction.dataLength); + out.put(compiledInstruction.data); + } + + return out.array(); + } + + protected void setFeePayer(Account feePayer) { + this.feePayer = feePayer; + } + + private List getAccountKeys() { + List keysList = accountKeys.getList(); + int feePayerIndex = findAccountIndex(keysList, feePayer.getPublicKey()); + + List newList = new ArrayList<>(); + AccountMeta feePayerMeta = keysList.get(feePayerIndex); + newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); + keysList.remove(feePayerIndex); + newList.addAll(keysList); + + return newList; + } + + private int findAccountIndex(List accountMetaList, PublicKey key) { + for (int i = 0; i < accountMetaList.size(); i++) { + if (accountMetaList.get(i).getPublicKey().equals(key)) { + return i; + } + } + + throw new RuntimeException("unable to find account index"); + } +} diff --git a/src/main/java/org/p2p/solanaj/core/LegacyTransaction.java b/src/main/java/org/p2p/solanaj/core/LegacyTransaction.java new file mode 100644 index 00000000..01e50a27 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/core/LegacyTransaction.java @@ -0,0 +1,76 @@ +package org.p2p.solanaj.core; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.bitcoinj.core.Base58; +import org.p2p.solanaj.utils.Shortvec; +import org.p2p.solanaj.utils.TweetNaclFast; + +public class LegacyTransaction { + + public static final int SIGNATURE_LENGTH = 64; + + private final LegacyMessage legacyMessage; + private final List signatures; + private byte[] serializedLegacyMessage; + + public LegacyTransaction() { + this.legacyMessage = new LegacyMessage(); + this.signatures = new ArrayList<>(); + } + + public LegacyTransaction addInstruction(TransactionInstruction instruction) { + legacyMessage.addInstruction(instruction); + + return this; + } + + public void setRecentBlockHash(String recentBlockhash) { + legacyMessage.setRecentBlockHash(recentBlockhash); + } + + public void sign(Account signer) { + sign(Collections.singletonList(signer)); + } + + public void sign(List signers) { + + if (signers.isEmpty()) { + throw new IllegalArgumentException("No signers"); + } + + Account feePayer = signers.get(0); + legacyMessage.setFeePayer(feePayer); + + serializedLegacyMessage = legacyMessage.serialize(); + + for (Account signer : signers) { + TweetNaclFast.Signature signatureProvider = new TweetNaclFast.Signature(new byte[0], signer.getSecretKey()); + byte[] signature = signatureProvider.detached(serializedLegacyMessage); + + signatures.add(Base58.encode(signature)); + } + } + + public byte[] serialize() { + int signaturesSize = signatures.size(); + byte[] signaturesLength = Shortvec.encodeLength(signaturesSize); + + ByteBuffer out = ByteBuffer + .allocate(signaturesLength.length + signaturesSize * SIGNATURE_LENGTH + serializedLegacyMessage.length); + + out.put(signaturesLength); + + for (String signature : signatures) { + byte[] rawSignature = Base58.decode(signature); + out.put(rawSignature); + } + + out.put(serializedLegacyMessage); + + return out.array(); + } +} \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/core/LegacyTransactionBuilder.java b/src/main/java/org/p2p/solanaj/core/LegacyTransactionBuilder.java new file mode 100644 index 00000000..1412f286 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/core/LegacyTransactionBuilder.java @@ -0,0 +1,35 @@ +package org.p2p.solanaj.core; + +import java.util.List; + +/** + * Builder for constructing {@link LegacyTransaction} objects to be used in sendLegacyTransaction. + */ +public class LegacyTransactionBuilder { + + private final LegacyTransaction legacyTransaction; + + public LegacyTransactionBuilder() { + legacyTransaction = new LegacyTransaction(); + } + + public LegacyTransactionBuilder addInstruction(TransactionInstruction transactionInstruction) { + legacyTransaction.addInstruction(transactionInstruction); + return this; + } + + public LegacyTransactionBuilder setRecentBlockHash(String recentBlockHash) { + legacyTransaction.setRecentBlockHash(recentBlockHash); + return this; + } + + public LegacyTransactionBuilder setSigners(List signers) { + legacyTransaction.sign(signers); + return this; + } + + public LegacyTransaction build() { + return legacyTransaction; + } + +} diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index 3badab14..bb7420e1 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -2,56 +2,137 @@ import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import lombok.Getter; import org.bitcoinj.core.Base58; -import org.p2p.solanaj.utils.ShortvecEncoding; +import org.p2p.solanaj.utils.GuardedArrayUtils; +import org.p2p.solanaj.utils.Shortvec; public class Message { - private class MessageHeader { + @Getter + public static class MessageHeader { static final int HEADER_LENGTH = 3; - byte numRequiredSignatures = 0; - byte numReadonlySignedAccounts = 0; - byte numReadonlyUnsignedAccounts = 0; + private final byte numRequiredSignatures; + private final byte numReadonlySignedAccounts; + private final byte numReadonlyUnsignedAccounts; byte[] toByteArray() { return new byte[] { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts }; } + + MessageHeader(byte[] byteArray) { + numRequiredSignatures = byteArray[0]; + numReadonlySignedAccounts = byteArray[1]; + numReadonlyUnsignedAccounts = byteArray[2]; + } + + @Override + public String toString() { + return "MessageHeader{" + + "numRequiredSignatures=" + numRequiredSignatures + + ", numReadonlySignedAccounts=" + numReadonlySignedAccounts + + ", numReadonlyUnsignedAccounts=" + numReadonlyUnsignedAccounts + + '}'; + } } - private class CompiledInstruction { - byte programIdIndex; - byte[] keyIndicesCount; - byte[] keyIndices; - byte[] dataLength; - byte[] data; + @Getter + public static class CompiledInstruction { + private byte programIdIndex; + private byte[] keyIndicesCount; + private byte[] keyIndices; + private byte[] dataLength; + private byte[] data; int getLength() { // 1 = programIdIndex length return 1 + keyIndicesCount.length + keyIndices.length + dataLength.length + data.length; } + + @Override + public String toString() { + return "CompiledInstruction{" + + "programIdIndex=" + programIdIndex + + ", keyIndicesCount=" + Arrays.toString(keyIndicesCount) + + ", keyIndices=" + Arrays.toString(keyIndices) + + ", dataLength=" + Arrays.toString(dataLength) + + ", data=" + Arrays.toString(data) + + '}'; + } + } + + @Getter + public static class MessageAddressTableLookup { + private PublicKey accountKey; + private byte[] writableIndexesCountLength; + private byte[] writableIndexes; + private byte[] readonlyIndexesCountLength; + private byte[] readonlyIndexes; + + int getLength() { + // 1 = programIdIndex length + return PublicKey.PUBLIC_KEY_LENGTH + writableIndexesCountLength.length + writableIndexes.length + + readonlyIndexesCountLength.length + readonlyIndexes.length; + } + + @Override + public String toString() { + return "MessageAddressTableLookup{" + + "accountKey=" + accountKey + + ", writableIndexesCountLength=" + Arrays.toString(writableIndexesCountLength) + + ", writableIndexes=" + Arrays.toString(writableIndexes) + + ", readonlyIndexesCountLength=" + Arrays.toString(readonlyIndexesCountLength) + + ", readonlyIndexes=" + Arrays.toString(readonlyIndexes) + + '}'; + } } private static final int RECENT_BLOCK_HASH_LENGTH = 32; private MessageHeader messageHeader; private String recentBlockhash; - private AccountKeysList accountKeys; - private List instructions; + private final AccountKeysList accountKeys; + private final List compiledInstructions; + private final List addressTableLookups; private Account feePayer; public Message() { this.accountKeys = new AccountKeysList(); - this.instructions = new ArrayList(); + this.compiledInstructions = new ArrayList<>(); + this.addressTableLookups = new ArrayList<>(); + } + + public Message(MessageHeader messageHeader, String recentBlockhash, AccountKeysList accountKeys, + List compiledInstructions, List addressTableLookups) { + this.messageHeader = messageHeader; + this.recentBlockhash = recentBlockhash; + this.accountKeys = accountKeys; + this.compiledInstructions = compiledInstructions; + this.addressTableLookups = addressTableLookups; } public Message addInstruction(TransactionInstruction instruction) { accountKeys.addAll(instruction.getKeys()); accountKeys.add(new AccountMeta(instruction.getProgramId(), false, false)); - instructions.add(instruction); + List keysList = getAccountKeys(); + int keysSize = instruction.getKeys().size(); + + CompiledInstruction compiledInstruction = new CompiledInstruction(); + compiledInstruction.programIdIndex = (byte) findAccountIndex(keysList, instruction.getProgramId()); + compiledInstruction.keyIndicesCount = Shortvec.encodeLength(keysSize); + byte[] keyIndices = new byte[keysSize]; + for (int i = 0; i < instruction.getKeys().size(); i++) { + keyIndices[i] = (byte) findAccountIndex(keysList, instruction.getKeys().get(i).getPublicKey()); + } + compiledInstruction.keyIndices = keyIndices; + compiledInstruction.dataLength = Shortvec.encodeLength(instruction.getData().length); + compiledInstruction.data = instruction.getData(); + compiledInstructions.add(compiledInstruction); return this; } @@ -65,62 +146,35 @@ public byte[] serialize() { throw new IllegalArgumentException("recentBlockhash required"); } - if (instructions.size() == 0) { + if (compiledInstructions.isEmpty()) { throw new IllegalArgumentException("No instructions provided"); } - messageHeader = new MessageHeader(); - List keysList = getAccountKeys(); int accountKeysSize = keysList.size(); - byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); + byte[] accountAddressesLength = Shortvec.encodeLength(accountKeysSize); + byte[] instructionsCountLength = Shortvec.encodeLength(compiledInstructions.size()); int compiledInstructionsLength = 0; - List compiledInstructions = new ArrayList(); - - for (TransactionInstruction instruction : instructions) { - int keysSize = instruction.getKeys().size(); - - byte[] keyIndices = new byte[keysSize]; - for (int i = 0; i < keysSize; i++) { - keyIndices[i] = (byte) findAccountIndex(keysList, instruction.getKeys().get(i).getPublicKey()); - } - - CompiledInstruction compiledInstruction = new CompiledInstruction(); - compiledInstruction.programIdIndex = (byte) findAccountIndex(keysList, instruction.getProgramId()); - compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); - compiledInstruction.keyIndices = keyIndices; - compiledInstruction.dataLength = ShortvecEncoding.encodeLength(instruction.getData().length); - compiledInstruction.data = instruction.getData(); - - compiledInstructions.add(compiledInstruction); - + for (CompiledInstruction compiledInstruction : this.compiledInstructions) { compiledInstructionsLength += compiledInstruction.getLength(); } - byte[] instructionsLength = ShortvecEncoding.encodeLength(compiledInstructions.size()); - + byte[] addressTableLookupsCountLength = Shortvec.encodeLength(addressTableLookups.size()); + int addressTableLookupsLength = 0; + for (MessageAddressTableLookup addressTableLookup : this.addressTableLookups) { + addressTableLookupsLength += addressTableLookup.getLength(); + } int bufferSize = MessageHeader.HEADER_LENGTH + RECENT_BLOCK_HASH_LENGTH + accountAddressesLength.length - + (accountKeysSize * PublicKey.PUBLIC_KEY_LENGTH) + instructionsLength.length - + compiledInstructionsLength; + + (accountKeysSize * PublicKey.PUBLIC_KEY_LENGTH) + instructionsCountLength.length + + compiledInstructionsLength + addressTableLookupsCountLength.length + addressTableLookupsLength; ByteBuffer out = ByteBuffer.allocate(bufferSize); ByteBuffer accountKeysBuff = ByteBuffer.allocate(accountKeysSize * PublicKey.PUBLIC_KEY_LENGTH); for (AccountMeta accountMeta : keysList) { accountKeysBuff.put(accountMeta.getPublicKey().toByteArray()); - - if (accountMeta.isSigner()) { - messageHeader.numRequiredSignatures += 1; - if (!accountMeta.isWritable()) { - messageHeader.numReadonlySignedAccounts += 1; - } - } else { - if (!accountMeta.isWritable()) { - messageHeader.numReadonlyUnsignedAccounts += 1; - } - } } out.put(messageHeader.toByteArray()); @@ -129,8 +183,7 @@ public byte[] serialize() { out.put(accountKeysBuff.array()); out.put(Base58.decode(recentBlockhash)); - - out.put(instructionsLength); + out.put(instructionsCountLength); for (CompiledInstruction compiledInstruction : compiledInstructions) { out.put(compiledInstruction.programIdIndex); out.put(compiledInstruction.keyIndicesCount); @@ -139,18 +192,88 @@ public byte[] serialize() { out.put(compiledInstruction.data); } + out.put(addressTableLookupsCountLength); + for (MessageAddressTableLookup addressTableLookup : addressTableLookups) { + out.put(addressTableLookup.accountKey.toByteArray()); + out.put(addressTableLookup.writableIndexesCountLength); + out.put(addressTableLookup.writableIndexes); + out.put(addressTableLookup.readonlyIndexesCountLength); + out.put(addressTableLookup.readonlyIndexes); + } + return out.array(); } + public static Message deserialize(List serializedMessageList) { + // Remove the byte as it is used to indicate legacy Transaction. + GuardedArrayUtils.guardedShift(serializedMessageList); + + // Remove three bytes for header + byte[] messageHeaderBytes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); + MessageHeader messageHeader = new MessageHeader(messageHeaderBytes); + + // Total static account keys + int accountKeysSize = Shortvec.decodeLength(serializedMessageList); + List accountKeys = new ArrayList<>(accountKeysSize); + for (int i = 0; i < accountKeysSize; i++) { + byte[] accountMetaPublicKeyByteArray = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH); + PublicKey publicKey = new PublicKey(accountMetaPublicKeyByteArray); + accountKeys.add(new AccountMeta(publicKey, false, false)); + } + AccountKeysList accountKeysList = new AccountKeysList(); + accountKeysList.addAll(accountKeys); + + // recent_blockhash + String recentBlockHash = Base58.encode(GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH)); + + // Deserialize instructions + int instructionsLength = Shortvec.decodeLength(serializedMessageList); + List compiledInstructions = new ArrayList<>(instructionsLength); + for (int i = 0; i < instructionsLength; i++) { + CompiledInstruction compiledInstruction = new CompiledInstruction(); + compiledInstruction.programIdIndex = GuardedArrayUtils.guardedShift(serializedMessageList); + int keysSize = Shortvec.decodeLength(serializedMessageList); // keysSize + compiledInstruction.keyIndicesCount = Shortvec.encodeLength(keysSize); + compiledInstruction.keyIndices = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, keysSize); + var dataLength = Shortvec.decodeLength(serializedMessageList); + compiledInstruction.dataLength = Shortvec.encodeLength(dataLength); + compiledInstruction.data = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, dataLength); + + compiledInstructions.add(compiledInstruction); + + } + + // Deserialize addressTableLookups + int addressTableLookupsLength = Shortvec.decodeLength(serializedMessageList); + List addressTableLookups = new ArrayList<>(addressTableLookupsLength); + for (int i = 0; i < addressTableLookupsLength; i++) { + MessageAddressTableLookup addressTableLookup = new MessageAddressTableLookup(); + byte[] accountKeyByteArray = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, PublicKey.PUBLIC_KEY_LENGTH); + addressTableLookup.accountKey = new PublicKey(accountKeyByteArray); + int writableIndexesLength = Shortvec.decodeLength(serializedMessageList); // keysSize + addressTableLookup.writableIndexesCountLength = Shortvec.encodeLength(writableIndexesLength); + addressTableLookup.writableIndexes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, writableIndexesLength); + int readonlyIndexesLength = Shortvec.decodeLength(serializedMessageList); + addressTableLookup.readonlyIndexesCountLength = Shortvec.encodeLength(readonlyIndexesLength); + addressTableLookup.readonlyIndexes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, readonlyIndexesLength); + + addressTableLookups.add(addressTableLookup); + } + + return new Message(messageHeader, recentBlockHash, accountKeysList, compiledInstructions, addressTableLookups); + } + protected void setFeePayer(Account feePayer) { this.feePayer = feePayer; } - private List getAccountKeys() { + public List getAccountKeys() { List keysList = accountKeys.getList(); int feePayerIndex = findAccountIndex(keysList, feePayer.getPublicKey()); - List newList = new ArrayList(); + List newList = new ArrayList<>(); AccountMeta feePayerMeta = keysList.get(feePayerIndex); newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); keysList.remove(feePayerIndex); @@ -159,7 +282,7 @@ private List getAccountKeys() { return newList; } - private int findAccountIndex(List accountMetaList, PublicKey key) { + public int findAccountIndex(List accountMetaList, PublicKey key) { for (int i = 0; i < accountMetaList.size(); i++) { if (accountMetaList.get(i).getPublicKey().equals(key)) { return i; @@ -168,4 +291,16 @@ private int findAccountIndex(List accountMetaList, PublicKey key) { throw new RuntimeException("unable to find account index"); } + + @Override + public String toString() { + return "Message{" + + "messageHeader=" + messageHeader + + ", recentBlockhash='" + recentBlockhash + '\'' + + ", accountKeys=" + accountKeys + + ", compiledInstructions=" + compiledInstructions + + ", addressTableLookups=" + addressTableLookups + + ", feePayer=" + feePayer + + '}'; + } } diff --git a/src/main/java/org/p2p/solanaj/core/PublicKey.java b/src/main/java/org/p2p/solanaj/core/PublicKey.java index 64af1818..cf8deda5 100644 --- a/src/main/java/org/p2p/solanaj/core/PublicKey.java +++ b/src/main/java/org/p2p/solanaj/core/PublicKey.java @@ -6,6 +6,7 @@ import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; import org.bitcoinj.core.Base58; import org.bitcoinj.core.Sha256Hash; import org.p2p.solanaj.utils.ByteUtils; @@ -17,7 +18,7 @@ public class PublicKey { public static final int PUBLIC_KEY_LENGTH = 32; - private byte[] pubkey; + private final byte[] pubkey; public PublicKey(String pubkey) { if (pubkey.length() < PUBLIC_KEY_LENGTH) { @@ -97,31 +98,23 @@ public static PublicKey createProgramAddress(List seeds, PublicKey progr return new PublicKey(hash); } + @Getter public static class ProgramDerivedAddress { - private PublicKey address; - private int nonce; + private final PublicKey address; + private final int nonce; public ProgramDerivedAddress(PublicKey address, int nonce) { this.address = address; this.nonce = nonce; } - public PublicKey getAddress() { - return address; - } - - public int getNonce() { - return nonce; - } - } public static ProgramDerivedAddress findProgramAddress(List seeds, PublicKey programId) throws Exception { int nonce = 255; PublicKey address; - List seedsWithNonce = new ArrayList(); - seedsWithNonce.addAll(seeds); + List seedsWithNonce = new ArrayList<>(seeds); while (nonce != 0) { try { diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index 397c7ba5..8b9590f7 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -3,23 +3,31 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.bitcoinj.core.Base58; -import org.p2p.solanaj.utils.ShortvecEncoding; +import org.p2p.solanaj.utils.ByteUtils; +import org.p2p.solanaj.utils.GuardedArrayUtils; +import org.p2p.solanaj.utils.Shortvec; import org.p2p.solanaj.utils.TweetNaclFast; public class Transaction { public static final int SIGNATURE_LENGTH = 64; - private Message message; - private List signatures; + private final Message message; + private final List signatures; private byte[] serializedMessage; public Transaction() { this.message = new Message(); - this.signatures = new ArrayList(); + this.signatures = new ArrayList<>(); + } + + public Transaction(Message message, List signatures) { + this.message = message; + this.signatures = signatures; } public Transaction addInstruction(TransactionInstruction instruction) { @@ -33,31 +41,38 @@ public void setRecentBlockHash(String recentBlockhash) { } public void sign(Account signer) { - sign(Arrays.asList(signer)); + sign(Collections.singletonList(signer)); } public void sign(List signers) { - - if (signers.size() == 0) { + if (signers.isEmpty()) { throw new IllegalArgumentException("No signers"); } Account feePayer = signers.get(0); message.setFeePayer(feePayer); + List signerPubKeys = List.copyOf(message.getAccountKeys()); + signerPubKeys = signerPubKeys.subList(0, signers.size()); + serializedMessage = message.serialize(); for (Account signer : signers) { + int signerIndex = message.findAccountIndex(signerPubKeys, signer.getPublicKey()); + if (signerIndex < 0) { + throw new IllegalArgumentException("Cannot sign with non signer key: " + + signer.getPublicKey().toBase58()); + } TweetNaclFast.Signature signatureProvider = new TweetNaclFast.Signature(new byte[0], signer.getSecretKey()); byte[] signature = signatureProvider.detached(serializedMessage); - signatures.add(Base58.encode(signature)); + this.signatures.set(signerIndex, Base58.encode(signature)); } } public byte[] serialize() { int signaturesSize = signatures.size(); - byte[] signaturesLength = ShortvecEncoding.encodeLength(signaturesSize); + byte[] signaturesLength = Shortvec.encodeLength(signaturesSize); ByteBuffer out = ByteBuffer .allocate(signaturesLength.length + signaturesSize * SIGNATURE_LENGTH + serializedMessage.length); @@ -73,4 +88,29 @@ public byte[] serialize() { return out.array(); } + + public static Transaction deserialize(byte[] serializedTransaction) { + List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); + + int signaturesSize = Shortvec.decodeLength(serializedTransactionList); + List signatures = new ArrayList<>(signaturesSize); + + for (int i = 0; i < signaturesSize; i++) { + + byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); + signatures.add(Base58.encode(signatureBytes)); + } + + Message message = Message.deserialize(serializedTransactionList); + return new Transaction(message, signatures); + } + + @Override + public String toString() { + return "Transaction{" + + "message=" + message + + ", signatures=" + signatures + + ", serializedMessage=" + Arrays.toString(serializedMessage) + + '}'; + } } diff --git a/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java b/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java deleted file mode 100644 index bc8b15fc..00000000 --- a/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.p2p.solanaj.core; - -import java.util.List; - -/** - * Builder for constructing {@link Transaction} objects to be used in sendTransaction. - */ -public class TransactionBuilder { - - private final Transaction transaction; - - public TransactionBuilder() { - transaction = new Transaction(); - } - - public TransactionBuilder addInstruction(TransactionInstruction transactionInstruction) { - transaction.addInstruction(transactionInstruction); - return this; - } - - public TransactionBuilder setRecentBlockHash(String recentBlockHash) { - transaction.setRecentBlockHash(recentBlockHash); - return this; - } - - public TransactionBuilder setSigners(List signers) { - transaction.sign(signers); - return this; - } - - public Transaction build() { - return transaction; - } - -} diff --git a/src/main/java/org/p2p/solanaj/programs/JupiterSwapProgram.java b/src/main/java/org/p2p/solanaj/programs/JupiterSwapProgram.java new file mode 100644 index 00000000..1a631203 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/programs/JupiterSwapProgram.java @@ -0,0 +1,110 @@ +package org.p2p.solanaj.programs; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.net.URIBuilder; +import org.p2p.solanaj.core.Account; +import org.p2p.solanaj.core.Transaction; +import org.p2p.solanaj.rpc.RpcClient; +import org.p2p.solanaj.rpc.RpcException; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Base64; + +public final class JupiterSwapProgram { + + public static final String SOL_QUOTE_TOKEN = "So11111111111111111111111111111111111111112"; + + private static final CloseableHttpClient httpClient = HttpClients.createDefault(); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static final HttpClientResponseHandler handler = response -> { + int status = response.getCode(); + if (status >= 200 && status < 300) { + HttpEntity entity = response.getEntity(); + return entity != null ? objectMapper.readTree(EntityUtils.toString(entity)) : null; + } else { + throw new IOException("Unexpected response status: " + status); + } + }; + + public static URI createQuoteUri(String inputToken, String outputToken, String amount, String slippage) { + try { + return new URIBuilder("https://quote-api.jup.ag/v6/quote") + .addParameter("inputMint", inputToken) + .addParameter("outputMint", outputToken) + .addParameter("amount", amount) + .addParameter("slippageBps", slippage) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException("Failed to create quote URI: ", e); + } + } + + public static JsonNode getJupiterQuote(URI quoteUri) { + HttpGet quoteRequest = new HttpGet(quoteUri); + + JsonNode quote; + try { + quote = httpClient.execute(quoteRequest, handler); + } catch (IOException e) { + throw new RuntimeException("Failed to retrieve quote from jupiter for " + quoteUri, e); + } + return quote; + } + + public static String swapToken(RpcClient rpcClient, Account account, JsonNode quote) { + SwapRequest swapRequestBody = new SwapRequest(quote, account.getPublicKey().toString()); + String jsonSwapRequestBody; + try { + jsonSwapRequestBody = objectMapper.writeValueAsString(swapRequestBody); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + HttpPost swapRequest = new HttpPost("https://quote-api.jup.ag/v6/swap"); + swapRequest.setHeader("Content-Type", "application/json"); + swapRequest.setEntity(new StringEntity(jsonSwapRequestBody)); + + JsonNode swapRes; + try { + swapRes = httpClient.execute(swapRequest, handler); + } catch (IOException e) { + throw new RuntimeException(e); + } + + String swapTransaction = swapRes.get("swapTransaction").asText(); + + byte[] base64Decoded = Base64.getDecoder().decode(swapTransaction); + Transaction transaction = Transaction.deserialize(base64Decoded); + + try { + return rpcClient.getApi().sendTransaction(transaction, account); + } catch (RpcException e) { + throw new RuntimeException("Failed to send swap transaction", e); + } + } + + public static String swapToken(RpcClient rpcClient, Account account, String inputToken, String outputToken, String amount, String slippage) { + URI quoteUri = createQuoteUri(inputToken, outputToken, amount, slippage); + return swapToken(rpcClient, account, quoteUri); + } + + public static String swapToken(RpcClient rpcClient, Account account, URI quoteUri) { + JsonNode quote = getJupiterQuote(quoteUri); + return swapToken(rpcClient, account, quote); + } + + public record SwapRequest(Object quoteResponse, String userPublicKey) {} +} diff --git a/src/main/java/org/p2p/solanaj/programs/SystemProgram.java b/src/main/java/org/p2p/solanaj/programs/SystemProgram.java index 29bf8846..a128c606 100644 --- a/src/main/java/org/p2p/solanaj/programs/SystemProgram.java +++ b/src/main/java/org/p2p/solanaj/programs/SystemProgram.java @@ -15,7 +15,7 @@ public class SystemProgram extends Program { public static final int PROGRAM_INDEX_TRANSFER = 2; public static TransactionInstruction transfer(PublicKey fromPublicKey, PublicKey toPublickKey, long lamports) { - ArrayList keys = new ArrayList(); + ArrayList keys = new ArrayList<>(); keys.add(new AccountMeta(fromPublicKey, true, true)); keys.add(new AccountMeta(toPublickKey, false, true)); @@ -29,7 +29,7 @@ public static TransactionInstruction transfer(PublicKey fromPublicKey, PublicKey public static TransactionInstruction createAccount(PublicKey fromPublicKey, PublicKey newAccountPublikkey, long lamports, long space, PublicKey programId) { - ArrayList keys = new ArrayList(); + ArrayList keys = new ArrayList<>(); keys.add(new AccountMeta(fromPublicKey, true, true)); keys.add(new AccountMeta(newAccountPublikkey, true, true)); @@ -43,7 +43,7 @@ public static TransactionInstruction createAccount(PublicKey fromPublicKey, Publ } public static TransactionInstruction assign(PublicKey owner, PublicKey newOwner) { - ArrayList keys = new ArrayList(); + ArrayList keys = new ArrayList<>(); keys.add(new AccountMeta(owner, true, true)); byte[] data = new byte[4 + 32]; diff --git a/src/main/java/org/p2p/solanaj/programs/anchor/AnchorBasicTutorialProgram.java b/src/main/java/org/p2p/solanaj/programs/anchor/AnchorBasicTutorialProgram.java index d24cd4fd..2620929c 100644 --- a/src/main/java/org/p2p/solanaj/programs/anchor/AnchorBasicTutorialProgram.java +++ b/src/main/java/org/p2p/solanaj/programs/anchor/AnchorBasicTutorialProgram.java @@ -46,7 +46,7 @@ public static TransactionInstruction initialize(Account caller) { * @return byte array containing sighash for "global::initialize" */ private static byte[] encodeInitializeData() { - MessageDigest digest = null; + MessageDigest digest; byte[] encodedHash = null; int sigHashStart = 0; int sigHashEnd = 8; diff --git a/src/main/java/org/p2p/solanaj/rpc/Cluster.java b/src/main/java/org/p2p/solanaj/rpc/Cluster.java index 41452359..0657f194 100644 --- a/src/main/java/org/p2p/solanaj/rpc/Cluster.java +++ b/src/main/java/org/p2p/solanaj/rpc/Cluster.java @@ -1,18 +1,18 @@ package org.p2p.solanaj.rpc; +import lombok.Getter; + +@Getter public enum Cluster { DEVNET("https://api.devnet.solana.com"), TESTNET("https://api.testnet.solana.com"), MAINNET("https://api.mainnet-beta.solana.com"), ANKR("https://rpc.ankr.com/solana"); - private String endpoint; + private final String endpoint; Cluster(String endpoint) { this.endpoint = endpoint; } - public String getEndpoint() { - return endpoint; - } } diff --git a/src/main/java/org/p2p/solanaj/rpc/LoggingInterceptor.java b/src/main/java/org/p2p/solanaj/rpc/LoggingInterceptor.java index 0167f129..8960c9d6 100644 --- a/src/main/java/org/p2p/solanaj/rpc/LoggingInterceptor.java +++ b/src/main/java/org/p2p/solanaj/rpc/LoggingInterceptor.java @@ -4,6 +4,7 @@ import okhttp3.Request; import okhttp3.Response; import okio.Buffer; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.logging.Logger; @@ -12,6 +13,7 @@ public class LoggingInterceptor implements Interceptor { private static final Logger LOGGER = Logger.getLogger(LoggingInterceptor.class.getName()); + @NotNull @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java index 37f0b3d2..1d7c13fe 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java @@ -3,6 +3,7 @@ import java.util.*; import java.util.stream.Collectors; import org.p2p.solanaj.core.Account; +import org.p2p.solanaj.core.LegacyTransaction; import org.p2p.solanaj.core.PublicKey; import org.p2p.solanaj.core.Transaction; import org.p2p.solanaj.rpc.types.*; @@ -27,7 +28,7 @@ import org.p2p.solanaj.ws.listeners.NotificationEventListener; public class RpcApi { - private RpcClient client; + private final RpcClient client; public RpcApi(RpcClient client) { this.client = client; @@ -61,6 +62,69 @@ public String getRecentBlockhash(Commitment commitment) throws RpcException { return client.call("getRecentBlockhash", params, RecentBlockhash.class).getValue().getBlockhash(); } + + public String sendLegacyTransaction(LegacyTransaction legacyTransaction, Account signer, String recentBlockHash) throws + RpcException { + return sendLegacyTransaction(legacyTransaction, Collections.singletonList(signer), recentBlockHash); + } + + public String sendLegacyTransaction(LegacyTransaction legacyTransaction, Account signer) throws RpcException { + return sendLegacyTransaction(legacyTransaction, Collections.singletonList(signer), null); + } + + /** + * Sends a transaction to the RPC server. + * + * @param legacyTransaction The transaction to send. + * @param signers The list of accounts signing the transaction. + * @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 sendLegacyTransaction(LegacyTransaction legacyTransaction, List signers, String recentBlockHash, + RpcSendTransactionConfig rpcSendTransactionConfig) + throws RpcException { + if (recentBlockHash == null) { + recentBlockHash = getRecentBlockhash(); + } + legacyTransaction.setRecentBlockHash(recentBlockHash); + legacyTransaction.sign(signers); + byte[] serializedTransaction = legacyTransaction.serialize(); + + String base64Trx = Base64.getEncoder().encodeToString(serializedTransaction); + + List params = new ArrayList<>(); + + params.add(base64Trx); + params.add(rpcSendTransactionConfig); + + return client.call("sendLegacyTransaction", params, String.class); + } + + /** + * Sends a transaction to the network for processing. + * A default RpcSendTransactionConfig is used. + * + * @param legacyTransaction the transaction to send + * @param signers the list of accounts that will sign the transaction + * @param recentBlockHash the recent block hash to include in the transaction + * @return the result of the transaction + * @throws RpcException if an error occurs during the RPC call + */ + public String sendLegacyTransaction(LegacyTransaction legacyTransaction, List signers, String recentBlockHash) + throws RpcException { + return sendLegacyTransaction(legacyTransaction, signers, recentBlockHash, new RpcSendTransactionConfig()); + } + + public void sendAndConfirmLegacyTransaction(LegacyTransaction legacyTransaction, List signers, + NotificationEventListener listener) throws RpcException { + String signature = sendLegacyTransaction(legacyTransaction, signers, null); + + SubscriptionWebSocketClient subClient = SubscriptionWebSocketClient.getInstance(client.getEndpoint()); + subClient.signatureSubscribe(signature, listener); + } + public String sendTransaction(Transaction transaction, Account signer, String recentBlockHash) throws RpcException { return sendTransaction(transaction, Collections.singletonList(signer), recentBlockHash); @@ -81,7 +145,7 @@ public String sendTransaction(Transaction transaction, Account signer) throws Rp * @throws RpcException If an error occurs during the RPC call. */ public String sendTransaction(Transaction transaction, List signers, String recentBlockHash, - RpcSendTransactionConfig rpcSendTransactionConfig) + RpcSendTransactionConfig rpcSendTransactionConfig) throws RpcException { if (recentBlockHash == null) { recentBlockHash = getRecentBlockhash(); @@ -92,12 +156,12 @@ public String sendTransaction(Transaction transaction, List signers, St String base64Trx = Base64.getEncoder().encodeToString(serializedTransaction); - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(base64Trx); params.add(rpcSendTransactionConfig); - return client.call("sendTransaction", params, String.class); + return client.call("sendLegacyTransaction", params, String.class); } /** @@ -116,7 +180,7 @@ public String sendTransaction(Transaction transaction, List signers, St } public void sendAndConfirmTransaction(Transaction transaction, List signers, - NotificationEventListener listener) throws RpcException { + NotificationEventListener listener) throws RpcException { String signature = sendTransaction(transaction, signers, null); SubscriptionWebSocketClient subClient = SubscriptionWebSocketClient.getInstance(client.getEndpoint()); @@ -159,14 +223,14 @@ public ConfirmedTransaction getTransaction(String signature, Commitment commitme @SuppressWarnings({ "unchecked", "rawtypes" }) public List getConfirmedSignaturesForAddress2(PublicKey account, int limit) throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(account.toString()); params.add(new ConfirmedSignFAddr2(limit, Commitment.CONFIRMED)); List rawResult = client.call("getConfirmedSignaturesForAddress2", params, List.class); - List result = new ArrayList(); + List result = new ArrayList<>(); for (AbstractMap item : rawResult) { result.add(new SignatureInformation(item)); } @@ -176,14 +240,36 @@ public List getConfirmedSignaturesForAddress2(PublicKey ac public List getSignaturesForAddress(PublicKey account, int limit, Commitment commitment) throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(account.toString()); params.add(new ConfirmedSignFAddr2(limit, commitment)); List rawResult = client.call("getSignaturesForAddress", params, List.class); - List result = new ArrayList(); + List result = new ArrayList<>(); + for (AbstractMap item : rawResult) { + result.add(new SignatureInformation(item)); + } + + return result; + } + + public List getSignaturesForAddress(PublicKey account, String beforeSignature, int limit, + Commitment commitment) + throws RpcException { + if (beforeSignature == null) { + return getSignaturesForAddress(account, limit, commitment); + } + + List params = new ArrayList<>(); + + params.add(account.toString()); + params.add(new ConfirmedSignFAddr2(beforeSignature, limit, commitment)); + + List rawResult = client.call("getSignaturesForAddress", params, List.class); + + List result = new ArrayList<>(); for (AbstractMap item : rawResult) { result.add(new SignatureInformation(item)); } @@ -192,7 +278,7 @@ public List getSignaturesForAddress(PublicKey account, int } public List getProgramAccounts(PublicKey account, long offset, String bytes) throws RpcException { - List filters = new ArrayList(); + List filters = new ArrayList<>(); filters.add(new Filter(new Memcmp(offset, bytes))); ProgramAccountConfig programAccountConfig = new ProgramAccountConfig(filters); @@ -200,7 +286,7 @@ public List getProgramAccounts(PublicKey account, long offset, S } public List getProgramAccountsBase64(PublicKey account, long offset, String bytes) throws RpcException { - List filters = new ArrayList(); + List filters = new ArrayList<>(); Memcmp memcmp = new Memcmp(offset, bytes); filters.add(new Filter(memcmp)); @@ -217,7 +303,7 @@ public List getProgramAccounts(PublicKey account) throws RpcExce @SuppressWarnings({ "unchecked", "rawtypes" }) public List getProgramAccounts(PublicKey account, ProgramAccountConfig programAccountConfig) throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(account.toString()); @@ -227,7 +313,7 @@ public List getProgramAccounts(PublicKey account, ProgramAccount List rawResult = client.call("getProgramAccounts", params, List.class); - List result = new ArrayList(); + List result = new ArrayList<>(); for (AbstractMap item : rawResult) { result.add(new ProgramAccount(item)); } @@ -243,9 +329,7 @@ public List getProgramAccounts(PublicKey account, List m params.add(account.toString()); List filters = new ArrayList<>(); - memcmpList.forEach(memcmp -> { - filters.add(new Filter(memcmp)); - }); + memcmpList.forEach(memcmp -> filters.add(new Filter(memcmp))); filters.add(new DataSize(dataSize)); @@ -270,9 +354,7 @@ public List getProgramAccounts(PublicKey account, List m params.add(account.toString()); List filters = new ArrayList<>(); - memcmpList.forEach(memcmp -> { - filters.add(new Filter(memcmp)); - }); + memcmpList.forEach(memcmp -> filters.add(new Filter(memcmp))); ProgramAccountConfig programAccountConfig = new ProgramAccountConfig(filters); params.add(programAccountConfig); @@ -400,7 +482,7 @@ public String requestAirdrop(PublicKey address, long lamports) throws RpcExcepti } public String requestAirdrop(PublicKey address, long lamports, Commitment commitment) throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(address.toString()); params.add(lamports); @@ -412,7 +494,7 @@ public String requestAirdrop(PublicKey address, long lamports, Commitment commit } public BlockCommitment getBlockCommitment(long block) throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(block); @@ -511,7 +593,7 @@ public SimulatedTransaction simulateTransaction(String transaction, List getClusterNodes() throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); // TODO - fix uncasted type stuff List rawResult = client.call("getClusterNodes", params, List.class); @@ -573,7 +655,7 @@ public Block getBlock(int slot, Map optionalParams) throws RpcEx * @throws RpcException */ public SnapshotSlot getHighestSnapshotSlot() throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); return client.call("getHighestSnapshotSlot", params, SnapshotSlot.class); } @@ -597,7 +679,7 @@ public EpochInfo getEpochInfo(Commitment commitment) throws RpcException { } public EpochSchedule getEpochSchedule() throws RpcException { - List params = new ArrayList(); + List params = new ArrayList<>(); return client.call("getEpochSchedule", params, EpochSchedule.class); } diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcClient.java b/src/main/java/org/p2p/solanaj/rpc/RpcClient.java index 83445da1..58ca62b0 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcClient.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcClient.java @@ -91,7 +91,6 @@ public T call(String method, List params, Class clazz) throws Rpc try { Response response = httpClient.newCall(request).execute(); final String result = response.body().string(); - // System.out.println("Response = " + result); RpcResponse rpcResult = resultAdapter.fromJson(result); if (rpcResult.getError() != null) { diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcException.java b/src/main/java/org/p2p/solanaj/rpc/RpcException.java index ae5ce2b4..b3b602e5 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcException.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcException.java @@ -1,6 +1,9 @@ package org.p2p.solanaj.rpc; +import java.io.Serial; + public class RpcException extends Exception { + @Serial private final static long serialVersionUID = 8315999767009642193L; public RpcException(String message) { diff --git a/src/main/java/org/p2p/solanaj/rpc/types/BlockProduction.java b/src/main/java/org/p2p/solanaj/rpc/types/BlockProduction.java index 063651c5..dea23389 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/BlockProduction.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/BlockProduction.java @@ -25,13 +25,10 @@ public static class BlockProductionRange { @Getter @ToString public static class BlockProductionValue { + @Getter @Json(name = "byIdentity") private Map> byIdentity; - public Map> getByIdentity() { - return byIdentity; - } - @Json(name = "range") private BlockProductionRange blockProductionRange; diff --git a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedBlock.java b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedBlock.java index f578a91a..36215430 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedBlock.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedBlock.java @@ -1,7 +1,6 @@ package org.p2p.solanaj.rpc.types; import com.squareup.moshi.Json; -import lombok.Data; import lombok.Getter; import lombok.ToString; diff --git a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedSignFAddr2.java b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedSignFAddr2.java index 7d191986..5766de1e 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedSignFAddr2.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedSignFAddr2.java @@ -6,7 +6,7 @@ public class ConfirmedSignFAddr2 { @Json(name = "limit") - private long limit; + private final long limit; @Json(name = "before") private String before; @@ -15,10 +15,16 @@ public class ConfirmedSignFAddr2 { private String until; @Json(name = "commitment") - private String commitment; + private final String commitment; public ConfirmedSignFAddr2(int limit, Commitment commitment) { this.limit = limit; this.commitment = commitment.getValue(); } + + public ConfirmedSignFAddr2(String before, int limit, Commitment commitment) { + this.before = before; + this.limit = limit; + this.commitment = commitment.getValue(); + } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java b/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java index 1e8a33c3..447aaea8 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java @@ -24,16 +24,16 @@ public static class Account { private String data; @Json(name = "executable") - private boolean executable; + private final boolean executable; @Json(name = "lamports") - private double lamports; + private final double lamports; @Json(name = "owner") - private String owner; + private final String owner; @Json(name = "rentEpoch") - private double rentEpoch; + private final double rentEpoch; private String encoding; @@ -46,7 +46,7 @@ public Account(Object acc) { List dataList = ((List) rawData); this.data = dataList.get(0); - this.encoding = (String) dataList.get(1); + this.encoding = dataList.get(1); } else if (rawData instanceof String) { this.data = (String) rawData; } @@ -67,10 +67,10 @@ public byte[] getDecodedData() { } @Json(name = "account") - private Account account; + private final Account account; @Json(name = "pubkey") - private String pubkey; + private final String pubkey; public PublicKey getPublicKey() { return new PublicKey(pubkey); diff --git a/src/main/java/org/p2p/solanaj/rpc/types/RpcRequest.java b/src/main/java/org/p2p/solanaj/rpc/types/RpcRequest.java index 708fc096..7199bfb2 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/RpcRequest.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/RpcRequest.java @@ -12,16 +12,16 @@ public class RpcRequest { @Json(name = "jsonrpc") - private String jsonrpc = "2.0"; + private final String jsonrpc = "2.0"; @Json(name = "method") - private String method; + private final String method; @Json(name = "params") - private List params; + private final List params; @Json(name = "id") - private String id = UUID.randomUUID().toString(); + private final String id = UUID.randomUUID().toString(); public RpcRequest(String method) { this(method, null); diff --git a/src/main/java/org/p2p/solanaj/rpc/types/Supply.java b/src/main/java/org/p2p/solanaj/rpc/types/Supply.java index 79ae2c4b..239d9a51 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/Supply.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/Supply.java @@ -3,7 +3,6 @@ import com.squareup.moshi.Json; import lombok.Getter; import lombok.ToString; -import org.p2p.solanaj.core.PublicKey; import java.util.List; diff --git a/src/main/java/org/p2p/solanaj/rpc/types/config/Commitment.java b/src/main/java/org/p2p/solanaj/rpc/types/config/Commitment.java index cc6199f0..39ad04ef 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/config/Commitment.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/config/Commitment.java @@ -1,5 +1,8 @@ package org.p2p.solanaj.rpc.types.config; +import lombok.Getter; + +@Getter public enum Commitment { FINALIZED("finalized"), @@ -17,7 +20,4 @@ public enum Commitment { this.value = value; } - public String getValue() { - return value; - } } diff --git a/src/main/java/org/p2p/solanaj/rpc/types/config/ProgramAccountConfig.java b/src/main/java/org/p2p/solanaj/rpc/types/config/ProgramAccountConfig.java index 5c7ffa6e..4a40e095 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/config/ProgramAccountConfig.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/config/ProgramAccountConfig.java @@ -2,15 +2,18 @@ import java.util.List; +import lombok.Setter; import org.p2p.solanaj.rpc.types.config.RpcSendTransactionConfig.Encoding; public class ProgramAccountConfig { + @Setter private Encoding encoding = null; + @Setter private List filters = null; - private String commitment = "processed"; + private final String commitment = "processed"; public ProgramAccountConfig(List filters) { this.filters = filters; @@ -20,11 +23,4 @@ public ProgramAccountConfig(Encoding encoding) { this.encoding = encoding; } - public void setEncoding(Encoding encoding) { - this.encoding = encoding; - } - - public void setFilters(List filters) { - this.filters = filters; - } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/rpc/types/config/RpcSendTransactionConfig.java b/src/main/java/org/p2p/solanaj/rpc/types/config/RpcSendTransactionConfig.java index 41960f7a..67446f7e 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/config/RpcSendTransactionConfig.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/config/RpcSendTransactionConfig.java @@ -10,11 +10,11 @@ @Builder public class RpcSendTransactionConfig { - public static enum Encoding { + public enum Encoding { base64("base64"), base58("base58"); - private String enc; + private final String enc; Encoding(String enc) { this.enc = enc; diff --git a/src/main/java/org/p2p/solanaj/token/TokenManager.java b/src/main/java/org/p2p/solanaj/token/TokenManager.java index 9bf7757c..eff2ad90 100644 --- a/src/main/java/org/p2p/solanaj/token/TokenManager.java +++ b/src/main/java/org/p2p/solanaj/token/TokenManager.java @@ -1,8 +1,8 @@ package org.p2p.solanaj.token; import org.p2p.solanaj.core.Account; +import org.p2p.solanaj.core.LegacyTransaction; import org.p2p.solanaj.core.PublicKey; -import org.p2p.solanaj.core.Transaction; import org.p2p.solanaj.programs.MemoProgram; import org.p2p.solanaj.programs.TokenProgram; import org.p2p.solanaj.rpc.RpcClient; @@ -20,10 +20,10 @@ public TokenManager(final RpcClient client) { } public String transfer(final Account owner, final PublicKey source, final PublicKey destination, final PublicKey tokenMint, long amount) { - final Transaction transaction = new Transaction(); + final LegacyTransaction legacyTransaction = new LegacyTransaction(); // SPL token instruction - transaction.addInstruction( + legacyTransaction.addInstruction( TokenProgram.transfer( source, destination, @@ -33,17 +33,17 @@ public String transfer(final Account owner, final PublicKey source, final Public ); // Memo - transaction.addInstruction( + legacyTransaction.addInstruction( MemoProgram.writeUtf8( owner.getPublicKey(), "Hello from SolanaJ" ) ); - // Call sendTransaction + // Call sendLegacyTransaction String result = null; try { - result = client.getApi().sendTransaction(transaction, owner); + result = client.getApi().sendLegacyTransaction(legacyTransaction, owner); } catch (RpcException e) { e.printStackTrace(); } @@ -61,9 +61,9 @@ public String transferCheckedToSolAddress(final Account owner, final PublicKey s e.printStackTrace(); } - final Transaction transaction = new Transaction(); + final LegacyTransaction legacyTransaction = new LegacyTransaction(); // SPL token instruction - transaction.addInstruction( + legacyTransaction.addInstruction( TokenProgram.transferChecked( source, tokenAccount, @@ -75,17 +75,17 @@ public String transferCheckedToSolAddress(final Account owner, final PublicKey s ); // Memo - transaction.addInstruction( + legacyTransaction.addInstruction( MemoProgram.writeUtf8( owner.getPublicKey(), "Hello from SolanaJ" ) ); - // Call sendTransaction + // Call sendLegacyTransaction String result = null; try { - result = client.getApi().sendTransaction(transaction, owner); + result = client.getApi().sendLegacyTransaction(legacyTransaction, owner); } catch (RpcException e) { e.printStackTrace(); } @@ -94,10 +94,10 @@ public String transferCheckedToSolAddress(final Account owner, final PublicKey s } public String initializeAccount(Account newAccount, PublicKey usdcTokenMint, Account owner) { - final Transaction transaction = new Transaction(); + final LegacyTransaction legacyTransaction = new LegacyTransaction(); // SPL token instruction - transaction.addInstruction( + legacyTransaction.addInstruction( TokenProgram.initializeAccount( newAccount.getPublicKey(), usdcTokenMint, @@ -105,10 +105,10 @@ public String initializeAccount(Account newAccount, PublicKey usdcTokenMint, Acc ) ); - // Call sendTransaction + // Call sendLegacyTransaction String result = null; try { - result = client.getApi().sendTransaction(transaction, owner); + result = client.getApi().sendLegacyTransaction(legacyTransaction, owner); } catch (RpcException e) { e.printStackTrace(); } diff --git a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java index 60ff25d6..b89204fe 100644 --- a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java @@ -1,11 +1,16 @@ package org.p2p.solanaj.utils; +import com.google.common.primitives.Bytes; + import static org.bitcoinj.core.Utils.*; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class ByteUtils { public static final int UINT_32_LENGTH = 4; @@ -72,4 +77,17 @@ public static int getBit(byte[] data, int pos) { return valInt; } + public static byte[] toByteArray(List byteList) { + return Bytes.toArray(byteList); + } + + public static List toByteList(byte[] bytes) { + return IntStream.range(0, bytes.length) + .mapToObj(i -> bytes[i]) + .collect(Collectors.toList()); + } + + public static byte[] emptyByteArray() { + return new byte[]{}; + } } diff --git a/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java b/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java new file mode 100644 index 00000000..c2fe99b7 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java @@ -0,0 +1,42 @@ +package org.p2p.solanaj.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GuardedArrayUtils { + + public static Byte guardedShift(List byteArray) { + if (byteArray.isEmpty()) { + throw new IllegalArgumentException("Byte array length is 0"); + } + return byteArray.remove(0); + } + + public static byte[] guardedSplice( + List byteList, + Integer start, + Integer deleteCount, + byte... items) { + List removedItems; + if (deleteCount != null) { + if (start + deleteCount > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = new ArrayList<>(byteList.subList(start, start + deleteCount)); + byteList.subList(start, start + deleteCount).clear(); + } else { + if (start > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = Collections.emptyList(); + } + List itemsToAdd = ByteUtils.toByteList(items); + byteList.addAll(start, itemsToAdd); + + if (!removedItems.isEmpty()) { + return ByteUtils.toByteArray(removedItems); + } + return ByteUtils.emptyByteArray(); + } +} diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/Shortvec.java similarity index 62% rename from src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java rename to src/main/java/org/p2p/solanaj/utils/Shortvec.java index 4a1d9080..478daf7c 100644 --- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java +++ b/src/main/java/org/p2p/solanaj/utils/Shortvec.java @@ -1,8 +1,10 @@ package org.p2p.solanaj.utils; +import java.util.List; + import static org.bitcoinj.core.Utils.*; -public class ShortvecEncoding { +public class Shortvec { public static byte[] encodeLength(int len) { byte[] out = new byte[10]; @@ -27,4 +29,18 @@ public static byte[] encodeLength(int len) { return bytes; } + + public static int decodeLength(List dataBytesList) { + int len = 0; + int size = 0; + for (;;) { + int elem = (int) dataBytesList.remove(0); + len |= (elem & 0x7f) << (size * 7); + size += 1; + if ((elem & 0x80) == 0) { + break; + } + } + return len; + } } diff --git a/src/main/java/org/p2p/solanaj/utils/TweetNaclFast.java b/src/main/java/org/p2p/solanaj/utils/TweetNaclFast.java index 8e6b22a0..87bd6cea 100644 --- a/src/main/java/org/p2p/solanaj/utils/TweetNaclFast.java +++ b/src/main/java/org/p2p/solanaj/utils/TweetNaclFast.java @@ -4,6 +4,8 @@ // Copyright (c) 2014 Tom Zhou +import lombok.Getter; + import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.lang.System; @@ -25,10 +27,10 @@ public static final class Box { private final static String TAG = "Box"; - private AtomicLong nonce; + private final AtomicLong nonce; - private byte [] theirPublicKey; - private byte [] mySecretKey; + private final byte [] theirPublicKey; + private final byte [] mySecretKey; private byte [] sharedKey; public Box(byte [] theirPublicKey, byte [] mySecretKey) { @@ -337,23 +339,17 @@ private byte[] generateNonce() { * */ public static final int overheadLength = 16; - public static class KeyPair { - private byte [] publicKey; - private byte [] secretKey; + @Getter + public static class KeyPair { + private final byte [] publicKey; + private final byte [] secretKey; public KeyPair() { publicKey = new byte[publicKeyLength]; secretKey = new byte[secretKeyLength]; } - public byte [] getPublicKey() { - return publicKey; - } - - public byte [] getSecretKey() { - return secretKey; - } - } + } /* * @description @@ -390,9 +386,9 @@ public static final class SecretBox { private final static String TAG = "SecretBox"; - private AtomicLong nonce; + private final AtomicLong nonce; - private byte [] key; + private final byte [] key; public SecretBox(byte [] key) { this(key, 68); @@ -687,8 +683,8 @@ public static final class Signature { private final static String TAG = "Signature"; - private byte [] theirPublicKey; - private byte [] mySecretKey; + private final byte [] theirPublicKey; + private final byte [] mySecretKey; public Signature(byte [] theirPublicKey, byte [] mySecretKey) { this.theirPublicKey = theirPublicKey; @@ -792,23 +788,17 @@ public boolean detached_verify(byte [] message, byte [] signature) { * Generates new random key pair for signing and * returns it as an object with publicKey and secretKey members * */ - public static class KeyPair { - private byte [] publicKey; - private byte [] secretKey; + @Getter + public static class KeyPair { + private final byte [] publicKey; + private final byte [] secretKey; public KeyPair() { publicKey = new byte[publicKeyLength]; secretKey = new byte[secretKeyLength]; } - public byte [] getPublicKey() { - return publicKey; - } - - public byte [] getSecretKey() { - return secretKey; - } - } + } /* * @description @@ -1451,10 +1441,10 @@ public static int crypto_stream_xor(byte [] c,int cpos, byte [] m,int mpos, long */ public static final class poly1305 { - private byte[] buffer; - private int[] r; - private int[] h; - private int[] pad; + private final byte[] buffer; + private final int[] r; + private final int[] h; + private final int[] pad; private int leftover; private int fin; diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/HdAddress.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/HdAddress.java index 782d1939..6dcc0e44 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/HdAddress.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/HdAddress.java @@ -1,6 +1,7 @@ package org.p2p.solanaj.utils.bip32.wallet; +import lombok.Getter; import org.p2p.solanaj.utils.bip32.wallet.key.HdPrivateKey; import org.p2p.solanaj.utils.bip32.wallet.key.HdPublicKey; @@ -9,9 +10,12 @@ */ public class HdAddress { + @Getter private final HdPrivateKey privateKey; + @Getter private final HdPublicKey publicKey; private final SolanaCoin solanaCoin; + @Getter private final String path; public HdAddress(HdPrivateKey privateKey, HdPublicKey publicKey, SolanaCoin solanaCoin, String path) { @@ -21,19 +25,8 @@ public HdAddress(HdPrivateKey privateKey, HdPublicKey publicKey, SolanaCoin sola this.path = path; } - public HdPrivateKey getPrivateKey() { - return privateKey; - } - - public HdPublicKey getPublicKey() { - return publicKey; - } - public SolanaCoin getCoinType() { return solanaCoin; } - public String getPath() { - return path; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaBip44.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaBip44.java index 50b9fd9c..f0c1fa3b 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaBip44.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaBip44.java @@ -29,14 +29,11 @@ public SolanaBip44(){ * @return PrivateKey */ public byte[] getPrivateKeyFromSeed(byte[] seed, DerivableType derivableType) { - switch (derivableType){ - case BIP44: - return getPrivateKeyFromBip44Seed(seed); - case BIP44CHANGE: - return getPrivateKeyFromBip44SeedWithChange(seed); - default: - throw new RuntimeException("DerivableType not supported"); - } + return switch (derivableType) { + case BIP44 -> getPrivateKeyFromBip44Seed(seed); + case BIP44CHANGE -> getPrivateKeyFromBip44SeedWithChange(seed); + default -> throw new RuntimeException("DerivableType not supported"); + }; } private byte[] getPrivateKeyFromBip44SeedWithChange(byte[] seed) { diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaCoin.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaCoin.java index 997f985f..99c1b321 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaCoin.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/SolanaCoin.java @@ -1,30 +1,34 @@ package org.p2p.solanaj.utils.bip32.wallet; +import lombok.Getter; import org.p2p.solanaj.utils.bip32.wallet.key.SolanaCurve; public class SolanaCoin { - private final SolanaCurve curve = new SolanaCurve(); - private final long coinType = 501; - private final long purpose = 44; - private final boolean alwaysHardened = true; - /** - * Get the curve + * -- GETTER -- + * Get the curve * * @return curve */ - public SolanaCurve getCurve() { - return curve; - } - + @Getter + private final SolanaCurve curve = new SolanaCurve(); /** - * get the coin type + * -- GETTER -- + * get the coin type * * @return coin type */ - public long getCoinType() { - return coinType; - } + @Getter + private final long coinType = 501; + /** + * -- GETTER -- + * get the coin purpose + * + * @return purpose + */ + @Getter + private final long purpose = 44; + private final boolean alwaysHardened = true; /** * get whether the addresses must always be hardened @@ -35,12 +39,4 @@ public boolean getAlwaysHardened() { return alwaysHardened; } - /** - * get the coin purpose - * - * @return purpose - */ - public long getPurpose() { - return purpose; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdKey.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdKey.java index 2d23d340..9cbd5c39 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdKey.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdKey.java @@ -1,5 +1,7 @@ package org.p2p.solanaj.utils.bip32.wallet.key; +import lombok.Getter; +import lombok.Setter; import org.p2p.solanaj.utils.bip32.crypto.Hash; import java.io.ByteArrayOutputStream; @@ -11,12 +13,17 @@ *

* Will probably be migrated to builder pattern. */ +@Setter public class HdKey { + @Getter private byte[] version; + @Getter private int depth; private byte[] fingerprint; private byte[] childNumber; + @Getter private byte[] chainCode; + @Getter private byte[] keyData; HdKey(byte[] version, int depth, byte[] fingerprint, byte[] childNumber, byte[] chainCode, byte[] keyData) { @@ -31,34 +38,6 @@ public class HdKey { HdKey() { } - public void setVersion(byte[] version) { - this.version = version; - } - - public void setDepth(int depth) { - this.depth = depth; - } - - public void setFingerprint(byte[] fingerprint) { - this.fingerprint = fingerprint; - } - - public void setChildNumber(byte[] childNumber) { - this.childNumber = childNumber; - } - - public void setChainCode(byte[] chainCode) { - this.chainCode = chainCode; - } - - public void setKeyData(byte[] keyData) { - this.keyData = keyData; - } - - public byte[] getChainCode() { - return chainCode; - } - /** * Get the full chain key. This is not the public/private key for the address. * @return full HD Key @@ -83,15 +62,4 @@ public byte[] getKey() { return key.toByteArray(); } - public int getDepth() { - return depth; - } - - public byte[] getKeyData() { - return keyData; - } - - public byte[] getVersion() { - return version; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPrivateKey.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPrivateKey.java index 3f972c50..848df106 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPrivateKey.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPrivateKey.java @@ -1,16 +1,14 @@ package org.p2p.solanaj.utils.bip32.wallet.key; +import lombok.Getter; +import lombok.Setter; + /** * Defines a key with a given private key */ +@Setter +@Getter public class HdPrivateKey extends HdKey { private byte[] privateKey; - public byte[] getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(byte[] privateKey) { - this.privateKey = privateKey; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPublicKey.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPublicKey.java index a7305f5e..0dfadccb 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPublicKey.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/HdPublicKey.java @@ -1,16 +1,14 @@ package org.p2p.solanaj.utils.bip32.wallet.key; +import lombok.Getter; +import lombok.Setter; + /** * Defines a key with a given public key */ +@Setter +@Getter public class HdPublicKey extends HdKey { private byte[] publicKey; - public byte[] getPublicKey() { - return publicKey; - } - - public void setPublicKey(byte[] publicKey) { - this.publicKey = publicKey; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/SolanaCurve.java b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/SolanaCurve.java index 18f891b6..82e57fc0 100644 --- a/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/SolanaCurve.java +++ b/src/main/java/org/p2p/solanaj/utils/bip32/wallet/key/SolanaCurve.java @@ -1,11 +1,11 @@ package org.p2p.solanaj.utils.bip32.wallet.key; +import lombok.Getter; + +@Getter public class SolanaCurve { private static final String ed25519Curve = "ed25519 seed"; private final String seed = SolanaCurve.ed25519Curve; - public String getSeed() { - return seed; - } } diff --git a/src/main/java/org/p2p/solanaj/ws/SignatureNotification.java b/src/main/java/org/p2p/solanaj/ws/SignatureNotification.java index 59af8557..eecc6254 100644 --- a/src/main/java/org/p2p/solanaj/ws/SignatureNotification.java +++ b/src/main/java/org/p2p/solanaj/ws/SignatureNotification.java @@ -1,16 +1,15 @@ package org.p2p.solanaj.ws; +import lombok.Getter; + +@Getter public class SignatureNotification { - private Object error; + private final Object error; public SignatureNotification(Object error) { this.error = error; } - public Object getError() { - return error; - } - public boolean hasError() { return error != null; } diff --git a/src/main/java/org/p2p/solanaj/ws/SubscriptionWebSocketClient.java b/src/main/java/org/p2p/solanaj/ws/SubscriptionWebSocketClient.java index bb833a7f..89345e86 100644 --- a/src/main/java/org/p2p/solanaj/ws/SubscriptionWebSocketClient.java +++ b/src/main/java/org/p2p/solanaj/ws/SubscriptionWebSocketClient.java @@ -22,7 +22,7 @@ public class SubscriptionWebSocketClient extends WebSocketClient { - private class SubscriptionParams { + private static class SubscriptionParams { RpcRequest request; NotificationEventListener listener; @@ -32,9 +32,9 @@ private class SubscriptionParams { } } - private Map subscriptions = new ConcurrentHashMap<>(); - private Map subscriptionIds = new ConcurrentHashMap<>(); - private Map subscriptionListeners = new ConcurrentHashMap<>(); + private final Map subscriptions = new ConcurrentHashMap<>(); + private final Map subscriptionIds = new ConcurrentHashMap<>(); + private final Map subscriptionListeners = new ConcurrentHashMap<>(); private static final Logger LOGGER = Logger.getLogger(SubscriptionWebSocketClient.class.getName()); public static SubscriptionWebSocketClient getExactPathInstance(String endpoint) { @@ -103,7 +103,7 @@ public void accountSubscribe(String key, NotificationEventListener listener) { } public void signatureSubscribe(String signature, NotificationEventListener listener) { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(signature); RpcRequest rpcRequest = new RpcRequest("signatureSubscribe", params); @@ -115,7 +115,7 @@ public void signatureSubscribe(String signature, NotificationEventListener liste } public void logsSubscribe(String mention, NotificationEventListener listener) { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(Map.of("mentions", List.of(mention))); params.add(Map.of("commitment", "finalized")); @@ -128,7 +128,7 @@ public void logsSubscribe(String mention, NotificationEventListener listener) { } public void logsSubscribe(List mentions, NotificationEventListener listener) { - List params = new ArrayList(); + List params = new ArrayList<>(); params.add(Map.of("mentions", mentions)); params.add(Map.of("commitment", "finalized")); @@ -203,7 +203,7 @@ public void onError(Exception ex) { } private void updateSubscriptions() { - if (isOpen() && subscriptions.size() > 0) { + if (isOpen() && !subscriptions.isEmpty()) { JsonAdapter rpcRequestJsonAdapter = new Moshi.Builder().build().adapter(RpcRequest.class); for (SubscriptionParams sub : subscriptions.values()) { diff --git a/src/main/java/org/p2p/solanaj/ws/listeners/AccountNotificationEventListener.java b/src/main/java/org/p2p/solanaj/ws/listeners/AccountNotificationEventListener.java index e3a5e7e8..9989bdbe 100644 --- a/src/main/java/org/p2p/solanaj/ws/listeners/AccountNotificationEventListener.java +++ b/src/main/java/org/p2p/solanaj/ws/listeners/AccountNotificationEventListener.java @@ -10,7 +10,6 @@ public class AccountNotificationEventListener implements NotificationEventListen * Handle Account notification event (change in data or change in lamports). Type of "data" is a Map. * @param data Map */ - @SuppressWarnings("rawtypes") @Override public void onNotificationEvent(Object data) { LOGGER.info("Raw = " + data); diff --git a/src/main/java/org/p2p/solanaj/ws/listeners/LogNotificationEventListener.java b/src/main/java/org/p2p/solanaj/ws/listeners/LogNotificationEventListener.java index 76bd3aca..73c02bd1 100644 --- a/src/main/java/org/p2p/solanaj/ws/listeners/LogNotificationEventListener.java +++ b/src/main/java/org/p2p/solanaj/ws/listeners/LogNotificationEventListener.java @@ -10,7 +10,7 @@ public class LogNotificationEventListener implements NotificationEventListener { private static final Logger LOGGER = Logger.getLogger(LogNotificationEventListener.class.getName()); private final RpcClient client; - private PublicKey listeningPubkey; + private final PublicKey listeningPubkey; public LogNotificationEventListener(RpcClient client, PublicKey listeningPubkey) { this.client = client; @@ -21,7 +21,6 @@ public LogNotificationEventListener(RpcClient client, PublicKey listeningPubkey) * Handle Account notification event (change in data or change in lamports). Type of "data" is a Map. * @param data Map */ - @SuppressWarnings("rawtypes") @Override public void onNotificationEvent(Object data) { if (data != null) { diff --git a/src/test/java/org/p2p/solanaj/core/AnchorTest.java b/src/test/java/org/p2p/solanaj/core/AnchorTest.java index 28a98f98..040fd088 100644 --- a/src/test/java/org/p2p/solanaj/core/AnchorTest.java +++ b/src/test/java/org/p2p/solanaj/core/AnchorTest.java @@ -25,19 +25,19 @@ public class AnchorTest extends AccountBasedTest { public void basicInitializeTest() { final Account feePayer = testAccount; - final Transaction transaction = new Transaction(); - transaction.addInstruction( + final LegacyTransaction legacyTransaction = new LegacyTransaction(); + legacyTransaction.addInstruction( AnchorBasicTutorialProgram.initialize(feePayer) ); - transaction.addInstruction( + legacyTransaction.addInstruction( MemoProgram.writeUtf8(feePayer.getPublicKey(), "I just called an Anchor program from SolanaJ.") ); final List signers = List.of(feePayer); String result = null; try { - result = client.getApi().sendTransaction(transaction, signers, null); + result = client.getApi().sendLegacyTransaction(legacyTransaction, signers, null); } catch (RpcException e) { e.printStackTrace(); } diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/LegacyMessageTest.java similarity index 79% rename from src/test/java/org/p2p/solanaj/core/MessageTest.java rename to src/test/java/org/p2p/solanaj/core/LegacyMessageTest.java index 8716223c..b5a47ced 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/LegacyMessageTest.java @@ -6,7 +6,7 @@ import static org.junit.Assert.assertArrayEquals; -public class MessageTest { +public class LegacyMessageTest { @Test public void serializeMessage() { @@ -17,10 +17,10 @@ public void serializeMessage() { Account signer = new Account(Base58 .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); - Message message = new Message(); - message.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); - message.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - message.setFeePayer(signer); + LegacyMessage legacyMessage = new LegacyMessage(); + legacyMessage.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); + legacyMessage.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); + legacyMessage.setFeePayer(signer); 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, @@ -28,7 +28,7 @@ public void serializeMessage() { 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, - 0, 0, 0, 0, 0 }, toUnsignedByteArray(message.serialize())); + 0, 0, 0, 0, 0 }, toUnsignedByteArray(legacyMessage.serialize())); } int[] toUnsignedByteArray(byte[] in) { diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/LegacyTransactionTest.java similarity index 76% rename from src/test/java/org/p2p/solanaj/core/TransactionTest.java rename to src/test/java/org/p2p/solanaj/core/LegacyTransactionTest.java index 7e8fa2bb..625605a5 100644 --- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java +++ b/src/test/java/org/p2p/solanaj/core/LegacyTransactionTest.java @@ -11,7 +11,7 @@ import org.bitcoinj.core.Base58; -public class TransactionTest { +public class LegacyTransactionTest { private final static Account signer = new Account(Base58 .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); @@ -22,11 +22,11 @@ public void signAndSerialize() { PublicKey toPublickKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5"); int lamports = 3000; - Transaction transaction = new Transaction(); - transaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); - transaction.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - transaction.sign(signer); - byte[] serializedTransaction = transaction.serialize(); + LegacyTransaction legacyTransaction = new LegacyTransaction(); + legacyTransaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); + legacyTransaction.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); + legacyTransaction.sign(signer); + byte[] serializedTransaction = legacyTransaction.serialize(); assertEquals( "ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA=", @@ -36,7 +36,7 @@ public void signAndSerialize() { @Test public void transactionBuilderTest() { final String memo = "Test memo"; - final Transaction transaction = new TransactionBuilder() + final LegacyTransaction legacyTransaction = new LegacyTransactionBuilder() .addInstruction( MemoProgram.writeUtf8( signer.getPublicKey(), @@ -49,7 +49,7 @@ public void transactionBuilderTest() { assertEquals( "AV6w4Af9PSHhNsTSal4vlPF7Su9QXgCVyfDChHImJITLcS5BlNotKFeMoGw87VwjS3eNA2JCL+MEoReynCNbWAoBAAECBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQwFSlNQ+F3IgtYUpVZyeIopbd8eq6vQpgZ4iEky9O72oMviiMGZlPAy5mIJT92z865aQ2ipBrulSCScEzmEJkX1AQEBAAlUZXN0IG1lbW8=", - Base64.getEncoder().encodeToString(transaction.serialize()) + Base64.getEncoder().encodeToString(legacyTransaction.serialize()) ); } diff --git a/src/test/java/org/p2p/solanaj/core/MainnetTest.java b/src/test/java/org/p2p/solanaj/core/MainnetTest.java index f8bdc97f..3a6828a1 100644 --- a/src/test/java/org/p2p/solanaj/core/MainnetTest.java +++ b/src/test/java/org/p2p/solanaj/core/MainnetTest.java @@ -4,7 +4,6 @@ import org.junit.Ignore; import org.junit.Test; import org.p2p.solanaj.programs.MemoProgram; -import org.p2p.solanaj.programs.SystemProgram; import org.p2p.solanaj.rpc.Cluster; import org.p2p.solanaj.rpc.RpcClient; import org.p2p.solanaj.rpc.RpcException; @@ -14,7 +13,6 @@ import org.p2p.solanaj.token.TokenManager; import java.util.*; -import java.util.stream.Collectors; import static org.junit.Assert.*; @@ -93,7 +91,7 @@ public void getAccountInfoJsonParsed() { } /** - * Calls sendTransaction with a call to the Memo program included. + * Calls sendLegacyTransaction with a call to the Memo program included. */ @Test @Ignore @@ -104,7 +102,7 @@ public void transactionMemoTest() { // Create account from private key final Account feePayer = testAccount; - final Transaction transaction = new Transaction(); + final LegacyTransaction legacyTransaction = new LegacyTransaction(); // First intruction it adds here is a small amount of SOL (like 0.000001) just to have some content in the tx // Probably not really needed @@ -117,14 +115,14 @@ public void transactionMemoTest() { // ); // Add instruction to write memo - transaction.addInstruction( + legacyTransaction.addInstruction( MemoProgram.writeUtf8(feePayer.getPublicKey(), "Twitter: skynetcap") ); - // Call sendTransaction + // Call sendLegacyTransaction String result = null; try { - result = client.getApi().sendTransaction(transaction, feePayer); + result = client.getApi().sendLegacyTransaction(legacyTransaction, feePayer); LOGGER.info("Result = " + result); } catch (RpcException e) { e.printStackTrace(); diff --git a/src/test/java/org/p2p/solanaj/programs/BPFLoaderTest.java b/src/test/java/org/p2p/solanaj/programs/BPFLoaderTest.java index 15856aba..ab68a18a 100644 --- a/src/test/java/org/p2p/solanaj/programs/BPFLoaderTest.java +++ b/src/test/java/org/p2p/solanaj/programs/BPFLoaderTest.java @@ -3,8 +3,8 @@ import org.junit.Ignore; import org.junit.Test; import org.p2p.solanaj.core.Account; +import org.p2p.solanaj.core.LegacyTransaction; import org.p2p.solanaj.core.PublicKey; -import org.p2p.solanaj.core.Transaction; import org.p2p.solanaj.rpc.Cluster; import org.p2p.solanaj.rpc.RpcClient; import org.p2p.solanaj.rpc.RpcException; @@ -30,12 +30,12 @@ public void initializeBufferTest() throws RpcException { System.out.println(account.getPublicKey().toBase58()); - Transaction transaction = new Transaction(); + LegacyTransaction legacyTransaction = new LegacyTransaction(); // initialize buffer Account bufferAccount = new Account(); - transaction.addInstruction( + legacyTransaction.addInstruction( SystemProgram.createAccount( account.getPublicKey(), bufferAccount.getPublicKey(), @@ -45,7 +45,7 @@ public void initializeBufferTest() throws RpcException { ) ); - transaction.addInstruction( + legacyTransaction.addInstruction( BPFLoader.initializeBuffer( bufferAccount.getPublicKey(), account.getPublicKey() @@ -53,9 +53,9 @@ public void initializeBufferTest() throws RpcException { ); String hash = client.getApi().getRecentBlockhash(); - transaction.setRecentBlockHash(hash); + legacyTransaction.setRecentBlockHash(hash); - System.out.println("TX: " + client.getApi().sendTransaction(transaction, List.of(account, bufferAccount), hash)); + System.out.println("TX: " + client.getApi().sendLegacyTransaction(legacyTransaction, List.of(account, bufferAccount), hash)); } } diff --git a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java b/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java deleted file mode 100644 index 12f8686e..00000000 --- a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.p2p.solanaj.utils; - -import org.junit.Test; -import static org.junit.Assert.*; - -public class ShortvecEncodingTest { - - @Test - public void encodeLength() { - assertArrayEquals(new byte[] { 0 } /* [0] */, ShortvecEncoding.encodeLength(0)); - assertArrayEquals(new byte[] { 1 } /* [1] */, ShortvecEncoding.encodeLength(1)); - assertArrayEquals(new byte[] { 5 } /* [5] */, ShortvecEncoding.encodeLength(5)); - assertArrayEquals(new byte[] { 127 } /* [0x7f] */, ShortvecEncoding.encodeLength(127)); // 0x7f - assertArrayEquals(new byte[] { -128, 1 }/* [0x80, 0x01] */, ShortvecEncoding.encodeLength(128)); // 0x80 - assertArrayEquals(new byte[] { -1, 1 } /* [0xff, 0x01] */, ShortvecEncoding.encodeLength(255)); // 0xff - assertArrayEquals(new byte[] { -128, 2 } /* [0x80, 0x02] */, ShortvecEncoding.encodeLength(256)); // 0x100 - assertArrayEquals(new byte[] { -1, -1, 1 } /* [0xff, 0xff, 0x01] */, ShortvecEncoding.encodeLength(32767)); // 0x7fff - assertArrayEquals(new byte[] { -128, -128, -128, 1 } /* [0x80, 0x80, 0x80, 0x01] */, - ShortvecEncoding.encodeLength(2097152)); // 0x200000 - } -} diff --git a/src/test/java/org/p2p/solanaj/utils/ShortvecTest.java b/src/test/java/org/p2p/solanaj/utils/ShortvecTest.java new file mode 100644 index 00000000..4b0a7175 --- /dev/null +++ b/src/test/java/org/p2p/solanaj/utils/ShortvecTest.java @@ -0,0 +1,21 @@ +package org.p2p.solanaj.utils; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class ShortvecTest { + + @Test + public void encodeLength() { + assertArrayEquals(new byte[] { 0 } /* [0] */, Shortvec.encodeLength(0)); + assertArrayEquals(new byte[] { 1 } /* [1] */, Shortvec.encodeLength(1)); + assertArrayEquals(new byte[] { 5 } /* [5] */, Shortvec.encodeLength(5)); + assertArrayEquals(new byte[] { 127 } /* [0x7f] */, Shortvec.encodeLength(127)); // 0x7f + assertArrayEquals(new byte[] { -128, 1 }/* [0x80, 0x01] */, Shortvec.encodeLength(128)); // 0x80 + assertArrayEquals(new byte[] { -1, 1 } /* [0xff, 0x01] */, Shortvec.encodeLength(255)); // 0xff + assertArrayEquals(new byte[] { -128, 2 } /* [0x80, 0x02] */, Shortvec.encodeLength(256)); // 0x100 + assertArrayEquals(new byte[] { -1, -1, 1 } /* [0xff, 0xff, 0x01] */, Shortvec.encodeLength(32767)); // 0x7fff + assertArrayEquals(new byte[] { -128, -128, -128, 1 } /* [0x80, 0x80, 0x80, 0x01] */, + Shortvec.encodeLength(2097152)); // 0x200000 + } +}