Overview
Accounts are fundamental to Solana development. This guide covers creating and managing keypairs, generating Program Derived Addresses (PDAs), and working with account metadata.
Creating and Loading Keypairs
Generating a New Keypair
The Signer interface provides methods to generate new Ed25519 keypairs:
import software.sava.core.accounts.Signer;
import software.sava.core.accounts.PublicKey;
// Generate a new keypair
byte [] keyPairBytes = Signer . generatePrivateKeyPairBytes ();
Signer signer = Signer . createFromKeyPair (keyPairBytes);
// Access the public key
PublicKey publicKey = signer . publicKey ();
System . out . println ( "Public Key: " + publicKey . toBase58 ());
Loading from Private Key
Create a signer from an existing private key:
// From 32-byte private key
byte [] privateKey = // ... your private key bytes
Signer signer = Signer . createFromPrivateKey (privateKey);
// From 64-byte keypair (private + public)
byte [] keyPair = // ... your keypair bytes
Signer signer = Signer . createFromKeyPair (keyPair);
Loading from Base58
Parse public keys from Base58-encoded strings:
import software.sava.core.accounts.PublicKey;
// From Base58 string
String base58Address = "11111111111111111111111111111111" ;
PublicKey publicKey = PublicKey . fromBase58Encoded (base58Address);
// From Base64
String base64Address = // ... base64 string
PublicKey publicKey = PublicKey . fromBase64Encoded (base64Address);
Step 1: Generate Private Key
Use Signer.generatePrivateKeyBytes() to create a new 32-byte private key using secure random generation.
Call Signer.createFromPrivateKey() to derive the public key and create a signer instance.
Save the keypair bytes securely. Never expose private keys in code or logs.
Generating Program Derived Addresses
PDAs are deterministic addresses derived from seeds and a program ID.
Finding a PDA
import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.ProgramDerivedAddress;
import java.util.List;
PublicKey programId = PublicKey . fromBase58Encoded ( "YourProgramId" );
PublicKey userAccount = PublicKey . fromBase58Encoded ( "UserPublicKey" );
// Define seeds for PDA derivation
List < byte []> seeds = List . of (
"vault" . getBytes (),
userAccount . toByteArray ()
);
// Find PDA with bump seed
ProgramDerivedAddress pda = PublicKey . findProgramAddress (seeds, programId);
System . out . println ( "PDA Address: " + pda . publicKey (). toBase58 ());
System . out . println ( "Bump: " + pda . nonce ());
Source: PublicKey.java:204-216
Creating Program Addresses
For manual PDA creation without bump finding:
// Create program address with specific seeds
PublicKey address = PublicKey . createProgramAddress (seeds, programId);
if (address != null ) {
System . out . println ( "Valid PDA: " + address . toBase58 ());
} else {
System . out . println ( "Seeds do not create valid PDA" );
}
Source: PublicKey.java:198-202
Creating Accounts with Seeds
Generate derived addresses using ASCII seeds:
PublicKey baseAccount = PublicKey . fromBase58Encoded ( "BaseAddress" );
String seed = "my-seed" ;
PublicKey programId = PublicKey . fromBase58Encoded ( "ProgramId" );
// Create derived address
PublicKey derivedAddress = PublicKey . createWithSeed (
baseAccount,
seed,
programId
);
System . out . println ( "Derived Address: " + derivedAddress . toBase58 ());
Source: PublicKey.java:250-265
PDA seeds have a maximum length of 32 bytes, and you can use up to 16 seeds per derivation.
Account metadata describes how accounts are used in transactions.
import software.sava.core.accounts.meta.AccountMeta;
import software.sava.core.accounts.PublicKey;
PublicKey account = PublicKey . fromBase58Encoded ( "AccountAddress" );
// Read-only account
AccountMeta readOnly = AccountMeta . createRead (account);
// Writable account
AccountMeta writable = AccountMeta . createWrite (account);
// Read-only signer
AccountMeta signer = AccountMeta . createReadOnlySigner (account);
// Writable signer
AccountMeta writableSigner = AccountMeta . createWritableSigner (account);
// Fee payer (special writable signer)
AccountMeta feePayer = AccountMeta . createFeePayer (account);
Source: AccountMeta.java:23-51
Query account metadata properties:
AccountMeta meta = AccountMeta . createWritableSigner (account);
// Check properties
boolean isSigner = meta . signer ();
boolean isWritable = meta . write ();
boolean isFeePayer = meta . feePayer ();
boolean isInvoked = meta . invoked ();
PublicKey pubkey = meta . publicKey ();
When building transactions, metadata may need to be merged:
AccountMeta meta1 = AccountMeta . createRead (account);
AccountMeta meta2 = AccountMeta . createWrite (account);
// Merge - results in writable account
AccountMeta merged = meta1 . merge (meta2);
System . out . println ( "Writable: " + merged . write ()); // true
Source: AccountMeta.java:96
Use boolean flags to create metadata:
boolean writable = true ;
boolean signer = false ;
AccountMeta meta = AccountMeta . createMeta (account, writable, signer);
// With all flags
boolean invoked = false ;
boolean feePayer = false ;
AccountMeta fullMeta = AccountMeta . createMeta (
account,
invoked,
feePayer,
writable,
signer
);
Source: AccountMeta.java:60-84
Key Validation
Sava performs automatic keypair validation:
// Validation happens automatically during creation
try {
byte [] privateKey = // ... your key
Signer signer = Signer . createFromPrivateKey (privateKey);
// Key is valid
} catch ( IllegalStateException e ) {
System . err . println ( "Invalid keypair: " + e . getMessage ());
}
Source: Signer.java:22-31
Best Practices
Never hardcode private keys in source code
Use environment variables or secure key management systems
Clear sensitive data from memory after use
Cache PDAs when possible to avoid recomputation
Use meaningful, consistent seed structures
Document your seed derivation scheme
Building Transactions Learn how to construct transactions with account metadata
Signing and Sending Sign transactions with your keypairs