Skip to main content

Instruction Interface

software.sava.core.tx.Instruction Represents a call to a Solana program with accounts and data.

Factory Methods

With AccountMeta Program ID

static Instruction createInstruction(
    AccountMeta programId,
    List<AccountMeta> keys,
    byte[] data,
    int offset,
    int len
)
programId
AccountMeta
required
Program ID as invoked account metadata
keys
List<AccountMeta>
required
List of account metadata for instruction
data
byte[]
required
Instruction data bytes
offset
int
required
Offset in data array
len
int
required
Length of data to use
return
Instruction
InstructionRecord instance
static Instruction createInstruction(
    AccountMeta programId,
    List<AccountMeta> keys,
    byte[] data
)
data
byte[]
required
Complete instruction data array
static Instruction createInstruction(
    AccountMeta programId,
    List<AccountMeta> keys,
    Discriminator discriminator
)
discriminator
Discriminator
required
Program discriminator (instruction identifier)

With PublicKey Program ID

static Instruction createInstruction(
    PublicKey programId,
    List<AccountMeta> keys,
    byte[] data,
    int offset,
    int len
)
programId
PublicKey
required
Program ID public key
static Instruction createInstruction(
    PublicKey programId,
    List<AccountMeta> keys,
    byte[] data
)

Account Management

Add Extra Accounts

Instruction extraAccounts(List<AccountMeta> accounts)
accounts
List<AccountMeta>
required
Additional account metadata to add
return
Instruction
New instruction with added accounts
Instruction extraAccount(AccountMeta account)
account
AccountMeta
required
Single account to add
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
return
int
Number of accounts merged

Serialization

int serializedLength()
return
int
Length of serialized instruction in bytes
int serialize(
    byte[] out,
    int i,
    AccountIndexLookupTableEntry[] accountIndexLookupTable
)
out
byte[]
required
Destination byte array
i
int
required
Starting index in output array
accountIndexLookupTable
AccountIndexLookupTableEntry[]
required
Account index lookup table
return
int
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

AccountMeta programId()
return
AccountMeta
Program ID account metadata
List<AccountMeta> accounts()
return
List<AccountMeta>
List of account metadata for this instruction
byte[] data()
return
byte[]
Instruction data bytes (reference, not copy)
byte[] copyData()
return
byte[]
Copy of instruction data
int offset()
return
int
Offset in data array
int len()
return
int
Length of data in bytes

Discriminator Methods

int[] discriminator(int len)
len
int
required
Number of bytes to read as discriminator
return
int[]
Discriminator bytes as int array
Discriminator wrapDiscriminator(int len)
len
int
required
Discriminator length
return
Discriminator
Discriminator wrapper
boolean beginsWith(byte[] data)
data
byte[]
required
Byte pattern to check
return
boolean
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
);

Add Extra Accounts

// 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);

Instruction Data Format

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

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)

Build docs developers (and LLMs) love