From a1ce4907d28f2a63dfa948d1483964e389b7d951 Mon Sep 17 00:00:00 2001 From: "maksim.kovalev" Date: Thu, 28 Nov 2024 13:35:25 +0300 Subject: [PATCH 1/3] Add send transaction with external sign --- pom.xml | 2 +- .../java/org/p2p/solanaj/core/Message.java | 8 ++--- .../org/p2p/solanaj/core/Transaction.java | 27 ++++++++++++++-- src/main/java/org/p2p/solanaj/rpc/RpcApi.java | 32 +++++++++++++++++++ .../org/p2p/solanaj/core/MessageTest.java | 2 +- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 23c3d14d..2ee91ad7 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ scm:git:git://github.com/skynetcap/solanaj.git scm:git:ssh://github.com/skynetcap/solanaj.git https://github.com/skynetcap/solanaj/tree/main - + Michael Morrell diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index 1c5bbda0..12b655a2 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -41,7 +41,7 @@ int getLength() { private String recentBlockhash; private AccountKeysList accountKeys; private List instructions; - private Account feePayer; + private PublicKey feePayerPublicKey; public Message() { this.accountKeys = new AccountKeysList(); @@ -143,8 +143,8 @@ public byte[] serialize() { return out.array(); } - protected void setFeePayer(Account feePayer) { - this.feePayer = feePayer; + protected void setFeePayerPublicKey(PublicKey feePayerPublicKey) { + this.feePayerPublicKey = feePayerPublicKey; } private List getAccountKeys() { @@ -159,7 +159,7 @@ private List getAccountKeys() { .collect(Collectors.toList()); } - int feePayerIndex = findAccountIndex(keysList, feePayer.getPublicKey()); + int feePayerIndex = findAccountIndex(keysList, feePayerPublicKey); List newList = new ArrayList(); AccountMeta feePayerMeta = keysList.get(feePayerIndex); newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index bab10710..4f03dce1 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.function.Function; import org.bitcoinj.core.Base58; import org.p2p.solanaj.utils.ShortvecEncoding; @@ -76,8 +77,7 @@ public void sign(List signers) { } Account feePayer = signers.get(0); - message.setFeePayer(feePayer); - + message.setFeePayerPublicKey(feePayer.getPublicKey()); serializedMessage = message.serialize(); for (Account signer : signers) { @@ -91,6 +91,29 @@ public void sign(List signers) { } } + /** + * Signs the transaction with external signer. + * + * @param feePayerPublicKey - The public key of the signer's account. + * @param externalSigner - Function for external sign. + * @throws IllegalArgumentException if no signers are provided + */ + public void signByExternalSigner(PublicKey feePayerPublicKey, Function externalSigner) { + if (externalSigner == null) { + throw new IllegalArgumentException("No external signer provided"); + } + + message.setFeePayerPublicKey(feePayerPublicKey); + serializedMessage = message.serialize(); + + try { + byte[] signature = externalSigner.apply(message.serialize()); + signatures.add(Base58.encode(signature)); + } catch (Exception e) { + throw new RuntimeException("Error signing transaction", e); // Improve exception handling + } + } + /** * Serializes the transaction into a byte array. * diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java index 71b06b76..4363f5a3 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java @@ -13,6 +13,7 @@ import org.p2p.solanaj.ws.listeners.NotificationEventListener; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; public class RpcApi { @@ -91,6 +92,37 @@ public String sendTransaction(Transaction transaction, List signers, St return client.call("sendTransaction", params, String.class); } + /** + * Sends a transaction to the RPC server with external signer + * + * @param transaction - The transaction to send. + * @param feePayerPublicKey - The public key of the signer's account. + * @param externalSigner - Function for external sign. + * @param recentBlockHash - The recent block hash. If null, it will be obtained from the RPC server. + * @param rpcSendTransactionConfig - The configuration object for sending transactions via RPC. + * @return The transaction ID as a string. + * @throws RpcException If an error occurs during the RPC call. + */ + public String sendTransaction(Transaction transaction, PublicKey feePayerPublicKey, + Function externalSigner, String recentBlockHash, + RpcSendTransactionConfig rpcSendTransactionConfig) throws RpcException { + if (recentBlockHash == null) { + recentBlockHash = getLatestBlockhash().getValue().getBlockhash(); + } + transaction.setRecentBlockHash(recentBlockHash); + transaction.signByExternalSigner(feePayerPublicKey, externalSigner); + byte[] serializedTransaction = transaction.serialize(); + + String base64Trx = Base64.getEncoder().encodeToString(serializedTransaction); + + List params = new ArrayList<>(); + + params.add(base64Trx); + params.add(rpcSendTransactionConfig); + + return client.call("sendTransaction", params, String.class); + } + /** * Sends a transaction to the network for processing. * A default RpcSendTransactionConfig is used. diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index 05db0fda..fc108813 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -19,7 +19,7 @@ public void serializeMessage() { Message message = new Message(); message.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); message.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - message.setFeePayer(signer); + message.setFeePayerPublicKey(signer.getPublicKey()); assertArrayEquals(new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, From 50c66c5ba4796b72a8d0de8a4ea61b46514ac9e6 Mon Sep 17 00:00:00 2001 From: "maksim.kovalev" Date: Tue, 2 Sep 2025 16:14:55 +0300 Subject: [PATCH 2/3] Add token 2022 program support --- .../programs/AssociatedTokenProgram.java | 26 +++--- .../p2p/solanaj/programs/TokenProgram.java | 86 +++++++++++++------ .../org/p2p/solanaj/token/TokenManager.java | 9 +- .../programs/AssociatedTokenProgramTest.java | 6 +- .../solanaj/programs/TokenProgramTest.java | 16 ++-- 5 files changed, 92 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/p2p/solanaj/programs/AssociatedTokenProgram.java b/src/main/java/org/p2p/solanaj/programs/AssociatedTokenProgram.java index 1a26e1e7..50bd088d 100644 --- a/src/main/java/org/p2p/solanaj/programs/AssociatedTokenProgram.java +++ b/src/main/java/org/p2p/solanaj/programs/AssociatedTokenProgram.java @@ -30,8 +30,9 @@ public class AssociatedTokenProgram extends Program { */ public static TransactionInstruction create(PublicKey fundingAccount, PublicKey walletAddress, - PublicKey mint) { - return createInstruction(CREATE_METHOD_ID, fundingAccount, walletAddress, mint); + PublicKey mint, + PublicKey tokenProgram) { + return createInstruction(CREATE_METHOD_ID, fundingAccount, walletAddress, mint, tokenProgram); } /** @@ -45,8 +46,9 @@ public static TransactionInstruction create(PublicKey fundingAccount, */ public static TransactionInstruction createIdempotent(PublicKey fundingAccount, PublicKey walletAddress, - PublicKey mint) { - return createInstruction(CREATE_IDEMPOTENT_METHOD_ID, fundingAccount, walletAddress, mint); + PublicKey mint, + PublicKey tokenProgram) { + return createInstruction(CREATE_IDEMPOTENT_METHOD_ID, fundingAccount, walletAddress, mint, tokenProgram); } /** @@ -66,7 +68,8 @@ public static TransactionInstruction recoverNested(PublicKey nestedAccount, PublicKey destinationAccount, PublicKey ownerAccount, PublicKey ownerMint, - PublicKey wallet) { + PublicKey wallet, + PublicKey tokenProgram) { final List keys = new ArrayList<>(); keys.add(new AccountMeta(nestedAccount, false, true)); @@ -75,7 +78,7 @@ public static TransactionInstruction recoverNested(PublicKey nestedAccount, keys.add(new AccountMeta(ownerAccount, false, false)); keys.add(new AccountMeta(ownerMint, false, false)); keys.add(new AccountMeta(wallet, true, true)); - keys.add(new AccountMeta(TokenProgram.PROGRAM_ID, false, false)); + keys.add(new AccountMeta(tokenProgram, false, false)); byte[] transactionData = encodeInstructionData(RECOVER_NESTED_METHOD_ID); @@ -85,29 +88,30 @@ public static TransactionInstruction recoverNested(PublicKey nestedAccount, private static TransactionInstruction createInstruction(int methodId, PublicKey fundingAccount, PublicKey walletAddress, - PublicKey mint) { + PublicKey mint, + PublicKey tokenProgram) { final List keys = new ArrayList<>(); - PublicKey pda = findAssociatedTokenAddress(walletAddress, mint); + PublicKey pda = findAssociatedTokenAddress(walletAddress, mint, tokenProgram); keys.add(new AccountMeta(fundingAccount, true, true)); keys.add(new AccountMeta(pda, false, true)); keys.add(new AccountMeta(walletAddress, false, false)); keys.add(new AccountMeta(mint, false, false)); keys.add(new AccountMeta(SystemProgram.PROGRAM_ID, false, false)); - keys.add(new AccountMeta(TokenProgram.PROGRAM_ID, false, false)); + keys.add(new AccountMeta(tokenProgram, false, false)); byte[] transactionData = encodeInstructionData(methodId); return createTransactionInstruction(PROGRAM_ID, keys, transactionData); } - private static PublicKey findAssociatedTokenAddress(PublicKey walletAddress, PublicKey mint) { + private static PublicKey findAssociatedTokenAddress(PublicKey walletAddress, PublicKey mint, PublicKey tokenProgram) { try { PublicKey pda = PublicKey.findProgramAddress( List.of( walletAddress.toByteArray(), - TokenProgram.PROGRAM_ID.toByteArray(), + tokenProgram.toByteArray(), mint.toByteArray() ), PROGRAM_ID diff --git a/src/main/java/org/p2p/solanaj/programs/TokenProgram.java b/src/main/java/org/p2p/solanaj/programs/TokenProgram.java index a80fca32..0009a230 100644 --- a/src/main/java/org/p2p/solanaj/programs/TokenProgram.java +++ b/src/main/java/org/p2p/solanaj/programs/TokenProgram.java @@ -15,6 +15,7 @@ public class TokenProgram extends Program { public static final PublicKey PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + public static final PublicKey PROGRAM_ID_2022 = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); /** * The public key of the Solana rent sysvar. @@ -45,7 +46,13 @@ public class TokenProgram extends Program { * @param owner account/private key signing this transaction * @return transaction id for explorer */ - public static TransactionInstruction transfer(PublicKey source, PublicKey destination, long amount, PublicKey owner) { + public static TransactionInstruction transfer( + PublicKey source, + PublicKey destination, + long amount, + PublicKey owner, + boolean isToken2022 + ) { final List keys = new ArrayList<>(); keys.add(new AccountMeta(source,false, true)); @@ -57,7 +64,7 @@ public static TransactionInstruction transfer(PublicKey source, PublicKey destin ); return createTransactionInstruction( - PROGRAM_ID, + isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, transactionData ); @@ -74,7 +81,15 @@ public static TransactionInstruction transfer(PublicKey source, PublicKey destin * @param tokenMint The public key of the token's mint * @return A TransactionInstruction for the transfer */ - public static TransactionInstruction transferChecked(PublicKey source, PublicKey destination, long amount, byte decimals, PublicKey owner, PublicKey tokenMint) { + public static TransactionInstruction transferChecked( + PublicKey source, + PublicKey destination, + long amount, + byte decimals, + PublicKey owner, + PublicKey tokenMint, + boolean isToken2022 + ) { final List keys = new ArrayList<>(); keys.add(new AccountMeta(source,false, true)); @@ -89,13 +104,18 @@ public static TransactionInstruction transferChecked(PublicKey source, PublicKey ); return createTransactionInstruction( - PROGRAM_ID, + isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, transactionData ); } - public static TransactionInstruction initializeAccount(final PublicKey account, final PublicKey mint, final PublicKey owner) { + public static TransactionInstruction initializeAccount( + final PublicKey account, + final PublicKey mint, + final PublicKey owner, + boolean isToken2022 + ) { final List keys = new ArrayList<>(); keys.add(new AccountMeta(account,false, true)); @@ -108,13 +128,18 @@ public static TransactionInstruction initializeAccount(final PublicKey account, buffer.put((byte) INITIALIZE_ACCOUNT_METHOD_ID); return createTransactionInstruction( - PROGRAM_ID, + isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array() ); } - public static TransactionInstruction closeAccount(final PublicKey accountPubkey, final PublicKey destinationPubkey, final PublicKey ownerPubkey) { + public static TransactionInstruction closeAccount( + final PublicKey accountPubkey, + final PublicKey destinationPubkey, + final PublicKey ownerPubkey, + boolean isToken2022 + ) { final List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -126,7 +151,7 @@ public static TransactionInstruction closeAccount(final PublicKey accountPubkey, buffer.put((byte) CLOSE_ACCOUNT_METHOD_ID); return createTransactionInstruction( - PROGRAM_ID, + isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array() ); @@ -179,7 +204,8 @@ public static TransactionInstruction initializeMint( PublicKey mintPubkey, int decimals, PublicKey mintAuthority, - PublicKey freezeAuthority + PublicKey freezeAuthority, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(mintPubkey, false, true)); @@ -195,7 +221,7 @@ public static TransactionInstruction initializeMint( buffer.put(freezeAuthority.toByteArray()); } - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -209,7 +235,8 @@ public static TransactionInstruction initializeMint( public static TransactionInstruction initializeMultisig( PublicKey multisigPubkey, List signerPubkeys, - int m + int m, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(multisigPubkey, false, true)); @@ -223,7 +250,7 @@ public static TransactionInstruction initializeMultisig( buffer.put((byte) INITIALIZE_MULTISIG_METHOD_ID); buffer.put((byte) m); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -239,7 +266,8 @@ public static TransactionInstruction approve( PublicKey sourcePubkey, PublicKey delegatePubkey, PublicKey ownerPubkey, - long amount + long amount, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(sourcePubkey, false, true)); @@ -251,7 +279,7 @@ public static TransactionInstruction approve( buffer.put((byte) APPROVE_METHOD_ID); buffer.putLong(amount); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -263,7 +291,8 @@ public static TransactionInstruction approve( */ public static TransactionInstruction revoke( PublicKey accountPubkey, - PublicKey ownerPubkey + PublicKey ownerPubkey, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -272,7 +301,7 @@ public static TransactionInstruction revoke( ByteBuffer buffer = ByteBuffer.allocate(1); buffer.put((byte) REVOKE_METHOD_ID); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -288,7 +317,8 @@ public static TransactionInstruction setAuthority( PublicKey accountPubkey, PublicKey currentAuthorityPubkey, PublicKey newAuthorityPubkey, - AuthorityType authorityType + AuthorityType authorityType, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -303,7 +333,7 @@ public static TransactionInstruction setAuthority( buffer.put(newAuthorityPubkey.toByteArray()); } - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -319,7 +349,8 @@ public static TransactionInstruction mintTo( PublicKey mintPubkey, PublicKey destinationPubkey, PublicKey authorityPubkey, - long amount + long amount, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(mintPubkey, false, true)); @@ -331,7 +362,7 @@ public static TransactionInstruction mintTo( buffer.put((byte) MINT_TO_METHOD_ID); buffer.putLong(amount); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -347,7 +378,8 @@ public static TransactionInstruction burn( PublicKey accountPubkey, PublicKey mintPubkey, PublicKey ownerPubkey, - long amount + long amount, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -359,7 +391,7 @@ public static TransactionInstruction burn( buffer.put((byte) BURN_METHOD_ID); buffer.putLong(amount); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -373,7 +405,8 @@ public static TransactionInstruction burn( public static TransactionInstruction freezeAccount( PublicKey accountPubkey, PublicKey mintPubkey, - PublicKey authorityPubkey + PublicKey authorityPubkey, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -383,7 +416,7 @@ public static TransactionInstruction freezeAccount( ByteBuffer buffer = ByteBuffer.allocate(1); buffer.put((byte) FREEZE_ACCOUNT_METHOD_ID); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** @@ -397,7 +430,8 @@ public static TransactionInstruction freezeAccount( public static TransactionInstruction thawAccount( PublicKey accountPubkey, PublicKey mintPubkey, - PublicKey authorityPubkey + PublicKey authorityPubkey, + boolean isToken2022 ) { List keys = new ArrayList<>(); keys.add(new AccountMeta(accountPubkey, false, true)); @@ -407,7 +441,7 @@ public static TransactionInstruction thawAccount( ByteBuffer buffer = ByteBuffer.allocate(1); buffer.put((byte) THAW_ACCOUNT_METHOD_ID); - return createTransactionInstruction(PROGRAM_ID, keys, buffer.array()); + return createTransactionInstruction(isToken2022? PROGRAM_ID_2022: PROGRAM_ID, keys, buffer.array()); } /** diff --git a/src/main/java/org/p2p/solanaj/token/TokenManager.java b/src/main/java/org/p2p/solanaj/token/TokenManager.java index 9bf7757c..7154a17c 100644 --- a/src/main/java/org/p2p/solanaj/token/TokenManager.java +++ b/src/main/java/org/p2p/solanaj/token/TokenManager.java @@ -28,7 +28,8 @@ public String transfer(final Account owner, final PublicKey source, final Public source, destination, amount, - owner.getPublicKey() + owner.getPublicKey(), + false ) ); @@ -70,7 +71,8 @@ public String transferCheckedToSolAddress(final Account owner, final PublicKey s amount, decimals, owner.getPublicKey(), - tokenMint + tokenMint, + false ) ); @@ -101,7 +103,8 @@ public String initializeAccount(Account newAccount, PublicKey usdcTokenMint, Acc TokenProgram.initializeAccount( newAccount.getPublicKey(), usdcTokenMint, - owner.getPublicKey() + owner.getPublicKey(), + false ) ); diff --git a/src/test/java/org/p2p/solanaj/programs/AssociatedTokenProgramTest.java b/src/test/java/org/p2p/solanaj/programs/AssociatedTokenProgramTest.java index 1a88f8a9..8fa6f5bb 100644 --- a/src/test/java/org/p2p/solanaj/programs/AssociatedTokenProgramTest.java +++ b/src/test/java/org/p2p/solanaj/programs/AssociatedTokenProgramTest.java @@ -23,7 +23,7 @@ public class AssociatedTokenProgramTest { @Test public void testCreate() { - TransactionInstruction instruction = AssociatedTokenProgram.create(FUNDING_ACCOUNT, WALLET_ADDRESS, MINT); + TransactionInstruction instruction = AssociatedTokenProgram.create(FUNDING_ACCOUNT, WALLET_ADDRESS, MINT, TokenProgram.PROGRAM_ID); assertEquals(AssociatedTokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(6, instruction.getKeys().size()); @@ -35,7 +35,7 @@ public void testCreate() { @Test public void testCreateIdempotent() { - TransactionInstruction instruction = AssociatedTokenProgram.createIdempotent(FUNDING_ACCOUNT, WALLET_ADDRESS, MINT); + TransactionInstruction instruction = AssociatedTokenProgram.createIdempotent(FUNDING_ACCOUNT, WALLET_ADDRESS, MINT, TokenProgram.PROGRAM_ID); assertEquals(AssociatedTokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(6, instruction.getKeys().size()); @@ -48,7 +48,7 @@ public void testCreateIdempotent() { @Test public void testRecoverNested() { TransactionInstruction instruction = AssociatedTokenProgram.recoverNested( - NESTED_ACCOUNT, NESTED_MINT, DESTINATION_ACCOUNT, OWNER_ACCOUNT, OWNER_MINT, WALLET_ADDRESS); + NESTED_ACCOUNT, NESTED_MINT, DESTINATION_ACCOUNT, OWNER_ACCOUNT, OWNER_MINT, WALLET_ADDRESS, TokenProgram.PROGRAM_ID); assertEquals(AssociatedTokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(7, instruction.getKeys().size()); diff --git a/src/test/java/org/p2p/solanaj/programs/TokenProgramTest.java b/src/test/java/org/p2p/solanaj/programs/TokenProgramTest.java index 5d649db6..5fafab47 100644 --- a/src/test/java/org/p2p/solanaj/programs/TokenProgramTest.java +++ b/src/test/java/org/p2p/solanaj/programs/TokenProgramTest.java @@ -27,7 +27,7 @@ public void testInitializeMint() { PublicKey mintAuthority = new PublicKey("FuLFkNQzNEAzZ2dEgXVUqVVLxJYLYhbSgpZf9RVVXZuT"); PublicKey freezeAuthority = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); - TransactionInstruction instruction = TokenProgram.initializeMint(mintPubkey, decimals, mintAuthority, freezeAuthority); + TransactionInstruction instruction = TokenProgram.initializeMint(mintPubkey, decimals, mintAuthority, freezeAuthority, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(2, instruction.getKeys().size()); @@ -57,7 +57,7 @@ public void testInitializeMultisig() { ); int m = 2; - TransactionInstruction instruction = TokenProgram.initializeMultisig(multisigPubkey, signerPubkeys, m); + TransactionInstruction instruction = TokenProgram.initializeMultisig(multisigPubkey, signerPubkeys, m, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(4, instruction.getKeys().size()); @@ -81,7 +81,7 @@ public void testApprove() { PublicKey ownerPubkey = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); long amount = 1000000000; // 1 billion (assuming 9 decimals) - TransactionInstruction instruction = TokenProgram.approve(sourcePubkey, delegatePubkey, ownerPubkey, amount); + TransactionInstruction instruction = TokenProgram.approve(sourcePubkey, delegatePubkey, ownerPubkey, amount, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); @@ -114,7 +114,7 @@ public void testTransfer() { long amount = 1000000000; // 1 billion (assuming 9 decimals) PublicKey owner = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); - TransactionInstruction instruction = TokenProgram.transfer(source, destination, amount, owner); + TransactionInstruction instruction = TokenProgram.transfer(source, destination, amount, owner, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); @@ -141,7 +141,7 @@ public void testBurn() { PublicKey owner = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); long amount = 500000000; // 500 million (assuming 9 decimals) - TransactionInstruction instruction = TokenProgram.burn(account, mint, owner, amount); + TransactionInstruction instruction = TokenProgram.burn(account, mint, owner, amount, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); @@ -168,7 +168,7 @@ public void testMintTo() { PublicKey authority = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); long amount = 750000000; // 750 million (assuming 9 decimals) - TransactionInstruction instruction = TokenProgram.mintTo(mint, destination, authority, amount); + TransactionInstruction instruction = TokenProgram.mintTo(mint, destination, authority, amount, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); @@ -194,7 +194,7 @@ public void testFreezeAccount() { PublicKey mint = new PublicKey("FuLFkNQzNEAzZ2dEgXVUqVVLxJYLYhbSgpZf9RVVXZuT"); PublicKey authority = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); - TransactionInstruction instruction = TokenProgram.freezeAccount(account, mint, authority); + TransactionInstruction instruction = TokenProgram.freezeAccount(account, mint, authority, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); @@ -216,7 +216,7 @@ public void testThawAccount() { PublicKey mint = new PublicKey("FuLFkNQzNEAzZ2dEgXVUqVVLxJYLYhbSgpZf9RVVXZuT"); PublicKey authority = new PublicKey("HNGVuL5kqjDehw7KR63w9gxow32sX6xzRNgLb8GkbwCM"); - TransactionInstruction instruction = TokenProgram.thawAccount(account, mint, authority); + TransactionInstruction instruction = TokenProgram.thawAccount(account, mint, authority, false); assertEquals(TokenProgram.PROGRAM_ID, instruction.getProgramId()); assertEquals(3, instruction.getKeys().size()); From 5d5f552b5038e99c3cd8b8488186ea6a2fe0834d Mon Sep 17 00:00:00 2001 From: "maksim.kovalev" Date: Tue, 2 Sep 2025 16:48:11 +0300 Subject: [PATCH 3/3] Merge branches --- .gradle/8.5/checksums/checksums.lock | Bin 0 -> 17 bytes .../dependencies-accessors.lock | Bin 0 -> 17 bytes .gradle/8.5/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.5/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../buildOutputCleanup.lock | Bin 0 -> 17 bytes pom.xml | 2 +- .../java/org/p2p/solanaj/core/Account.java | 4 ++-- .../java/org/p2p/solanaj/core/Message.java | 2 +- .../java/org/p2p/solanaj/core/PublicKey.java | 4 ++-- .../org/p2p/solanaj/core/Transaction.java | 2 +- .../p2p/solanaj/programs/SystemProgram.java | 21 ++++++++++++++++++ .../p2p/solanaj/rpc/types/ProgramAccount.java | 2 +- .../java/org/p2p/solanaj/utils/ByteUtils.java | 12 ++++++++++ .../p2p/solanaj/utils/ShortvecEncoding.java | 6 +++++ .../p2p/solanaj/core/AccountBasedTest.java | 2 +- .../org/p2p/solanaj/core/AccountTest.java | 2 +- .../org/p2p/solanaj/core/MessageTest.java | 2 +- .../org/p2p/solanaj/core/TransactionTest.java | 2 +- .../solanaj/programs/SystemProgramTest.java | 2 +- 19 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 .gradle/8.5/checksums/checksums.lock create mode 100644 .gradle/8.5/dependencies-accessors/dependencies-accessors.lock create mode 100644 .gradle/8.5/fileChanges/last-build.bin create mode 100644 .gradle/8.5/fileHashes/fileHashes.lock create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..70309d0cc53ce0e93180c57c5bf00b97382609b5 GIT binary patch literal 17 TcmZQR@{WJ8?vk!e3{U_7I9&vF literal 0 HcmV?d00001 diff --git a/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000000000000000000000000000000000000..e352785b6793c498790d1d16051eee292921a13d GIT binary patch literal 17 TcmZQRIsb}#!%x*|3{U_7Ji`RL literal 0 HcmV?d00001 diff --git a/.gradle/8.5/fileChanges/last-build.bin b/.gradle/8.5/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..c31107e253b3e8cfd6cd8fcd1932b305eb728e93 GIT binary patch literal 17 TcmZQ}*ONXZDShfK0~7!NB}fB7 literal 0 HcmV?d00001 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..f6798826c040525dc5c666875413fa8451fcd34d GIT binary patch literal 17 UcmZRMub2Fk@Kj_X0|YPv0566E*Z=?k literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index 2ee91ad7..ce6ecd3e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.bitcoinj bitcoinj-core - 0.16.3 + 0.17 com.google.protobuf diff --git a/src/main/java/org/p2p/solanaj/core/Account.java b/src/main/java/org/p2p/solanaj/core/Account.java index 7be0df15..4f151ea1 100644 --- a/src/main/java/org/p2p/solanaj/core/Account.java +++ b/src/main/java/org/p2p/solanaj/core/Account.java @@ -5,7 +5,7 @@ import java.util.List; import org.bitcoinj.crypto.*; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; import org.p2p.solanaj.utils.TweetNaclFast; import org.p2p.solanaj.utils.bip32.wallet.SolanaBip44; import org.p2p.solanaj.utils.bip32.wallet.DerivableType; @@ -30,7 +30,7 @@ public static Account fromMnemonic(List words, String passphrase) { byte[] seed = MnemonicCode.toSeed(words, passphrase); DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed); DeterministicHierarchy deterministicHierarchy = new DeterministicHierarchy(masterPrivateKey); - DeterministicKey child = deterministicHierarchy.get(HDUtils.parsePath("M/501H/0H/0/0"), true, true); + DeterministicKey child = deterministicHierarchy.get(HDPath.parsePath("M/501H/0H/0/0"), true, true); TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(child.getPrivKeyBytes()); return new Account(keyPair); } diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index 12b655a2..7ad6024d 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -1,6 +1,6 @@ package org.p2p.solanaj.core; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; import org.p2p.solanaj.utils.ShortvecEncoding; import java.nio.ByteBuffer; diff --git a/src/main/java/org/p2p/solanaj/core/PublicKey.java b/src/main/java/org/p2p/solanaj/core/PublicKey.java index eec1c1b2..160de908 100644 --- a/src/main/java/org/p2p/solanaj/core/PublicKey.java +++ b/src/main/java/org/p2p/solanaj/core/PublicKey.java @@ -7,8 +7,8 @@ import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.bitcoinj.core.Base58; -import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.base.Base58; +import org.bitcoinj.base.Sha256Hash; import org.p2p.solanaj.utils.ByteUtils; import org.p2p.solanaj.utils.PublicKeySerializer; import org.p2p.solanaj.utils.TweetNaclFast; diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index 4f03dce1..554ee4dc 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -7,7 +7,7 @@ import java.util.Objects; import java.util.function.Function; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; import org.p2p.solanaj.utils.ShortvecEncoding; import org.p2p.solanaj.utils.TweetNaclFast; diff --git a/src/main/java/org/p2p/solanaj/programs/SystemProgram.java b/src/main/java/org/p2p/solanaj/programs/SystemProgram.java index 177321a1..ee138292 100644 --- a/src/main/java/org/p2p/solanaj/programs/SystemProgram.java +++ b/src/main/java/org/p2p/solanaj/programs/SystemProgram.java @@ -55,6 +55,27 @@ public static TransactionInstruction transfer(PublicKey fromPublicKey, PublicKey return new TransactionInstruction(PROGRAM_ID, keys, data); } + /** Write 8 bytes to the byte array (starting at the offset) as signed 64-bit integer in little endian format. */ + public static void int64ToByteArrayLE(long val, byte[] out, int offset) { + out[offset] = (byte) (0xFF & val); + out[offset + 1] = (byte) (0xFF & (val >> 8)); + out[offset + 2] = (byte) (0xFF & (val >> 16)); + out[offset + 3] = (byte) (0xFF & (val >> 24)); + out[offset + 4] = (byte) (0xFF & (val >> 32)); + out[offset + 5] = (byte) (0xFF & (val >> 40)); + out[offset + 6] = (byte) (0xFF & (val >> 48)); + out[offset + 7] = (byte) (0xFF & (val >> 56)); + } + + + /** Write 4 bytes to the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. */ + public static void uint32ToByteArrayLE(long val, byte[] out, int offset) { + out[offset] = (byte) (0xFF & val); + out[offset + 1] = (byte) (0xFF & (val >> 8)); + out[offset + 2] = (byte) (0xFF & (val >> 16)); + out[offset + 3] = (byte) (0xFF & (val >> 24)); + } + /** * Creates an instruction to create a new account. * 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..45f392cd 100644 --- a/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java +++ b/src/main/java/org/p2p/solanaj/rpc/types/ProgramAccount.java @@ -11,7 +11,7 @@ import org.p2p.solanaj.core.PublicKey; import org.p2p.solanaj.rpc.types.config.RpcSendTransactionConfig.Encoding; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; @Getter @ToString diff --git a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java index 60ff25d6..4b21483e 100644 --- a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java @@ -43,6 +43,18 @@ public static void uint64ToByteStreamLE(BigInteger val, OutputStream stream) thr } } + /** + * Returns a copy of the given byte array in reverse order. + */ + public static byte[] reverseBytes(byte[] bytes) { + // We could use the XOR trick here but it's easier to understand if we don't. If we find this is really a + // performance issue the matter can be revisited. + byte[] buf = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) + buf[i] = bytes[bytes.length - 1 - i]; + return buf; + } + public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java index 4a1d9080..9d9e4ad6 100644 --- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java +++ b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java @@ -27,4 +27,10 @@ public static byte[] encodeLength(int len) { return bytes; } + + /** Write 2 bytes to the byte array (starting at the offset) as unsigned 16-bit integer in little endian format. */ + public static void uint16ToByteArrayLE(int val, byte[] out, int offset) { + out[offset] = (byte) (0xFF & val); + out[offset + 1] = (byte) (0xFF & (val >> 8)); + } } diff --git a/src/test/java/org/p2p/solanaj/core/AccountBasedTest.java b/src/test/java/org/p2p/solanaj/core/AccountBasedTest.java index cb168c7e..d3fa6400 100644 --- a/src/test/java/org/p2p/solanaj/core/AccountBasedTest.java +++ b/src/test/java/org/p2p/solanaj/core/AccountBasedTest.java @@ -1,6 +1,6 @@ package org.p2p.solanaj.core; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; import org.junit.jupiter.api.BeforeAll; import java.io.FileInputStream; diff --git a/src/test/java/org/p2p/solanaj/core/AccountTest.java b/src/test/java/org/p2p/solanaj/core/AccountTest.java index 7a9c0c8f..4449dd02 100644 --- a/src/test/java/org/p2p/solanaj/core/AccountTest.java +++ b/src/test/java/org/p2p/solanaj/core/AccountTest.java @@ -6,7 +6,7 @@ import java.util.Arrays; import java.util.logging.Logger; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; public class AccountTest { diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index fc108813..d9598a41 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -1,6 +1,6 @@ package org.p2p.solanaj.core; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import org.p2p.solanaj.programs.SystemProgram; diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/TransactionTest.java index eae14086..8947bb5e 100644 --- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java +++ b/src/test/java/org/p2p/solanaj/core/TransactionTest.java @@ -9,7 +9,7 @@ import java.util.Base64; import java.util.List; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; public class TransactionTest { diff --git a/src/test/java/org/p2p/solanaj/programs/SystemProgramTest.java b/src/test/java/org/p2p/solanaj/programs/SystemProgramTest.java index 2cdec47f..9953828f 100644 --- a/src/test/java/org/p2p/solanaj/programs/SystemProgramTest.java +++ b/src/test/java/org/p2p/solanaj/programs/SystemProgramTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Disabled; import static org.junit.jupiter.api.Assertions.*; -import org.bitcoinj.core.Base58; +import org.bitcoinj.base.Base58; public class SystemProgramTest {