Skip to content

Commit

Permalink
JUP-setup jupiter program to swap solana tokens and added functionali… (
Browse files Browse the repository at this point in the history
#1)

* 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 <chintan_mbp@Chintans-MacBook-Pro.local>
  • Loading branch information
i-contemplator and Chintan Patel authored Aug 16, 2024
1 parent a5173cb commit 500c98a
Show file tree
Hide file tree
Showing 52 changed files with 1,061 additions and 420 deletions.
14 changes: 7 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
14 changes: 13 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.15.0</version>
</dependency>
</dependencies>

<distributionManagement>
Expand Down Expand Up @@ -186,7 +198,7 @@
<goal>sign</goal>
</goals>
<configuration>
<homedir>c:/Users/Michael/.gnupg/</homedir>
<homedir>/Users/chintan_mbp/.gnupg/</homedir>
<keyname>0x27FAE7D2</keyname>
</configuration>
</execution>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/p2p/solanaj/core/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
36 changes: 19 additions & 17 deletions src/main/java/org/p2p/solanaj/core/AccountKeysList.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import java.util.HashMap;

public class AccountKeysList {
private HashMap<String, AccountMeta> accounts;
private final HashMap<String, AccountMeta> accounts;

public AccountKeysList() {
accounts = new HashMap<String, AccountMeta>();
accounts = new HashMap<>();
}

public void add(AccountMeta accountMeta) {
Expand All @@ -31,29 +31,31 @@ public void addAll(Collection<AccountMeta> metas) {
}

public ArrayList<AccountMeta> getList() {
ArrayList<AccountMeta> accountKeysList = new ArrayList<AccountMeta>(accounts.values());
ArrayList<AccountMeta> accountKeysList = new ArrayList<>(accounts.values());
accountKeysList.sort(metaComparator);

return accountKeysList;
}

private static final Comparator<AccountMeta> metaComparator = new Comparator<AccountMeta>() {
private static final Comparator<AccountMeta> 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 +
'}';
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/p2p/solanaj/core/AccountMeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 +
'}';
}
}
170 changes: 170 additions & 0 deletions src/main/java/org/p2p/solanaj/core/LegacyMessage.java
Original file line number Diff line number Diff line change
@@ -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<TransactionInstruction> 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<AccountMeta> keysList = getAccountKeys();
int accountKeysSize = keysList.size();

byte[] accountAddressesLength = Shortvec.encodeLength(accountKeysSize);

int compiledInstructionsLength = 0;
List<CompiledInstruction> 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<AccountMeta> getAccountKeys() {
List<AccountMeta> keysList = accountKeys.getList();
int feePayerIndex = findAccountIndex(keysList, feePayer.getPublicKey());

List<AccountMeta> 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<AccountMeta> 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");
}
}
76 changes: 76 additions & 0 deletions src/main/java/org/p2p/solanaj/core/LegacyTransaction.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<Account> 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();
}
}
Loading

0 comments on commit 500c98a

Please sign in to comment.