Skip to main content
Solana accounts are the foundation of the blockchain’s state storage. The Sava SDK provides a comprehensive set of interfaces and utilities for working with accounts, public keys, signers, and Program Derived Addresses (PDAs).

PublicKey Interface

The PublicKey interface represents a Solana account’s 32-byte address. It provides methods for creating, encoding, and verifying public keys.

Creating Public Keys

import software.sava.core.accounts.PublicKey;

// From Base58 encoded string
PublicKey pubKey = PublicKey.fromBase58Encoded("11111111111111111111111111111111");

// From byte array
byte[] keyBytes = new byte[32];
PublicKey pubKey = PublicKey.createPubKey(keyBytes);

// Read from buffer
PublicKey pubKey = PublicKey.readPubKey(buffer, offset);

Encoding and Conversion

Public keys can be converted between different formats:
// Convert to Base58 string
String base58 = pubKey.toBase58();

// Convert to byte array
byte[] bytes = pubKey.toByteArray();

// Convert to Java PublicKey
java.security.PublicKey javaPubKey = pubKey.toJavaPublicKey();

Constants

The PublicKey interface defines important constants:
int PUBLIC_KEY_LENGTH = 32;  // 32 bytes
int MAX_SEED_LENGTH = 32;    // Maximum seed length for derived addresses
int MAX_SEEDS = 16;          // Maximum number of seeds
PublicKey NONE = ...;        // All-zeros key (11111111111111111111111111111111)

Account Metadata (AccountMeta)

The AccountMeta interface describes how an account is used in a transaction. It specifies whether an account is writable, a signer, the fee payer, or an invoked program.

Creating Account Metadata

import software.sava.core.accounts.meta.AccountMeta;

// Read-only account
AccountMeta readOnly = AccountMeta.createRead(publicKey);

// Writable account
AccountMeta writable = AccountMeta.createWrite(publicKey);

// Signer accounts
AccountMeta readOnlySigner = AccountMeta.createReadOnlySigner(publicKey);
AccountMeta writableSigner = AccountMeta.createWritableSigner(publicKey);

// Fee payer (automatically writable and signer)
AccountMeta feePayer = AccountMeta.createFeePayer(publicKey);

// Invoked program
AccountMeta program = AccountMeta.createInvoked(publicKey);

Account Meta Properties

Each AccountMeta provides methods to check its properties:
boolean isSigner = accountMeta.signer();
boolean isWritable = accountMeta.write();
boolean isFeePayer = accountMeta.feePayer();
boolean isInvoked = accountMeta.invoked();
PublicKey key = accountMeta.publicKey();
When building instructions, accounts are automatically sorted according to Solana’s requirements: fee payer first, then signers, then writable accounts, and finally read-only accounts.

Merging Account Metadata

When multiple instructions reference the same account with different permissions, metadata can be merged:
AccountMeta merged = accountMeta1.merge(accountMeta2);
// The merged metadata will have the most permissive settings

Signers and Keypairs

The Signer interface represents an account that can sign transactions using its private key.

Generating Keypairs

import software.sava.core.accounts.Signer;

// Generate new random keypair
byte[] privateKey = Signer.generatePrivateKeyBytes();
Signer signer = Signer.createFromPrivateKey(privateKey);

// Generate complete keypair (private + public)
byte[] keyPair = Signer.generatePrivateKeyPairBytes();
Signer signer = Signer.createFromKeyPair(keyPair);

Loading Existing Keypairs

// From separate private and public keys
Signer signer = Signer.createFromKeyPair(publicKeyBytes, privateKeyBytes);

// From 64-byte keypair (32 private + 32 public)
Signer signer = Signer.createFromKeyPair(keyPairBytes);

// From Java PrivateKey and PublicKey
Signer signer = Signer.createFromKeyPair(publicKey, privateKey);

Signing Messages

// Sign a message
byte[] message = "Hello Solana".getBytes();
byte[] signature = signer.sign(message);

// Sign with offset and length
byte[] signature = signer.sign(message, offset, length);

// Sign in-place at specific position
int nextPosition = signer.sign(message, msgOffset, msgLen, outputPosition);

Key Validation

The SDK automatically validates keypairs when creating signers:
// Validates that public key matches private key
Signer.validateKeyPair(privateKey, expectedPublicKey);

// Validates complete keypair
Signer.validateKeyPair(keyPairBytes);
Always validate keypairs when loading them from external sources to ensure cryptographic consistency.

Program Derived Addresses (PDAs)

PDAs are deterministic addresses derived from a program ID and seeds. They provide a way to create accounts that can only be controlled by programs.

Finding PDAs

PDAs are found by searching for an address that is off the Ed25519 curve:
import software.sava.core.accounts.ProgramDerivedAddress;
import java.util.List;

// Define seeds
List<byte[]> seeds = List.of(
    "metadata".getBytes(),
    tokenMint.toByteArray(),
    authority.toByteArray()
);

// Find PDA with bump seed
ProgramDerivedAddress pda = PublicKey.findProgramAddress(seeds, programId);

// Access the derived address
PublicKey pdaAddress = pda.publicKey();
int bumpSeed = pda.nonce();
List<byte[]> usedSeeds = pda.seeds();

Creating Program Addresses

If you already know the bump seed, you can directly create a program address:
// Create with known bump seed
List<byte[]> seedsWithBump = new ArrayList<>(seeds);
seedsWithBump.add(new byte[]{(byte) bumpSeed});

PublicKey programAddress = PublicKey.createProgramAddress(seedsWithBump, programId);
// Returns null if the address is on the curve

Seeds and Constraints

// Maximum values
int maxSeeds = PublicKey.MAX_SEEDS;          // 16 seeds
int maxSeedLength = PublicKey.MAX_SEED_LENGTH; // 32 bytes per seed
The bump seed is searched from 255 down to 0. The first value that produces an off-curve address is used. This ensures deterministic derivation.

Account With Seed

For simpler use cases, you can create accounts derived from a base key and ASCII seed:
AccountWithSeed account = PublicKey.createOffCurveAccountWithAsciiSeed(
    baseKey,
    "my-seed",
    programId
);

PublicKey derived = account.address();
byte[] seedWithBump = account.seed();

Signature Verification

The PublicKey interface provides methods to verify Ed25519 signatures:
// Verify signature
boolean valid = publicKey.verifySignature(message, signature);

// Verify with offset and length
boolean valid = publicKey.verifySignature(
    message, 
    msgOffset, 
    msgLength, 
    signature
);

// Verify string message
boolean valid = publicKey.verifySignature("Hello Solana", signature);

// Static verification from byte arrays
boolean valid = PublicKey.verifySignature(
    publicKeyBytes,
    publicKeyOffset,
    messageBytes,
    msgOffset,
    msgLength,
    signatureBytes
);

Best Practices

  1. Reuse PublicKey instances: PublicKey objects are immutable and can be safely reused
  2. Validate imported keys: Always validate keypairs from external sources
  3. Cache PDAs: Finding PDAs is computationally expensive; cache them when possible
  4. Use appropriate AccountMeta types: Choose the minimal permissions needed for each account
  5. Handle PDA derivation failures: createProgramAddress returns null if unable to find an off-curve address
  • Transactions - Learn how accounts are used in transactions
  • Cryptography - Understand the underlying Ed25519 operations
  • Encoding - See how public keys are encoded and serialized

Build docs developers (and LLMs) love