Skip to content

Commit

Permalink
Merge pull request #48 from skynetcap/1.18.0-staging
Browse files Browse the repository at this point in the history
1.18.0
  • Loading branch information
skynetcap authored Sep 10, 2024
2 parents c5c6f7e + 26fd65e commit bd64e02
Show file tree
Hide file tree
Showing 24 changed files with 2,666 additions and 369 deletions.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Add the following Maven dependency to your project's `pom.xml`:
<dependency>
<groupId>com.mmorrell</groupId>
<artifactId>solanaj</artifactId>
<version>1.17.6</version>
<version>1.18.0</version>
</dependency>
```

Expand Down
10 changes: 9 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>com.mmorrell</groupId>
<artifactId>solanaj</artifactId>
<packaging>jar</packaging>
<version>1.18.0-SNAPSHOT</version>
<version>1.18.0</version>
<name>${project.groupId}:${project.artifactId}</name>
<description>Java client for Solana RPC</description>
<url>https://github.com/skynetcap/solanaj</url>
Expand Down Expand Up @@ -119,6 +119,12 @@
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
</dependencies>

<distributionManagement>
Expand Down Expand Up @@ -172,6 +178,8 @@
</executions>
<configuration>
<source>17</source>
<doclint>none</doclint>
<failOnError>false</failOnError>
</configuration>
</plugin>
<plugin>
Expand Down
70 changes: 56 additions & 14 deletions src/main/java/org/p2p/solanaj/core/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,75 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

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.
*/
public class Transaction {

public static final int SIGNATURE_LENGTH = 64;

private Message message;
private List<String> signatures;
private final Message message;
private final List<String> signatures;
private byte[] serializedMessage;

/**
* Constructs a new Transaction instance.
*/
public Transaction() {
this.message = new Message();
this.signatures = new ArrayList<String>();
this.signatures = new ArrayList<>(); // Use diamond operator
}

/**
* Adds an instruction to the transaction.
*
* @param instruction The instruction to add
* @return This Transaction instance for method chaining
* @throws NullPointerException if the instruction is null
*/
public Transaction addInstruction(TransactionInstruction instruction) {
Objects.requireNonNull(instruction, "Instruction cannot be null"); // Add input validation
message.addInstruction(instruction);

return this;
}

/**
* Sets the recent blockhash for the transaction.
*
* @param recentBlockhash The recent blockhash to set
* @throws NullPointerException if the recentBlockhash is null
*/
public void setRecentBlockHash(String recentBlockhash) {
Objects.requireNonNull(recentBlockhash, "Recent blockhash cannot be null"); // Add input validation
message.setRecentBlockHash(recentBlockhash);
}

/**
* Signs the transaction with a single signer.
*
* @param signer The account to sign the transaction
* @throws NullPointerException if the signer is null
*/
public void sign(Account signer) {
sign(Arrays.asList(signer));
sign(Arrays.asList(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation
}

/**
* Signs the transaction with multiple signers.
*
* @param signers The list of accounts to sign the transaction
* @throws IllegalArgumentException if no signers are provided
*/
public void sign(List<Account> signers) {

if (signers.size() == 0) {
throw new IllegalArgumentException("No signers");
if (signers == null || signers.isEmpty()) {
throw new IllegalArgumentException("No signers provided");
}

Account feePayer = signers.get(0);
Expand All @@ -48,19 +81,28 @@ public void sign(List<Account> signers) {
serializedMessage = message.serialize();

for (Account signer : signers) {
TweetNaclFast.Signature signatureProvider = new TweetNaclFast.Signature(new byte[0], signer.getSecretKey());
byte[] signature = signatureProvider.detached(serializedMessage);

signatures.add(Base58.encode(signature));
try {
TweetNaclFast.Signature signatureProvider = new TweetNaclFast.Signature(new byte[0], signer.getSecretKey());
byte[] signature = signatureProvider.detached(serializedMessage);
signatures.add(Base58.encode(signature));
} catch (Exception e) {
throw new RuntimeException("Error signing transaction", e); // Improve exception handling
}
}
}

/**
* Serializes the transaction into a byte array.
*
* @return The serialized transaction as a byte array
*/
public byte[] serialize() {
int signaturesSize = signatures.size();
byte[] signaturesLength = ShortvecEncoding.encodeLength(signaturesSize);

ByteBuffer out = ByteBuffer
.allocate(signaturesLength.length + signaturesSize * SIGNATURE_LENGTH + serializedMessage.length);
// Calculate total size before allocating ByteBuffer
int totalSize = signaturesLength.length + signaturesSize * SIGNATURE_LENGTH + serializedMessage.length;
ByteBuffer out = ByteBuffer.allocate(totalSize);

out.put(signaturesLength);

Expand Down
52 changes: 51 additions & 1 deletion src/main/java/org/p2p/solanaj/core/TransactionBuilder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.p2p.solanaj.core;

import java.util.List;
import java.util.Objects;

/**
* Builder for constructing {@link Transaction} objects to be used in sendTransaction.
Expand All @@ -9,25 +10,74 @@ public class TransactionBuilder {

private final Transaction transaction;

/**
* Constructs a new TransactionBuilder.
*/
public TransactionBuilder() {
transaction = new Transaction();
this.transaction = new Transaction();
}

/**
* Adds a single instruction to the transaction.
*
* @param transactionInstruction the instruction to add
* @return this builder for method chaining
* @throws NullPointerException if transactionInstruction is null
*/
public TransactionBuilder addInstruction(TransactionInstruction transactionInstruction) {
Objects.requireNonNull(transactionInstruction, "Transaction instruction cannot be null");
transaction.addInstruction(transactionInstruction);
return this;
}

/**
* Adds multiple instructions to the transaction.
*
* @param instructions the list of instructions to add
* @return this builder for method chaining
* @throws NullPointerException if instructions is null
*/
public TransactionBuilder addInstructions(List<TransactionInstruction> instructions) {
Objects.requireNonNull(instructions, "Instructions list cannot be null");
instructions.forEach(this::addInstruction);
return this;
}

/**
* Sets the recent block hash for the transaction.
*
* @param recentBlockHash the recent block hash to set
* @return this builder for method chaining
* @throws NullPointerException if recentBlockHash is null
*/
public TransactionBuilder setRecentBlockHash(String recentBlockHash) {
Objects.requireNonNull(recentBlockHash, "Recent block hash cannot be null");
transaction.setRecentBlockHash(recentBlockHash);
return this;
}

/**
* Sets the signers for the transaction and signs it.
*
* @param signers the list of signers
* @return this builder for method chaining
* @throws NullPointerException if signers is null
* @throws IllegalArgumentException if signers is empty
*/
public TransactionBuilder setSigners(List<Account> signers) {
Objects.requireNonNull(signers, "Signers list cannot be null");
if (signers.isEmpty()) {
throw new IllegalArgumentException("Signers list cannot be empty");
}
transaction.sign(signers);
return this;
}

/**
* Builds and returns the constructed Transaction object.
*
* @return the built Transaction
*/
public Transaction build() {
return transaction;
}
Expand Down
122 changes: 116 additions & 6 deletions src/main/java/org/p2p/solanaj/core/TransactionInstruction.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,127 @@
package org.p2p.solanaj.core;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Arrays;

/**
* Represents an instruction to be executed by a Solana program.
*/
@Getter
@AllArgsConstructor
public class TransactionInstruction {

private PublicKey programId;
private final PublicKey programId;
private final List<AccountMeta> keys;
private final byte[] data;

public TransactionInstruction(PublicKey programId, List<AccountMeta> keys, byte[] data) {
this.programId = Objects.requireNonNull(programId, "Program ID cannot be null");
this.keys = Collections.unmodifiableList(Objects.requireNonNull(keys, "Keys cannot be null"));
this.data = Arrays.copyOf(Objects.requireNonNull(data, "Data cannot be null"), data.length);
}

/**
* Creates a new builder for TransactionInstruction.
*
* @return a new Builder instance
*/
public static Builder builder() {
return new Builder();
}

/**
* Compares this TransactionInstruction with another object for equality.
*
* @param o the object to compare with
* @return true if the objects are equal, false otherwise
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TransactionInstruction that = (TransactionInstruction) o;
return Objects.equals(programId, that.programId) &&
Objects.equals(keys, that.keys) &&
Arrays.equals(data, that.data);
}

/**
* Generates a hash code for this TransactionInstruction.
*
* @return the hash code
*/
@Override
public int hashCode() {
int result = Objects.hash(programId, keys);
result = 31 * result + Arrays.hashCode(data);
return result;
}

/**
* Returns a string representation of this TransactionInstruction.
*
* @return a string representation of the object
*/
@Override
public String toString() {
return "TransactionInstruction{" +
"programId=" + programId +
", keys=" + keys +
", data=" + Arrays.toString(data) +
'}';
}

/**
* Builder class for creating TransactionInstruction instances.
*/
public static class Builder {
private PublicKey programId;
private List<AccountMeta> keys;
private byte[] data;

/**
* Sets the program ID for this instruction.
*
* @param programId the PublicKey of the program to execute this instruction
* @return this Builder instance
*/
public Builder programId(PublicKey programId) {
this.programId = programId;
return this;
}

/**
* Sets the list of account keys for this instruction.
*
* @param keys the list of AccountMeta objects representing the accounts
* @return this Builder instance
*/
public Builder keys(List<AccountMeta> keys) {
this.keys = keys;
return this;
}

private List<AccountMeta> keys;
/**
* Sets the instruction data.
*
* @param data the byte array containing the instruction data
* @return this Builder instance
*/
public Builder data(byte[] data) {
this.data = data;
return this;
}

private byte[] data;
/**
* Builds and returns a new TransactionInstruction instance.
*
* @return a new TransactionInstruction instance
* @throws NullPointerException if programId, keys, or data is null
*/
public TransactionInstruction build() {
return new TransactionInstruction(programId, keys, data);
}
}
}
Loading

0 comments on commit bd64e02

Please sign in to comment.