# Core
Source: https://sava.software/libraries/core
Common Solana Cryptography & Serialization Utilities.
## [Dependencies](https://github.com/sava-software/sava/blob/main/sava-core/src/main/java/module-info.java)
* [org.bouncycastle.provider](https://www.bouncycastle.org/download/bouncy-castle-java/#latest)
## Features
* [Transaction (de)serialization](#transaction-deserialization)
* [Account Types:](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/accounts)
* Token, Mint, Token2022 and extensions
* Address Lookup Tables
* [Common collection of main-net addresses](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/accounts/SolanaAccounts.java)
* Ed25519 [public](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/accounts/PublicKey.java) and [private](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/accounts/Signer.java) keys
* Program derived addresses
* [Encoding:](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/encoding)
* [Base58](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/encoding/Base58.java)
* [Hex](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/encoding/Jex.java)
* [Little Endian Numbers](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/encoding/ByteUtil.java)
* [Compact u16](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/encoding/CompactU16Encoding.java)
## Private Key Parsing
### Usage
```java theme={null}
var jsonConfig = "";
var ji = JsonIterator.parse(jsonConfig);
var signer = PrivateKeyEncoding.fromJsonPrivateKey(ji);
```
### JSON Configuration
If the base58 encoded public key is configured via `pubKey` it will be used to further validate the public key derived
from the private key.
#### Solana CLI Key Pair JSON Array
By default, if only an array is configured, the assumption is that it is a key pair generated by the Solana CLI.
```
[1,2,3, ... ,42]
```
#### JSON Array
```json theme={null}
{
"pubKey": "",
"encoding": "jsonKeyPairArray",
"secret": []
}
```
#### Key Pair
Encoded 64 byte private/public key pair. Derived public key will be validated.
* base64KeyPair
* base58KeyPair
```json theme={null}
{
"pubKey": "",
"encoding": "base64KeyPair",
"secret": "asdf=="
}
```
#### Private Key Only
Encoded 32 byte private key.
* base64PrivateKey
* base58PrivateKey
```json theme={null}
{
"pubKey": "",
"encoding": "base64PrivateKey",
"secret": "asdf=="
}
```
## Transaction Deserialization
The [TransactionSkeleton](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/tx/TransactionSkeleton.java)
interface provides a lightweight view over transaction data. Allowing for partial introspection or complete reconstruction of
a [Transaction](https://github.com/sava-software/sava/tree/main/sava-core/src/main/java/software/sava/core/tx/Transaction.java).
### Legacy
```java theme={null}
byte[] legacyTransactionData = ...;
var skeleton = TransactionSkeleton.deserializeSkeleton(legacyTransactionData);
AccountMeta[] accounts = skeleton.parseAccounts();
Instruction[] instructions = skeleton.parseLegacyInstructions();
Transaction transaction = skeleton.createTransaction()
```
### Versioned
```java theme={null}
byte[] v0TransactionData = ...;
var skeleton = TransactionSkeleton.deserializeSkeleton(v0TransactionData);
var instructionsWithoutTableAccounts = skeleton.parseInstructionsWithoutTableAccounts();
PublicKey[] tableAccounts = skeleton.lookupTableAccounts();
// ... Fetch table accounts (See RPC) ...
AddressLookupTable lookupTable = ...;
AccountMeta[] accounts = skeleton.parseAccounts(lookupTable);
Instruction[] instructions = skeleton.parseInstructions(accounts);
Transaction transaction = skeleton.createTransaction(instructions, lookupTable);
```
## Creating Transactions
See the [IDL clients docs for typical transaction flows.](/libraries/idl-clients#typical-transaction-flow)
# IDL Clients
Source: https://sava.software/libraries/idl-clients
Generated source and convenient clients to (de)serialize instructions and accounts for common Solana programs.
## [Dependencies](https://github.com/sava-software/idl-clients/blob/main/idl-clients-core/src/main/java/module-info.java)
* java.net.http
* org.bouncycastle.provider
* systems.comodal.json\_iterator
* software.sava.core
* software.sava.rpc
## Usage Examples
### Create a Program Client
The [idl-clients-spl](https://github.com/sava-software/idl-clients/tree/main/idl-clients-spl) module, used throughout
the examples below, provides `SPLClient` and `SPLAccountClient` for working with tokens, associated token accounts,
stake accounts, address lookup tables, etc.
```java theme={null}
var solanaAccounts = SolanaAccounts.MAIN_NET;
var splClient = SPLClient.createClient(solanaAccounts);
// Create an account-specific client bound to an owner and fee payer
var owner = PublicKey.fromBase58Encoded(/* your public key */);
var feePayer = AccountMeta.createFeePayer(/* fee payer public key */);
var splAccountClient = splClient.createAccountClient(owner, feePayer);
```
### Typical Transaction Flow
```java theme={null}
Signer signer = ...; // load private key
var feePayer = AccountMeta.createFeePayer(signer.publicKey());
var solanaAccounts = SolanaAccounts.MAIN_NET;
try (var httpClient = HttpClient.newHttpClient()) {
var rpcClient = SolanaRpcClient.build()
.endpoint(SolanaNetwork.MAIN_NET.getEndpoint())
.httpClient(httpClient)
.createClient();
List instructions = List.of(
ComputeBudgetUtil.MAX_COMPUTE_BUDGET_IX,
ComputeBudgetProgram.setComputeUnitPrice(
solanaAccounts.invokedComputeBudgetProgram(),
0
)
// TODO: Create and add relevant instructions
);
var simulationTransaction = Transaction.createTx(feePayer, instructions);
var simulationFuture = rpcClient.simulateTransaction(simulationTransaction);
// Apply a user-provided fee or fetch an estimate from a service such as Helius' Fee API.
var cuPriceIx = ComputeBudgetProgram.setComputeUnitPrice(
solanaAccounts.invokedComputeBudgetProgram(),
42
);
var simulationResult = simulationFuture.join();
var transaction = simulationTransaction
.replaceInstruction(0,
ComputeBudgetProgram.setComputeUnitLimit(
solanaAccounts.invokedComputeBudgetProgram(),
// CU usage can be dynamic and may need to be increased.
simulationResult.unitsConsumed().getAsInt()
)
)
.replaceInstruction(1, cuPriceIx);
transaction.setRecentBlockHash(simulationResult.replacementBlockHash().blockhash());
transaction.sign(signer);
var base64Encoded = transaction.base64EncodeToString();
var sig = rpcClient.sendTransaction(base64Encoded).join();
System.out.println(sig);
}
```
### Transfer SOL
```java theme={null}
var transferTo = PublicKey.fromBase58Encoded(/* recipient */);
var transferIx = splAccountClient.transferSolLamports(transferTo, 42);
```
### Associated Token Accounts
```java theme={null}
var mint = PublicKey.fromBase58Encoded(/* token mint */);
// Find the ATA for the standard Token Program
ProgramDerivedAddress ata = splClient.findATA(owner, mint);
// Create the ATA (idempotent - won't fail if it already exists)
var tokenProgram = solanaAccounts.tokenProgram();
Instruction createAtaIx = splAccountClient.createATAForOwnerFundedByFeePayer(
true, // idempotent
ata.publicKey(),
mint,
tokenProgram
);
```
### Token Transfers
```java theme={null}
var fromTokenAccount = PublicKey.fromBase58Encoded(/* source token account */);
var toTokenAccount = PublicKey.fromBase58Encoded(/* destination token account */);
var tokenMint = PublicKey.fromBase58Encoded(/* mint */);
var invokedTokenProgram = solanaAccounts.invokedTokenProgram();
long amount = 1_000_000;
int decimals = 6;
Instruction transferIx = splAccountClient.transferTokenChecked(
invokedTokenProgram,
fromTokenAccount,
toTokenAccount,
amount,
decimals,
tokenMint
);
```
### Stake Accounts
```java theme={null}
Signer signer = ...;
var stakeAuthority = signer.publicKey();
var feePayer = AccountMeta.createFeePayer(stakeAuthority);
var validatorVoteAccount = PublicKey.fromBase58Encoded(/* vote account */);
long stakeLamports = LamportDecimal.fromBigDecimal(BigDecimal.ONE).longValue();
var solanaAccounts = SolanaAccounts.MAIN_NET;
try (var httpClient = HttpClient.newHttpClient()) {
var rpcClient = SolanaRpcClient.build()
.endpoint(SolanaNetwork.MAIN_NET.getEndpoint())
.httpClient(httpClient)
.createClient();
long minRent = rpcClient.getMinimumBalanceForRentExemption(StakeAccount.BYTES).join();
var seed = ZonedDateTime.now(ZoneOffset.UTC).toString();
var stakeAccountPubKey = PublicKey.createOffCurveAccountWithAsciiSeed(
stakeAuthority,
seed,
solanaAccounts.stakeProgram()
).publicKey();
var createStakeAccountIx = SystemProgram.createAccountWithSeed(
solanaAccounts.invokedSystemProgram(),
stakeAuthority, // payer
stakeAccountPubKey, // new account
stakeAuthority, // base account (signer)
stakeAuthority, // base
seed,
minRent + stakeLamports,
StakeAccount.BYTES,
solanaAccounts.stakeProgram()
);
var authorized = new Authorized(stakeAuthority, stakeAuthority);
var lockup = new Lockup(new UnixTimestamp(0), new Epoch(0), PublicKey.NONE);
var initializeIx = SolanaStakeInterfaceProgram.initialize(
solanaAccounts.invokedStakeProgram(),
solanaAccounts,
stakeAccountPubKey,
authorized,
lockup
);
var delegateStakeIx = SolanaStakeInterfaceProgram.delegateStake(
solanaAccounts.invokedStakeProgram(),
solanaAccounts,
stakeAccountPubKey,
validatorVoteAccount,
solanaAccounts.stakeConfig(),
stakeAuthority
);
var instructions = List.of(createStakeAccountIx, initializeIx, delegateStakeIx);
}
```
### Create & Extend Lookup Table
```java theme={null}
var solanaAccounts = SolanaAccounts.MAIN_NET;
var splClient = SPLClient.createClient(solanaAccounts);
var splAccountClient = splClient.createAccountClient(owner, feePayer);
var newAccounts = List.of(
PublicKey.fromBase58Encoded(""),
PublicKey.fromBase58Encoded(""),
PublicKey.fromBase58Encoded("")
);
long recentSlot = rpcClient.getSlot().join();
var lookupTablePDA = splAccountClient.findLookupTableAddress(recentSlot);
var createLookupTableIx = splAccountClient.createLookupTable(lookupTablePDA, recentSlot);
var extendTableIx = splAccountClient.extendLookupTable(lookupTablePDA.publicKey(), newAccounts);
var instructions = List.of(createLookupTableIx, extendTableIx);
```
### Durable Nonce Transactions
See the [official Solana documentation](https://solana.com/developers/courses/offline-transactions/durable-nonces) for context on durable nonce transactions.
The `SystemProgram` and `NonceAccount` types under `software.sava.idl.clients.spl.system` provide everything needed
to create, initialize, and consume durable nonce accounts without depending on `solana-programs`.
#### Create & Initialize Nonce Account
```java theme={null}
Signer signer = ...;
var rpcEndpoint = SolanaNetwork.MAIN_NET.getEndpoint();
try (var httpClient = HttpClient.newHttpClient()) {
var rpcClient = SolanaRpcClient.build()
.endpoint(rpcEndpoint)
.httpClient(httpClient)
.createClient();
var blockHashFuture = rpcClient.getLatestBlockHash();
var minRentFuture = rpcClient.getMinimumBalanceForRentExemption(NonceAccount.BYTES);
var solanaAccounts = SolanaAccounts.MAIN_NET;
var seed = "nonce";
var nonceAccountWithSeed = PublicKey.createOffCurveAccountWithAsciiSeed(
signer.publicKey(),
seed,
solanaAccounts.systemProgram()
);
var initializeNonceAccountIx = SystemProgram.initializeNonceAccount(
solanaAccounts.invokedSystemProgram(),
solanaAccounts,
nonceAccountWithSeed.publicKey(),
signer.publicKey()
);
System.out.format("""
Fetching block hash and minimum rent to create nonce account %s with authority %s.
""",
nonceAccountWithSeed.publicKey(),
signer.publicKey()
);
long minRent = minRentFuture.join();
var createNonceAccountIx = SystemProgram.createAccountWithSeed(
solanaAccounts.invokedSystemProgram(),
signer.publicKey(), // payer
nonceAccountWithSeed.publicKey(), // new account
signer.publicKey(), // base account (signer)
signer.publicKey(), // base
seed,
minRent,
NonceAccount.BYTES,
solanaAccounts.systemProgram()
);
var instructions = List.of(createNonceAccountIx, initializeNonceAccountIx);
var transaction = Transaction.createTx(signer.publicKey(), instructions);
var blockHash = blockHashFuture.join().blockHash();
transaction.setRecentBlockHash(blockHash);
transaction.sign(signer);
var base64Encoded = transaction.base64EncodeToString();
var sendTransactionFuture = rpcClient.sendTransaction(base64Encoded);
System.out.format("""
Creating nonce account %s
https://explorer.solana.com/tx/%s
""",
nonceAccountWithSeed.publicKey(),
transaction.getBase58Id()
);
var sig = sendTransactionFuture.join();
System.out.format("""
Confirmed transaction %s
https://solscan.io/account/%s
""",
sig,
nonceAccountWithSeed.publicKey()
);
var nonceAccountInfo = rpcClient.getAccountInfo(nonceAccountWithSeed.publicKey()).join();
var nonceAccount = NonceAccount.read(nonceAccountInfo);
System.out.println(nonceAccount);
}
```
#### Create & Send Durable Nonce Transaction
```java theme={null}
Signer signer = ...;
var nonceAccountKey = PublicKey.fromBase58Encoded("");
var sendToKey = PublicKey.fromBase58Encoded("");
var transferSOL = new BigDecimal("0.0");
var solanaAccounts = SolanaAccounts.MAIN_NET;
var rpcEndpoint = SolanaNetwork.MAIN_NET.getEndpoint();
try (var httpClient = HttpClient.newHttpClient()) {
var rpcClient = SolanaRpcClient.build()
.endpoint(rpcEndpoint)
.httpClient(httpClient)
.createClient();
var nonceAccountInfo = rpcClient.getAccountInfo(nonceAccountKey).join();
var nonceAccount = NonceAccount.read(nonceAccountInfo);
System.out.println(nonceAccount);
var advanceNonceIx = nonceAccount.advanceNonceAccount(solanaAccounts);
var transferIx = SystemProgram.transferSol(
solanaAccounts.invokedSystemProgram(),
signer.publicKey(),
sendToKey,
LamportDecimal.fromBigDecimal(transferSOL).longValue()
);
var instructions = List.of(advanceNonceIx, transferIx);
var transaction = Transaction.createTx(signer.publicKey(), instructions);
nonceAccount.setNonce(transaction);
transaction.sign(signer);
var base64Encoded = transaction.base64EncodeToString();
var sendTransactionFuture = rpcClient.sendTransaction(base64Encoded);
System.out.format("""
Transferring %s SOL from %s to %s.
https://explorer.solana.com/tx/%s
""",
transferSOL.toPlainString(), signer.publicKey(), sendToKey, transaction.getBase58Id()
);
var sig = sendTransactionFuture.join();
System.out.println("Confirmed transaction "+ sig);
}
```
# RPC
Source: https://sava.software/libraries/rpc
HTTP and WebSocket JSON RPC Clients.
## [Dependencies](https://github.com/sava-software/sava/blob/main/sava-rpc/src/main/java/module-info.java)
* java.net.http
* software.sava.json.iterator
* software.sava.core
## HTTP
See the official Solana docs for context on each [HTTP RPC method.](https://solana.com/docs/rpc/http)
### Create Client
```java theme={null}
try (var httpClient = HttpClient.newHttpClient()) {
var rpcClient = SolanaRpcClient.build()
.endpoint(SolanaNetwork.MAIN_NET.getEndpoint())
.httpClient(httpClient)
.createClient();
}
```
### Fetch & Parse Accounts
```java theme={null}
var accountInfo = rpcClient.getAccountInfo(
PublicKey.fromBase58Encoded("6T6vqb3VykNToz4sqY5C29Psb8iNUuVZPAqJXbAgorVF"),
).join();
byte[] tableData = accountInfo.data();
var table = AddressLookupTable.read(accountInfo.pubKey(), tableData);
System.out.println(table);
```
```java theme={null}
var accountInfo = rpcClient.getAccountInfo(
PublicKey.fromBase58Encoded("6T6vqb3VykNToz4sqY5C29Psb8iNUuVZPAqJXbAgorVF"),
AddressLookupTable.FACTORY
).join();
AddressLookupTable table = accountInfo.data();
System.out.println(table);
```
### Query Program Accounts with [Filters](https://solana.com/docs/rpc#filter-criteria)
**Note**: You will need access to an RPC node which has getProgramAccounts enabled, such as [Helius](https://www.helius.dev/).
Retrieve all lookup tables which are active and frozen.
```java theme={null}
byte[] stillActive = new byte[Long.BYTES];
ByteUtil.putInt64LE(stillActive, 0, Clock.MAX_SLOT);
var activeFilter = Filter.createMemCompFilter(DEACTIVATION_SLOT_OFFSET, stillActive);
var noAuthorityFilter = Filter.createMemCompFilter(AUTHORITY_OPTION_OFFSET, new byte[]{0});
var accountInfoFuture = rpcClient.getProgramAccounts(
SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
List.of(
activeFilter,
noAuthorityFilter
),
AddressLookupTable.FACTORY
);
var accountInfoList = accountInfoFuture.join();
accountInfoList.stream().map(AccountInfo::data).forEach(System.out::println);
System.out.format("Retrieved %d tables which are active and frozen.%n", accountInfoList.size());
```
## WebSocket
See the official Solana docs for context on each [WebSocket RPC method.](https://solana.com/docs/rpc/websocket)
### Create Client
```java theme={null}
try (var httpClient = HttpClient.newHttpClient()) {
var webSocket = SolanaRpcWebsocket.build()
.webSocketBuilder(httpClient.newWebSocketBuilder())
.uri(SolanaNetwork.MAIN_NET.getWebSocketEndpoint())
.solanaAccounts(SolanaAccounts.MAIN_NET)
.commitment(Commitment.CONFIRMED)
.onOpen(ws -> System.out.println("Websocket connected to " + ws.endpoint()))
.onClose((_, statusCode, reason) -> System.out.format("%d: %s%n", statusCode, reason))
.onError((_, throwable) -> throwable.printStackTrace())
.create();
webSocket.connect();
}
```
### Stream Program Accounts
#### Token Accounts
```java theme={null}
var solanaAccounts = SolanaAccounts.MAIN_NET;
var tokenProgram = solanaAccounts.tokenProgram();
var tokenOwner = PublicKey.fromBase58Encoded("");
webSocket.programSubscribe(
tokenProgram,
List.of(
Filter.createDataSizeFilter(TokenAccount.BYTES),
Filter.createMemCompFilter(TokenAccount.OWNER_OFFSET, tokenOwner)
),
accountInfo -> {
var tokenAccount = TokenAccount.read(accountInfo.pubKey(), accountInfo.data());
System.out.println(tokenAccount);
}
);
```
#### Address Lookup Tables
```java theme={null}
var solanaAccounts = SolanaAccounts.MAIN_NET;
var addressLookupTableProgram = solanaAccounts.addressLookupTableProgram();
webSocket.programSubscribe(addressLookupTableProgram, accountInfo -> {
var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
System.out.println(table);
});
```
#
Source: https://sava.software/project/contact
Sava is maintained
by [Sava Engineering, Inc](https://sava.engineering/).
Join the conversation
@sava\_software
[hello@sava.software](mailto:hello@sava.software)
# Spotlight
Source: https://sava.software/project/spotlight
See who's building with Sava
GLAM is an onchain asset management and tokenization platform
to launch and manage investment products on Solana.
***
[Claim your spotlight](https://tally.so/r/nrJR82)
# Quickstart
Source: https://sava.software/quickstart
Add Sava to your Gradle or Maven build.
## Build Configuration
Sava targets Java 25. Ensure you have a compatible JDK installed before adding Sava to your build.
```kotlin build.gradle.kts theme={null}
dependencies {
implementation("software.sava:sava-core:$VERSION")
implementation("software.sava:sava-rpc:$VERSION")
}
```
```groovy build.gradle theme={null}
dependencies {
implementation "software.sava:sava-core:$VERSION"
implementation "software.sava:sava-rpc:$VERSION"
}
```
```xml pom.xml theme={null}
software.sava
sava-core
VERSION
software.sava
sava-rpc
VERSION
```
A Gradle Version Catalog and Platform (BOM) is provided for each target version of Java.
Signed libraries are published to both Maven Central and the GitHub Package Repository.
***
# Vanity Address Generator
Source: https://sava.software/utilities/vanity
Generate addresses with a defined prefix and/or suffix.
## Configuration
**GitHub Access Token**: [Generate a classic token](https://github.com/settings/tokens) with the `read:packages` scope to access
dependencies hosted on GitHub Package Repository.
```properties .gradle/gradle.properties theme={null}
savaGithubPackagesUsername=GITHUB_USERNAME
savaGithubPackagesPassword=GITHUB_TOKEN
```
## Compile
```shell theme={null}
./sava-vanity/compile.sh
```
## Run
```shell theme={null}
./sava-vanity/genKeys.sh --prefix="abc" --outDir=".keys"
```
### Docker
Instead of running the local jlink binary, you can run a Docker image by passing the `docker` flag with the image name
and tag.
Build the image:
```shell theme={null}
docker build --build-arg PROJECT=sava-vanity -t sava-vanity:local -f sava-vanity/Dockerfile .
```
Run it:
```shell theme={null}
./sava-vanity/genKeys.sh --docker="sava-vanity:local" --prefix="abc" --outDir=".keys"
```
By default, the image is built only when it is not found locally. Pass the `build` flag to force a
rebuild even if the image already exists:
```shell theme={null}
./sava-vanity/genKeys.sh --docker="sava-vanity:local" --build --prefix="abc" --outDir=".keys"
```
When not using Docker, the local jlink binary image (`./gradlew :sava-vanity:image`) is built
automatically when it has not been built yet. Pass the `build` flag to force a rebuild of the local
binary image even if it already exists:
```shell theme={null}
./sava-vanity/genKeys.sh --build --prefix="abc" --outDir=".keys"
```
An `outDir` is required so the generated keys are saved to disk; the host directory is created if
necessary and bind-mounted into the container with write permissions so that generated keys are
persisted on the host:
```shell theme={null}
./sava-vanity/genKeys.sh --docker="sava-vanity:local" --prefix="abc" --outDir=".keys"
```
### Args
* A `prefix` and/or `suffix` must be provided.
* `outDir` is required so the generated keys are saved to disk.
* `numThreads` defaults to half of the systems CPU's.
* `keyFileFormat` controls the on-disk key file format and may be `properties` (default) or `json`.
* Each thread will check every `checkFound` iterations if `numKeys` have been found.
* `p1337Letters` allows alphabetic characters to be replaced by visually similar numbers.
* `1337Numbers` allows numbers to be replaced by visually similar alphabetic characters.
* `screen` may be enabled to manage the session so that it can be re-attached if a remote session is disconnected.
* `ctrl+a -> d` to detach
* `screen -r` to re-attach
### Run Control
* jvmArgs="-server -Xms64M -Xmx128M"
* \[d | docker | dockerImage]=
* \[b | build]=false
* screen=0
* \[nt | numThreads]=
* \[nk | numKeys]=1
* \[kf | keyFormat]="base64KeyPair"
* \[kff | keyFileFormat]="properties"
* \[cf | checkFound]=131072
* \[ld | logDelay]="5S"
* \[o | outDir]='.keys' (required)
* \[sv | sigVerify]=false
### Encryption
The generated secret key can be encrypted at rest by enabling the `encrypt` flag. The password is
never passed on the command line or as a JVM system property (both of which are visible in process
listings); it is supplied to the Java runtime only via the `SAVA_VANITY_ENCRYPT_PASSWORD`
environment variable.
* \[e | encrypt]=false
* \[pw | password] — securely prompts for the password (with confirmation) and forwards it to the
Java runtime via the environment variable. Implies `encrypt=true`.
* \[pe | passwordEnv]=ENV\_VAR\_NAME — reads the password from an already-exported environment variable
for fully non-interactive runs. Implies `encrypt=true`.
If `encrypt=true` is set without `password`/`passwordEnv`, and the
`SAVA_VANITY_ENCRYPT_PASSWORD` environment variable is not present, the application falls back to
reading the password from the interactive Java Console.
```shell theme={null}
# Securely prompt for the encryption password.
./sava-vanity/genKeys.sh --prefix="abc" --password
# Non-interactive: read the password from an existing environment variable.
export MY_VANITY_PASSWORD="..."
./sava-vanity/genKeys.sh --prefix="abc" --passwordEnv=MY_VANITY_PASSWORD
```
#### Key Derivation (KDF)
The password is run through a key derivation function before it is used to encrypt the secret. The
`kdf` flag selects the function and the secret is always encrypted with AES-256/GCM. The KDF parameters can be
customized; when they are omitted, hardened defaults are used.
* \[kdf]=argon2id — `argon2id` (memory-hard, the default) or `pbkdf2` (`PBKDF2WithHmacSHA512`).
* \[kit | kdfIterations] — number of iterations. Applies to both `pbkdf2` and `argon2id`.
* \[kmem | kdfMemoryKB] — Argon2id memory cost in KiB. Only valid with `kdf=argon2id`.
* \[kpar | kdfParallelism] — Argon2id parallelism (lanes). Only valid with `kdf=argon2id`.
Argon2id parameter tuning is all-or-nothing: either provide none of `kdfMemoryKB`,
`kdfParallelism` and `kdfIterations` (to use the defaults) or provide all three.
Because Argon2id is memory-hard, each concurrent derivation allocates `kdfMemoryKB` of heap
(default 262144 KB / 256 MiB). When `kdf=argon2id` is selected, `genKeys.sh` automatically sizes
the JVM heap to `(kdfMemoryKB × numThreads) + 128 MiB` so concurrent derivations do not exhaust
the default heap. Passing your own `--jvm` args disables this auto-sizing.
```shell theme={null}
# Use Argon2id (the default) with the hardened defaults.
./sava-vanity/genKeys.sh --prefix="abc" --password
# Use Argon2id with fully customized parameters (all three are required).
./sava-vanity/genKeys.sh --prefix="abc" --password --kdf=argon2id \
--kdfMemoryKB=262144 --kdfParallelism=4 --kdfIterations=3
# Opt out of Argon2id and customize only the PBKDF2 iteration count.
./sava-vanity/genKeys.sh --prefix="abc" --password --kdf=pbkdf2 --kdfIterations=600000
```
### Prefix
* \[p | prefix]=""
* \[pc | pCaseSensitive]=false
* \[pn | p1337Numbers]=true
* \[pl | p1337Letters]=true
### Suffix
* \[s | suffix]=""
* \[sc | sCaseSensitive]=false
* \[sn | s1337Numbers]=true
* \[sl | s1337Letters]=true
# Welcome
Source: https://sava.software/welcome
Sava is a minimal dependency Java SDK for building on Solana.
***
Crypto and data structure primitives.
HTTP and WebSocket JSON RPC clients.
Generated source and clients for interacting with onchain programs.
***