Instruction Interface
software.sava.core.tx.Instruction
Represents a call to a Solana program with accounts and data.
Factory Methods
static Instruction createInstruction(
AccountMeta programId,
List<AccountMeta> keys,
byte[] data,
int offset,
int len
)
Program ID as invoked account metadata
keys
List<AccountMeta>
required
List of account metadata for instruction
InstructionRecord instance
static Instruction createInstruction(
AccountMeta programId,
List<AccountMeta> keys,
byte[] data
)
Complete instruction data array
static Instruction createInstruction(
AccountMeta programId,
List<AccountMeta> keys,
Discriminator discriminator
)
Program discriminator (instruction identifier)
With PublicKey Program ID
static Instruction createInstruction(
PublicKey programId,
List<AccountMeta> keys,
byte[] data,
int offset,
int len
)
static Instruction createInstruction(
PublicKey programId,
List<AccountMeta> keys,
byte[] data
)
Account Management
Instruction extraAccounts(List<AccountMeta> accounts)
accounts
List<AccountMeta>
required
Additional account metadata to add
New instruction with added accounts
Instruction extraAccount(AccountMeta account)
Instruction extraAccounts(
Collection<PublicKey> accounts,
Function<PublicKey, AccountMeta> metaFactory
)
accounts
Collection<PublicKey>
required
Collection of public keys
metaFactory
Function<PublicKey, AccountMeta>
required
Function to convert PublicKey to AccountMeta
Instruction extraAccount(
PublicKey account,
Function<PublicKey, AccountMeta> metaFactory
)
Merge Accounts
int mergeAccounts(Map<PublicKey, AccountMeta> accounts)
accounts
Map<PublicKey, AccountMeta>
required
Map to merge accounts into
Number of accounts merged
Serialization
Length of serialized instruction in bytes
int serialize(
byte[] out,
int i,
AccountIndexLookupTableEntry[] accountIndexLookupTable
)
Starting index in output array
accountIndexLookupTable
AccountIndexLookupTableEntry[]
required
Account index lookup table
New index after serialization
int serialize(
byte[] out,
int i,
Map<PublicKey, Integer> accountIndexLookupTable
)
accountIndexLookupTable
Map<PublicKey, Integer>
required
Map of public keys to account indices
Properties
Program ID account metadata
List<AccountMeta> accounts()
List of account metadata for this instruction
Instruction data bytes (reference, not copy)
Discriminator Methods
int[] discriminator(int len)
Number of bytes to read as discriminator
Discriminator bytes as int array
Discriminator wrapDiscriminator(int len)
boolean beginsWith(byte[] data)
True if instruction data begins with specified bytes
Example Usage
Create Basic Instruction
import software.sava.core.tx.Instruction;
import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.meta.AccountMeta;
import java.util.List;
import java.nio.ByteBuffer;
// Define program ID
var programId = PublicKey.fromBase58Encoded(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
);
// Create account metadata
var accounts = List.of(
AccountMeta.createWrite(tokenAccount),
AccountMeta.createRead(mint),
AccountMeta.createSigner(authority)
);
// Create instruction data
var data = ByteBuffer.allocate(9)
.put((byte) 7) // Transfer discriminator
.putLong(1000000000L) // Amount
.array();
// Create instruction
var instruction = Instruction.createInstruction(
programId,
accounts,
data
);
System Transfer
import software.sava.core.accounts.SolanaAccounts;
import java.nio.ByteBuffer;
// Create transfer instruction
var systemProgram = SolanaAccounts.MAIN_NET.systemProgram();
var accounts = List.of(
AccountMeta.createWriteSigner(fromPubKey),
AccountMeta.createWrite(toPubKey)
);
var data = ByteBuffer.allocate(12)
.putInt(2) // Transfer discriminator
.putLong(1_000_000_000L) // 1 SOL in lamports
.array();
var transferIx = Instruction.createInstruction(
systemProgram,
accounts,
data
);
// Create base instruction
var ix = Instruction.createInstruction(
programId,
baseAccounts,
data
);
// Add remaining accounts
var extraAccounts = List.of(
AccountMeta.createRead(account1),
AccountMeta.createRead(account2),
AccountMeta.createRead(account3)
);
var completeIx = ix.extraAccounts(extraAccounts);
Dynamic Account Addition
import software.sava.core.accounts.meta.AccountMeta;
// Add accounts from public keys
var publicKeys = List.of(pubKey1, pubKey2, pubKey3);
var ix = baseInstruction.extraAccounts(
publicKeys,
AccountMeta::createRead // Convert to read-only accounts
);
// Or use different metadata
var writableIx = baseInstruction.extraAccounts(
publicKeys,
AccountMeta::createWrite
);
Check Instruction Type
// Check if instruction is a transfer
byte[] transferDiscriminator = new byte[]{2, 0, 0, 0};
if (instruction.beginsWith(transferDiscriminator)) {
System.out.println("This is a transfer instruction");
}
// Get discriminator
int[] disc = instruction.discriminator(8);
Serialize Instruction
import java.util.HashMap;
// Create account index map
var accountIndexes = new HashMap<PublicKey, Integer>();
accountIndexes.put(programId, 0);
accountIndexes.put(account1, 1);
accountIndexes.put(account2, 2);
// Serialize
byte[] output = new byte[instruction.serializedLength()];
instruction.serialize(output, 0, accountIndexes);
Solana instructions typically follow this format:
+-------------------+
| Discriminator | 1-8 bytes (identifies instruction)
+-------------------+
| Parameter 1 | Variable size
+-------------------+
| Parameter 2 | Variable size
+-------------------+
| ... |
+-------------------+
Common Discriminator Patterns
Anchor Programs: 8-byte SHA-256 hash of "namespace:instruction_name"
var discriminator = Hash.sha256(
"global:transfer".getBytes()
);
var anchorDiscriminator = Arrays.copyOf(discriminator, 8);
Native Programs: Simple integer discriminator
var data = ByteBuffer.allocate(4)
.putInt(2) // System program transfer = 2
.array();
Account metadata specifies how each account is used:
import software.sava.core.accounts.meta.AccountMeta;
// Read-only
var readOnly = AccountMeta.createRead(publicKey);
// Writable
var writable = AccountMeta.createWrite(publicKey);
// Signer (read-only)
var signer = AccountMeta.createSigner(publicKey);
// Signer and writable
var signerWrite = AccountMeta.createWriteSigner(publicKey);
// Invoked program
var program = AccountMeta.createInvoked(programId);
// Fee payer
var feePayer = AccountMeta.createFeePayer(feePayerKey);
Best Practices
- Use
extraAccounts() for remaining accounts patterns
- Validate instruction data length before creation
- Use discriminators to identify instruction types
- Keep instruction data serialization consistent
- Document discriminator values for your programs
- Order accounts consistently (signers first, then writable, then read-only)