diff --git a/src/main/java/org/p2p/solanaj/core/AddressTableLookup.java b/src/main/java/org/p2p/solanaj/core/AddressTableLookup.java
new file mode 100644
index 00000000..8acad34b
--- /dev/null
+++ b/src/main/java/org/p2p/solanaj/core/AddressTableLookup.java
@@ -0,0 +1,84 @@
+package org.p2p.solanaj.core;
+
+import com.squareup.moshi.Json;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.p2p.solanaj.utils.ShortvecEncoding;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Represents an Address Lookup Table (ALT) in Solana.
+ *
+ * ALTs allow transactions to reference additional addresses required for execution,
+ * enabling transactions that exceed the maximum number of accounts.
+ *
+ */
+@AllArgsConstructor
+public class AddressTableLookup {
+ @Json(name = "accountKey")
+ private String accountKey;
+
+ @Getter
+ @Json(name = "writableIndexes")
+ private List writableIndexes;
+
+ @Getter
+ @Json(name = "readonlyIndexes")
+ private List readonlyIndexes;
+
+ /**
+ * Constructs an AddressTableLookup with the given parameters.
+ *
+ * @param accountKey The public key of the address table
+ * @param writableIndexes The list of writable indexes
+ * @param readonlyIndexes The list of readonly indexes
+ */
+ public AddressTableLookup(PublicKey accountKey, List writableIndexes, List readonlyIndexes) {
+ this.accountKey = accountKey.toBase58();
+ this.writableIndexes = writableIndexes;
+ this.readonlyIndexes = readonlyIndexes;
+ }
+
+ /**
+ * Gets the account key as a PublicKey object.
+ *
+ * @return the account key
+ */
+ public PublicKey getAccountKey() {
+ return new PublicKey(accountKey);
+ }
+
+ /**
+ * Calculates the serialized size of the AddressTableLookup.
+ *
+ * @return the size in bytes
+ */
+ public int getSerializedSize() {
+ return 32 + // PublicKey size
+ ShortvecEncoding.decodeLength(writableIndexes) +
+ writableIndexes.size() +
+ ShortvecEncoding.decodeLength(readonlyIndexes) +
+ readonlyIndexes.size();
+ }
+
+ /**
+ * Serializes the AddressTableLookup into a byte array.
+ *
+ * @return the serialized byte array
+ */
+ public byte[] serialize() {
+ ByteBuffer buffer = ByteBuffer.allocate(getSerializedSize());
+ buffer.put(getAccountKey().toByteArray());
+ buffer.put(ShortvecEncoding.encodeLength(writableIndexes.size()));
+ for (Byte index : writableIndexes) {
+ buffer.put(index.byteValue());
+ }
+ buffer.put(ShortvecEncoding.encodeLength(readonlyIndexes.size()));
+ for (Byte index : readonlyIndexes) {
+ buffer.put(index.byteValue());
+ }
+ return buffer.array();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java
index 1c5bbda0..d9932b35 100644
--- a/src/main/java/org/p2p/solanaj/core/Message.java
+++ b/src/main/java/org/p2p/solanaj/core/Message.java
@@ -42,10 +42,12 @@ int getLength() {
private AccountKeysList accountKeys;
private List instructions;
private Account feePayer;
+ private List addressTableLookups;
public Message() {
this.accountKeys = new AccountKeysList();
this.instructions = new ArrayList();
+ this.addressTableLookups = new ArrayList<>();
}
public Message addInstruction(TransactionInstruction instruction) {
@@ -140,6 +142,15 @@ public byte[] serialize() {
out.put(compiledInstruction.data);
}
+ // Serialize address table lookups if present
+ if (!addressTableLookups.isEmpty()) {
+ byte[] addressTableLookupsLength = ShortvecEncoding.encodeLength(addressTableLookups.size());
+ out.put(addressTableLookupsLength);
+ for (AddressTableLookup lookup : addressTableLookups) {
+ out.put(lookup.serialize());
+ }
+ }
+
return out.array();
}
@@ -178,4 +189,12 @@ private int findAccountIndex(List accountMetaList, PublicKey key) {
throw new RuntimeException("unable to find account index");
}
+
+ public void addAddressTableLookup(AddressTableLookup lookup) {
+ addressTableLookups.add(lookup);
+ }
+
+ public List getAddressTableLookups() {
+ return addressTableLookups;
+ }
}
diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java
index bab10710..aeeba1e8 100644
--- a/src/main/java/org/p2p/solanaj/core/Transaction.java
+++ b/src/main/java/org/p2p/solanaj/core/Transaction.java
@@ -2,17 +2,21 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import lombok.Getter;
+import lombok.Setter;
import org.bitcoinj.core.Base58;
import org.p2p.solanaj.utils.ShortvecEncoding;
import org.p2p.solanaj.utils.TweetNaclFast;
/**
* Represents a Solana transaction.
+ *
* This class allows for building, signing, and serializing transactions.
+ * It supports both legacy and versioned transactions with Address Lookup Tables (ALTs).
+ *
*/
public class Transaction {
@@ -22,12 +26,18 @@ public class Transaction {
private final List signatures;
private byte[] serializedMessage;
+ @Getter
+ @Setter
+ private byte version = (byte) 0xFF; // Default to legacy version
+
+ private final List addressTableLookups = new ArrayList<>();
+
/**
* Constructs a new Transaction instance.
*/
public Transaction() {
this.message = new Message();
- this.signatures = new ArrayList<>(); // Use diamond operator
+ this.signatures = new ArrayList<>();
}
/**
@@ -38,7 +48,7 @@ public Transaction() {
* @throws NullPointerException if the instruction is null
*/
public Transaction addInstruction(TransactionInstruction instruction) {
- Objects.requireNonNull(instruction, "Instruction cannot be null"); // Add input validation
+ Objects.requireNonNull(instruction, "Instruction cannot be null");
message.addInstruction(instruction);
return this;
}
@@ -50,7 +60,7 @@ public Transaction addInstruction(TransactionInstruction instruction) {
* @throws NullPointerException if the recentBlockhash is null
*/
public void setRecentBlockHash(String recentBlockhash) {
- Objects.requireNonNull(recentBlockhash, "Recent blockhash cannot be null"); // Add input validation
+ Objects.requireNonNull(recentBlockhash, "Recent blockhash cannot be null");
message.setRecentBlockHash(recentBlockhash);
}
@@ -61,7 +71,7 @@ public void setRecentBlockHash(String recentBlockhash) {
* @throws NullPointerException if the signer is null
*/
public void sign(Account signer) {
- sign(Arrays.asList(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation
+ sign(List.of(Objects.requireNonNull(signer, "Signer cannot be null")));
}
/**
@@ -86,7 +96,7 @@ public void sign(List signers) {
byte[] signature = signatureProvider.detached(serializedMessage);
signatures.add(Base58.encode(signature));
} catch (Exception e) {
- throw new RuntimeException("Error signing transaction", e); // Improve exception handling
+ throw new RuntimeException("Error signing transaction", e);
}
}
}
@@ -97,13 +107,26 @@ public void sign(List signers) {
* @return The serialized transaction as a byte array
*/
public byte[] serialize() {
+ byte[] serializedMessage = message.serialize();
int signaturesSize = signatures.size();
byte[] signaturesLength = ShortvecEncoding.encodeLength(signaturesSize);
- // Calculate total size before allocating ByteBuffer
int totalSize = signaturesLength.length + signaturesSize * SIGNATURE_LENGTH + serializedMessage.length;
+ if (version != (byte) 0xFF) {
+ totalSize += 1; // Add 1 byte for version
+ if (version == 0) {
+ totalSize += ShortvecEncoding.encodeLength(addressTableLookups.size()).length;
+ for (AddressTableLookup lookup : addressTableLookups) {
+ totalSize += lookup.getSerializedSize();
+ }
+ }
+ }
+
ByteBuffer out = ByteBuffer.allocate(totalSize);
+ if (version != (byte) 0xFF) {
+ out.put(version);
+ }
out.put(signaturesLength);
for (String signature : signatures) {
@@ -113,6 +136,25 @@ public byte[] serialize() {
out.put(serializedMessage);
+ if (version == 0) {
+ byte[] addressTableLookupsLength = ShortvecEncoding.encodeLength(addressTableLookups.size());
+ out.put(addressTableLookupsLength);
+ for (AddressTableLookup lookup : addressTableLookups) {
+ out.put(lookup.serialize());
+ }
+ }
+
return out.array();
}
-}
+
+ /**
+ * Adds an address table lookup to the transaction.
+ *
+ * @param tablePubkey The public key of the address table
+ * @param writableIndexes The list of writable indexes
+ * @param readonlyIndexes The list of readonly indexes
+ */
+ public void addAddressTableLookup(PublicKey tablePubkey, List writableIndexes, List readonlyIndexes) {
+ addressTableLookups.add(new AddressTableLookup(tablePubkey, writableIndexes, readonlyIndexes));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java b/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java
index 2841ba52..4c3a6870 100644
--- a/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java
+++ b/src/main/java/org/p2p/solanaj/core/TransactionBuilder.java
@@ -1,17 +1,19 @@
package org.p2p.solanaj.core;
+import org.p2p.solanaj.programs.AddressLookupTableProgram;
+
import java.util.List;
import java.util.Objects;
/**
- * Builder for constructing {@link Transaction} objects to be used in sendTransaction.
+ * Builder class for constructing Transactions.
*/
public class TransactionBuilder {
private final Transaction transaction;
/**
- * Constructs a new TransactionBuilder.
+ * Constructs a new TransactionBuilder instance.
*/
public TransactionBuilder() {
this.transaction = new Transaction();
@@ -44,25 +46,24 @@ public TransactionBuilder addInstructions(List instructi
}
/**
- * Sets the recent block hash for the transaction.
+ * Sets the recent blockhash for the transaction.
*
- * @param recentBlockHash the recent block hash to set
- * @return this builder for method chaining
- * @throws NullPointerException if recentBlockHash is null
+ * @param recentBlockhash The recent blockhash to set
+ * @return This builder for method chaining
+ * @throws NullPointerException if the recentBlockhash is null
*/
- public TransactionBuilder setRecentBlockHash(String recentBlockHash) {
- Objects.requireNonNull(recentBlockHash, "Recent block hash cannot be null");
- transaction.setRecentBlockHash(recentBlockHash);
+ public TransactionBuilder setRecentBlockHash(String recentBlockhash) {
+ transaction.setRecentBlockHash(recentBlockhash);
return this;
}
/**
- * Sets the signers for the transaction and signs it.
+ * Sets the fee payer and signs the transaction with the provided signers.
*
- * @param signers the list of signers
- * @return this builder for method chaining
- * @throws NullPointerException if signers is null
- * @throws IllegalArgumentException if signers is empty
+ * @param signers The list of signers; the first signer is the fee payer
+ * @return This builder for method chaining
+ * @throws NullPointerException if the signers list is null
+ * @throws IllegalArgumentException if the signers list is empty
*/
public TransactionBuilder setSigners(List signers) {
Objects.requireNonNull(signers, "Signers list cannot be null");
@@ -73,6 +74,30 @@ public TransactionBuilder setSigners(List signers) {
return this;
}
+ /**
+ * Sets the version for the transaction.
+ *
+ * @param version the version to set
+ * @return this builder for method chaining
+ */
+ public TransactionBuilder setVersion(byte version) {
+ transaction.setVersion(version);
+ return this;
+ }
+
+ /**
+ * Adds an address table lookup to the transaction.
+ *
+ * @param tablePubkey the public key of the address table
+ * @param writableIndexes the list of writable indexes
+ * @param readonlyIndexes the list of readonly indexes
+ * @return this builder for method chaining
+ */
+ public TransactionBuilder addAddressTableLookup(PublicKey tablePubkey, List writableIndexes, List readonlyIndexes) {
+ transaction.addAddressTableLookup(tablePubkey, writableIndexes, readonlyIndexes);
+ return this;
+ }
+
/**
* Builds and returns the constructed Transaction object.
*
@@ -81,5 +106,4 @@ public TransactionBuilder setSigners(List signers) {
public Transaction build() {
return transaction;
}
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/p2p/solanaj/programs/AddressLookupTableProgram.java b/src/main/java/org/p2p/solanaj/programs/AddressLookupTableProgram.java
index d38ffc1d..14e0ef52 100644
--- a/src/main/java/org/p2p/solanaj/programs/AddressLookupTableProgram.java
+++ b/src/main/java/org/p2p/solanaj/programs/AddressLookupTableProgram.java
@@ -26,14 +26,17 @@ public class AddressLookupTableProgram extends Program {
/**
* Creates an instruction to create a new address lookup table.
*
- * @param authority The authority (signer) that can modify the table
- * @param payer The account paying for the table creation
- * @param recentSlot A recent slot to derive the table's address
+ * @param authority The authority (signer) that can modify the table
+ * @param payer The account paying for the table creation
+ * @param recentSlot A recent slot to derive the table's address
* @return A TransactionInstruction to create a new address lookup table
*/
public static TransactionInstruction createLookupTable(PublicKey authority, PublicKey payer, long recentSlot) {
PublicKey derivedAddress = PublicKey.findProgramAddress(
- List.of(authority.toByteArray(), ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(recentSlot).array()),
+ List.of(
+ authority.toByteArray(),
+ ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(recentSlot).array()
+ ),
PROGRAM_ID
).getAddress();
@@ -55,7 +58,7 @@ public static TransactionInstruction createLookupTable(PublicKey authority, Publ
* Creates an instruction to freeze an address lookup table.
*
* @param lookupTable The address of the lookup table to freeze
- * @param authority The authority (signer) of the lookup table
+ * @param authority The authority (signer) of the lookup table
* @return A TransactionInstruction to freeze an address lookup table
*/
public static TransactionInstruction freezeLookupTable(PublicKey lookupTable, PublicKey authority) {
@@ -73,17 +76,16 @@ public static TransactionInstruction freezeLookupTable(PublicKey lookupTable, Pu
* Creates an instruction to extend an address lookup table.
*
* @param lookupTable The address of the lookup table to extend
- * @param authority The authority (signer) of the lookup table
- * @param payer The account paying for the table extension
- * @param addresses The list of addresses to add to the table
+ * @param payer The account paying for the table extension
+ * @param authority The authority (signer) of the lookup table
+ * @param addresses The list of addresses to add to the table
* @return A TransactionInstruction to extend an address lookup table
*/
- public static TransactionInstruction extendLookupTable(PublicKey lookupTable, PublicKey authority, PublicKey payer, List addresses) {
+ public static TransactionInstruction extendLookupTable(PublicKey lookupTable, PublicKey payer, PublicKey authority, List addresses) {
List keys = new ArrayList<>();
keys.add(new AccountMeta(lookupTable, false, true));
- keys.add(new AccountMeta(authority, true, false));
keys.add(new AccountMeta(payer, true, true));
- keys.add(new AccountMeta(SystemProgram.PROGRAM_ID, false, false));
+ keys.add(new AccountMeta(authority, true, false));
ByteBuffer data = ByteBuffer.allocate(1 + 4 + addresses.size() * 32);
data.order(ByteOrder.LITTLE_ENDIAN);
@@ -100,7 +102,7 @@ public static TransactionInstruction extendLookupTable(PublicKey lookupTable, Pu
* Creates an instruction to deactivate an address lookup table.
*
* @param lookupTable The address of the lookup table to deactivate
- * @param authority The authority (signer) of the lookup table
+ * @param authority The authority (signer) of the lookup table
* @return A TransactionInstruction to deactivate an address lookup table
*/
public static TransactionInstruction deactivateLookupTable(PublicKey lookupTable, PublicKey authority) {
@@ -118,8 +120,8 @@ public static TransactionInstruction deactivateLookupTable(PublicKey lookupTable
* Creates an instruction to close an address lookup table.
*
* @param lookupTable The address of the lookup table to close
- * @param authority The authority (signer) of the lookup table
- * @param recipient The account to receive the closed table's lamports
+ * @param authority The authority (signer) of the lookup table
+ * @param recipient The account to receive the closed table's lamports
* @return A TransactionInstruction to close an address lookup table
*/
public static TransactionInstruction closeLookupTable(PublicKey lookupTable, PublicKey authority, PublicKey recipient) {
diff --git a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedTransaction.java b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedTransaction.java
index 60c1e765..00980cfc 100644
--- a/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedTransaction.java
+++ b/src/main/java/org/p2p/solanaj/rpc/types/ConfirmedTransaction.java
@@ -1,6 +1,10 @@
package org.p2p.solanaj.rpc.types;
+import org.p2p.solanaj.core.AddressTableLookup;
+import org.p2p.solanaj.core.PublicKey;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.Collections;
import com.squareup.moshi.Json;
import lombok.Getter;
@@ -42,8 +46,14 @@ public static class Instruction {
@ToString
public static class Message {
+ @Json(name = "version")
+ private byte version;
+
+ @Json(name = "addressTableLookups")
+ private List addressTableLookups;
+
@Json(name = "accountKeys")
- private List accountKeys;
+ private List accountKeyStrings;
@Json(name = "header")
private Header header;
@@ -53,6 +63,24 @@ public static class Message {
@Json(name = "recentBlockhash")
private String recentBlockhash;
+
+ /**
+ * Gets the list of account keys involved in the transaction as PublicKey objects.
+ * @return The list of account keys as PublicKey objects.
+ */
+ public List getAccountKeys() {
+ return accountKeyStrings.stream()
+ .map(PublicKey::new)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Gets the list of address table lookups.
+ * @return The list of address table lookups.
+ */
+ public List getAddressTableLookups() {
+ return addressTableLookups != null ? addressTableLookups : Collections.emptyList();
+ }
}
@Getter
@@ -131,4 +159,8 @@ public static class Transaction {
@Json(name = "transaction")
private Transaction transaction;
+
+ public Transaction getTransaction() {
+ return transaction;
+ }
}
diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java
index 4a1d9080..dc74dec8 100644
--- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java
+++ b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java
@@ -1,5 +1,8 @@
package org.p2p.solanaj.utils;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.bitcoinj.core.Utils.*;
public class ShortvecEncoding {
@@ -27,4 +30,19 @@ public static byte[] encodeLength(int len) {
return bytes;
}
+
+ public static int decodeLength(List dataBytesList) {
+ List dataBytes = new ArrayList<>(dataBytesList);
+ int len = 0;
+ int size = 0;
+ for (;;) {
+ int elem = (int) dataBytes.remove(0);
+ len |= (elem & 0x7f) << (size * 7);
+ size += 1;
+ if ((elem & 0x80) == 0) {
+ break;
+ }
+ }
+ return len;
+ }
}
diff --git a/src/test/java/org/p2p/solanaj/core/MainnetTest.java b/src/test/java/org/p2p/solanaj/core/MainnetTest.java
index 98dcf242..fd13815d 100644
--- a/src/test/java/org/p2p/solanaj/core/MainnetTest.java
+++ b/src/test/java/org/p2p/solanaj/core/MainnetTest.java
@@ -323,6 +323,8 @@ public void getSlotLeadersTest() throws RpcException {
}
@Test
+ @Disabled
+ @Deprecated
public void getSnapshotSlotTest() throws RpcException {
long snapshotSlot = client.getApi().getSnapshotSlot();
LOGGER.info(String.format("Snapshot slot = %d", snapshotSlot));
@@ -455,6 +457,8 @@ public void getFeeCalculatorForBlockhashTest() throws RpcException, InterruptedE
}
@Test
+ @Disabled
+ @Deprecated
public void getFeesRateGovernorTest() throws RpcException {
FeeRateGovernorInfo feeRateGovernorInfo = client.getApi().getFeeRateGovernor();
LOGGER.info(feeRateGovernorInfo.getValue().getFeeRateGovernor().toString());
@@ -468,6 +472,8 @@ public void getFeesRateGovernorTest() throws RpcException {
}
@Test
+ @Disabled
+ @Deprecated
public void getFeesInfoTest() throws RpcException {
FeesInfo feesInfo = client.getApi().getFees();
LOGGER.info(feesInfo.toString());
@@ -507,6 +513,8 @@ public void getMinimumBalanceForRentExemptionTest() throws RpcException {
}
@Test
+ @Disabled
+ @Deprecated
public void getRecentBlockhashTest() throws RpcException {
String recentBlockhash = client.getApi().getRecentBlockhash();
LOGGER.info(String.format("Recent blockhash = %s", recentBlockhash));
@@ -762,24 +770,24 @@ public void getTransactionTest() throws RpcException {
ConfirmedTransaction transactionInfo = client.getApi().getTransaction(transactionSignature);
- String fromKey = transactionInfo.getTransaction().getMessage().getAccountKeys().get(0);
- String toKey = transactionInfo.getTransaction().getMessage().getAccountKeys().get(1);
+ PublicKey fromKey = transactionInfo.getTransaction().getMessage().getAccountKeys().get(0);
+ PublicKey toKey = transactionInfo.getTransaction().getMessage().getAccountKeys().get(1);
- assertEquals("HHntUXQbUBdx8HZQQaT7W1ZSgKRitMtForz4YJXc6qF6", fromKey);
- assertEquals("6QcgNYEqHeUohoJWR5ppuRg9Ugh6scMzJY4j4tFnrZMu", toKey);
+ assertEquals("HHntUXQbUBdx8HZQQaT7W1ZSgKRitMtForz4YJXc6qF6", fromKey.toBase58());
+ assertEquals("6QcgNYEqHeUohoJWR5ppuRg9Ugh6scMzJY4j4tFnrZMu", toKey.toBase58());
ConfirmedTransaction transactionInfoCommitted = client.getApi().getTransaction(transactionSignature, Commitment.CONFIRMED);
- String fromKeyCommitted = transactionInfoCommitted.getTransaction().getMessage().getAccountKeys().get(0);
- String toKeyCommitted = transactionInfoCommitted.getTransaction().getMessage().getAccountKeys().get(1);
+ PublicKey fromKeyCommitted = transactionInfoCommitted.getTransaction().getMessage().getAccountKeys().get(0);
+ PublicKey toKeyCommitted = transactionInfoCommitted.getTransaction().getMessage().getAccountKeys().get(1);
- assertEquals("HHntUXQbUBdx8HZQQaT7W1ZSgKRitMtForz4YJXc6qF6", fromKeyCommitted);
- assertEquals("6QcgNYEqHeUohoJWR5ppuRg9Ugh6scMzJY4j4tFnrZMu", toKeyCommitted);
+ assertEquals("HHntUXQbUBdx8HZQQaT7W1ZSgKRitMtForz4YJXc6qF6", fromKeyCommitted.toBase58());
+ assertEquals("6QcgNYEqHeUohoJWR5ppuRg9Ugh6scMzJY4j4tFnrZMu", toKeyCommitted.toBase58());
}
@Test
public void isBlockhashValidTest() throws RpcException, InterruptedException {
- String recentBlockHash = client.getApi().getRecentBlockhash();
+ String recentBlockHash = client.getApi().getLatestBlockhash().getValue().getBlockhash();
Thread.sleep(500L);
assertTrue(client.getApi().isBlockhashValid(recentBlockHash));
}
diff --git a/src/test/java/org/p2p/solanaj/core/TransactionBuilderTest.java b/src/test/java/org/p2p/solanaj/core/TransactionBuilderTest.java
new file mode 100644
index 00000000..2438b1b5
--- /dev/null
+++ b/src/test/java/org/p2p/solanaj/core/TransactionBuilderTest.java
@@ -0,0 +1,150 @@
+package org.p2p.solanaj.core;
+
+import org.bitcoinj.core.Base58;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.p2p.solanaj.programs.SystemProgram;
+import org.p2p.solanaj.programs.MemoProgram;
+import org.p2p.solanaj.rpc.Cluster;
+import org.p2p.solanaj.rpc.RpcApi;
+import org.p2p.solanaj.rpc.RpcClient;
+import org.p2p.solanaj.rpc.RpcException;
+import org.p2p.solanaj.rpc.types.ConfirmedTransaction;
+
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+
+/**
+ * Unit tests for TransactionBuilder.
+ */
+public class TransactionBuilderTest {
+
+ private static final Account signer = new Account(Base58.decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs"));
+
+ /**
+ * Tests building a legacy transaction using TransactionBuilder.
+ */
+ @Test
+ public void buildLegacyTransaction() {
+ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ int lamports = 5000;
+
+ Transaction transaction = new TransactionBuilder()
+ .addInstruction(SystemProgram.transfer(fromPublicKey, toPublicKey, lamports))
+ .setRecentBlockHash("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5")
+ .setSigners(List.of(signer))
+ .build();
+
+ byte[] serialized = transaction.serialize();
+ String serializedBase64 = Base64.getEncoder().encodeToString(serialized);
+
+ assertNotNull(serializedBase64);
+ assertFalse(serializedBase64.isEmpty());
+
+ // Additional assertions can be added based on expected serialized output
+ }
+
+ /**
+ * Tests adding an instruction to the TransactionBuilder.
+ */
+ @Test
+ public void addInstructionTest() {
+ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ int lamports = 10000;
+
+ TransactionInstruction transferInstruction = SystemProgram.transfer(fromPublicKey, toPublicKey, lamports);
+ TransactionInstruction memoInstruction = MemoProgram.writeUtf8(signer.getPublicKey(), "Test Memo");
+
+ Transaction transaction = new TransactionBuilder()
+ .addInstruction(transferInstruction)
+ .addInstruction(memoInstruction)
+ .setRecentBlockHash("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo")
+ .setSigners(List.of(signer))
+ .build();
+
+ byte[] serialized = transaction.serialize();
+ String serializedBase64 = Base64.getEncoder().encodeToString(serialized);
+
+ assertNotNull(serializedBase64);
+ assertFalse(serializedBase64.isEmpty());
+
+ // Example assertions to verify both instructions are included
+ // These would need to match the expected serialized format
+ }
+
+ /**
+ * Tests building a versioned transaction with Address Lookup Tables using TransactionBuilder.
+ */
+ @Test
+ public void buildV0TransactionWithALT() {
+ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ int lamports = 7000;
+
+ PublicKey lookupTableAddress = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
+ List writableIndexes = Arrays.asList((byte) 0, (byte) 1);
+ List readonlyIndexes = Arrays.asList((byte) 2);
+
+ Transaction transaction = new TransactionBuilder()
+ .addInstruction(SystemProgram.transfer(fromPublicKey, toPublicKey, lamports))
+ .setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn")
+ .setSigners(List.of(signer))
+ .setVersion((byte) 0)
+ .addAddressTableLookup(lookupTableAddress, writableIndexes, readonlyIndexes)
+ .build();
+
+ byte[] serializedTransaction = transaction.serialize();
+
+ // Assert that the serialized transaction starts with version 0
+ assertEquals(0, serializedTransaction[0]);
+
+ // Verify the presence of address table lookup data
+ assertTrue(serializedTransaction.length > 200); // Approximate length check
+ }
+
+ /**
+ * Tests retrieving a version 0 transaction with an Address Lookup Table from the RPC API.
+ *
+ * @throws RpcException if there is an error during the RPC call.
+ */
+ @Test
+ public void testRetrieveV0TransactionWithALT() throws RpcException {
+ RpcClient rpcClient = new RpcClient(Cluster.MAINNET);
+ RpcApi api = rpcClient.getApi();
+
+ // This is a known v0 transaction with ALT on mainnet
+ String signature = "3t4B38bCZWRxYktRjMEmzE6YdyaZaq2rX74QUHGU5sSxQmxsTL2guuQ6Nf9cfsQFavhpJNJDeDK6D9MKx3ojTw16";
+
+ ConfirmedTransaction confirmedTx = api.getTransaction(signature);
+ assertNotNull(confirmedTx);
+
+ ConfirmedTransaction.Message message = confirmedTx.getTransaction().getMessage();
+ assertNotNull(message);
+
+ // Verify that this is a v0 transaction
+ assertEquals(0, message.getVersion());
+
+ // Verify the presence of account keys
+ List accountKeyStrings = message.getAccountKeyStrings();
+ assertNotNull(accountKeyStrings);
+ assertFalse(accountKeyStrings.isEmpty());
+
+ // Verify the presence of address table lookups
+ List addressTableLookups = message.getAddressTableLookups();
+ assertNotNull(addressTableLookups);
+ assertFalse(addressTableLookups.isEmpty());
+
+ // Verify the first address table lookup
+ AddressTableLookup firstLookup = addressTableLookups.get(0);
+ assertNotNull(firstLookup);
+ assertNotNull(firstLookup.getAccountKey());
+ assertFalse(firstLookup.getWritableIndexes().isEmpty());
+ assertFalse(firstLookup.getReadonlyIndexes().isEmpty());
+
+ // Additional validations or logs can be added here
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/TransactionTest.java
index eae14086..65b240bb 100644
--- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java
+++ b/src/test/java/org/p2p/solanaj/core/TransactionTest.java
@@ -1,38 +1,52 @@
package org.p2p.solanaj.core;
+import org.bitcoinj.core.Base58;
+import org.junit.jupiter.api.Test;
import org.p2p.solanaj.programs.MemoProgram;
import org.p2p.solanaj.programs.SystemProgram;
+import org.p2p.solanaj.rpc.Cluster;
+import org.p2p.solanaj.rpc.RpcApi;
+import org.p2p.solanaj.rpc.RpcClient;
+import org.p2p.solanaj.rpc.RpcException;
+import org.p2p.solanaj.rpc.types.ConfirmedTransaction;
-import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
+import java.util.Arrays;
import java.util.Base64;
import java.util.List;
-import org.bitcoinj.core.Base58;
-
+/**
+ * Unit tests for Transaction.
+ */
public class TransactionTest {
- private final static Account signer = new Account(Base58
- .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs"));
+ private static final Account signer = new Account(Base58.decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs"));
+ /**
+ * Tests signing and serializing a transaction.
+ */
@Test
public void signAndSerialize() {
PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
- PublicKey toPublickKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
int lamports = 3000;
Transaction transaction = new Transaction();
- transaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports));
+ transaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublicKey, lamports));
transaction.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn");
transaction.sign(signer);
byte[] serializedTransaction = transaction.serialize();
assertEquals(
"ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA=",
- Base64.getEncoder().encodeToString(serializedTransaction));
+ Base64.getEncoder().encodeToString(serializedTransaction)
+ );
}
+ /**
+ * Tests building a transaction with the TransactionBuilder.
+ */
@Test
public void transactionBuilderTest() {
final String memo = "Test memo";
@@ -53,4 +67,105 @@ public void transactionBuilderTest() {
);
}
-}
+ /**
+ * Tests creating a version 0 transaction with an Address Lookup Table.
+ */
+ @Test
+ public void testV0TransactionWithAddressLookupTable() {
+ Transaction transaction = new Transaction();
+ transaction.setVersion((byte) 0);
+
+ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ int lamports = 3000;
+
+ transaction.addInstruction(SystemProgram.transfer(fromPublicKey, toPublicKey, lamports));
+ transaction.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn");
+
+ PublicKey lookupTableAddress = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
+ List writableIndexes = Arrays.asList((byte) 0, (byte) 1);
+ List readonlyIndexes = Arrays.asList((byte) 2);
+ transaction.addAddressTableLookup(lookupTableAddress, writableIndexes, readonlyIndexes);
+
+ transaction.sign(signer);
+ byte[] serializedTransaction = transaction.serialize();
+
+ // Assert that the serialized transaction starts with version 0
+ assertEquals(0, serializedTransaction[0]);
+
+ // Verify the presence of address table lookup data
+ assertTrue(serializedTransaction.length > 200); // Approximate length check
+ }
+
+ /**
+ * Tests building a version 0 transaction using TransactionBuilder with an Address Lookup Table.
+ */
+ @Test
+ public void testV0TransactionBuilder() {
+ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey toPublicKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ int lamports = 3000;
+
+ PublicKey lookupTableAddress = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
+ List writableIndexes = Arrays.asList((byte) 0, (byte) 1);
+ List readonlyIndexes = Arrays.asList((byte) 2);
+
+ Transaction transaction = new TransactionBuilder()
+ .addInstruction(SystemProgram.transfer(fromPublicKey, toPublicKey, lamports))
+ .setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn")
+ .setSigners(List.of(signer))
+ .setVersion((byte) 0)
+ .addAddressTableLookup(lookupTableAddress, writableIndexes, readonlyIndexes)
+ .build();
+
+ byte[] serializedTransaction = transaction.serialize();
+
+ // Assert that the serialized transaction starts with version 0
+ assertEquals(0, serializedTransaction[0]);
+
+ // Verify the presence of address table lookup data
+ assertTrue(serializedTransaction.length > 200); // Approximate length check
+ }
+
+ /**
+ * Tests retrieving a version 0 transaction with an Address Lookup Table from the RPC API.
+ *
+ * @throws RpcException if there is an error during the RPC call.
+ */
+ @Test
+ public void testRetrieveV0TransactionWithALT() throws RpcException {
+ RpcClient rpcClient = new RpcClient(Cluster.MAINNET);
+ RpcApi api = rpcClient.getApi();
+
+ // This is a known v0 transaction with ALT on mainnet
+ String signature = "3t4B38bCZWRxYktRjMEmzE6YdyaZaq2rX74QUHGU5sSxQmxsTL2guuQ6Nf9cfsQFavhpJNJDeDK6D9MKx3ojTw16";
+
+ ConfirmedTransaction confirmedTx = api.getTransaction(signature);
+ assertNotNull(confirmedTx);
+
+ ConfirmedTransaction.Message message = confirmedTx.getTransaction().getMessage();
+ assertNotNull(message);
+
+ // Verify that this is a v0 transaction
+ assertEquals(0, message.getVersion());
+
+ // Verify the presence of account keys
+ List accountKeyStrings = message.getAccountKeyStrings();
+ assertNotNull(accountKeyStrings);
+ assertFalse(accountKeyStrings.isEmpty());
+
+ // Verify the presence of address table lookups
+ List addressTableLookups = message.getAddressTableLookups();
+ assertNotNull(addressTableLookups);
+ assertFalse(addressTableLookups.isEmpty());
+
+ // Verify the first address table lookup
+ AddressTableLookup firstLookup = addressTableLookups.get(0);
+ assertNotNull(firstLookup);
+ assertNotNull(firstLookup.getAccountKey());
+ assertFalse(firstLookup.getWritableIndexes().isEmpty());
+ assertFalse(firstLookup.getReadonlyIndexes().isEmpty());
+
+ // Additional validations or logs can be added here
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/p2p/solanaj/programs/AddressLookupTableProgramTest.java b/src/test/java/org/p2p/solanaj/programs/AddressLookupTableProgramTest.java
index 75871ffd..4bf6674e 100644
--- a/src/test/java/org/p2p/solanaj/programs/AddressLookupTableProgramTest.java
+++ b/src/test/java/org/p2p/solanaj/programs/AddressLookupTableProgramTest.java
@@ -3,27 +3,39 @@
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
+import org.p2p.solanaj.core.AccountMeta;
import org.p2p.solanaj.core.PublicKey;
import org.p2p.solanaj.core.TransactionInstruction;
-import java.util.Collections;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+/**
+ * Unit tests for AddressLookupTableProgram.
+ */
public class AddressLookupTableProgramTest {
- private static final PublicKey AUTHORITY = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
- private static final PublicKey PAYER = new PublicKey("11111111111111111111111111111111");
- private static final PublicKey LOOKUP_TABLE = new PublicKey("AddressLookupTab1e1111111111111111111111111");
- private static final long RECENT_SLOT = 123456;
-
/**
* Test for creating a lookup table.
*/
@Test
public void testCreateLookupTable() {
- TransactionInstruction instruction = AddressLookupTableProgram.createLookupTable(AUTHORITY, PAYER, RECENT_SLOT);
+ PublicKey authority = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey payer = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ long recentSlot = 123456789L;
+
+ TransactionInstruction instruction = AddressLookupTableProgram.createLookupTable(authority, payer, recentSlot);
assertNotNull(instruction);
assertEquals(AddressLookupTableProgram.PROGRAM_ID, instruction.getProgramId());
assertEquals(4, instruction.getKeys().size()); // Check number of keys
+
+ // Validate data
+ byte[] expectedData = new byte[9];
+ expectedData[0] = 0; // CREATE_LOOKUP_TABLE
+ ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(recentSlot);
+ System.arraycopy(buffer.array(), 0, expectedData, 1, 8);
+ assertArrayEquals(expectedData, instruction.getData());
}
/**
@@ -31,10 +43,17 @@ public void testCreateLookupTable() {
*/
@Test
public void testFreezeLookupTable() {
- TransactionInstruction instruction = AddressLookupTableProgram.freezeLookupTable(LOOKUP_TABLE, AUTHORITY);
+ PublicKey authority = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ PublicKey lookupTable = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
+
+ TransactionInstruction instruction = AddressLookupTableProgram.freezeLookupTable(lookupTable, authority);
assertNotNull(instruction);
assertEquals(AddressLookupTableProgram.PROGRAM_ID, instruction.getProgramId());
assertEquals(2, instruction.getKeys().size()); // Check number of keys
+
+ // Validate data
+ byte[] expectedData = new byte[]{1}; // FREEZE_LOOKUP_TABLE
+ assertArrayEquals(expectedData, instruction.getData());
}
/**
@@ -42,11 +61,30 @@ public void testFreezeLookupTable() {
*/
@Test
public void testExtendLookupTable() {
- PublicKey addressToAdd = new PublicKey("SysvarC1ock11111111111111111111111111111111");
- TransactionInstruction instruction = AddressLookupTableProgram.extendLookupTable(LOOKUP_TABLE, AUTHORITY, PAYER, Collections.singletonList(addressToAdd));
+ PublicKey lookupTable = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+ PublicKey payer = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5");
+ PublicKey authority = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo");
+ List addresses = List.of(
+ new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5"),
+ new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo")
+ );
+
+ TransactionInstruction instruction = AddressLookupTableProgram.extendLookupTable(lookupTable, payer, authority, addresses);
assertNotNull(instruction);
assertEquals(AddressLookupTableProgram.PROGRAM_ID, instruction.getProgramId());
- assertEquals(4, instruction.getKeys().size()); // Check number of keys
+ assertEquals(3, instruction.getKeys().size());
+ assertEquals(1 + 4 + addresses.size() * 32, instruction.getData().length);
+
+ // Validate data
+ ByteBuffer data = ByteBuffer.wrap(instruction.getData()).order(ByteOrder.LITTLE_ENDIAN);
+ assertEquals(2, data.get()); // EXTEND_LOOKUP_TABLE
+ assertEquals(addresses.size(), data.getInt());
+
+ for (PublicKey address : addresses) {
+ byte[] addrBytes = new byte[32];
+ data.get(addrBytes);
+ assertArrayEquals(address.toByteArray(), addrBytes);
+ }
}
/**
@@ -54,10 +92,17 @@ public void testExtendLookupTable() {
*/
@Test
public void testDeactivateLookupTable() {
- TransactionInstruction instruction = AddressLookupTableProgram.deactivateLookupTable(LOOKUP_TABLE, AUTHORITY);
+ PublicKey lookupTable = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+ PublicKey authority = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+
+ TransactionInstruction instruction = AddressLookupTableProgram.deactivateLookupTable(lookupTable, authority);
assertNotNull(instruction);
assertEquals(AddressLookupTableProgram.PROGRAM_ID, instruction.getProgramId());
- assertEquals(2, instruction.getKeys().size()); // Check number of keys
+ assertEquals(2, instruction.getKeys().size());
+
+ // Validate data
+ byte[] expectedData = new byte[]{3}; // DEACTIVATE_LOOKUP_TABLE
+ assertArrayEquals(expectedData, instruction.getData());
}
/**
@@ -65,10 +110,17 @@ public void testDeactivateLookupTable() {
*/
@Test
public void testCloseLookupTable() {
- PublicKey recipient = new PublicKey("SysvarRent111111111111111111111111111111111");
- TransactionInstruction instruction = AddressLookupTableProgram.closeLookupTable(LOOKUP_TABLE, AUTHORITY, recipient);
+ PublicKey lookupTable = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+ PublicKey authority = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+ PublicKey recipient = new PublicKey("skynetDj29GH6o6bAqoixCpDuYtWqi1rm8ZNx1hB3vq");
+
+ TransactionInstruction instruction = AddressLookupTableProgram.closeLookupTable(lookupTable, authority, recipient);
assertNotNull(instruction);
assertEquals(AddressLookupTableProgram.PROGRAM_ID, instruction.getProgramId());
- assertEquals(3, instruction.getKeys().size()); // Check number of keys
+ assertEquals(3, instruction.getKeys().size());
+
+ // Validate data
+ byte[] expectedData = new byte[]{4}; // CLOSE_LOOKUP_TABLE
+ assertArrayEquals(expectedData, instruction.getData());
}
}
\ No newline at end of file