# 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.
Hero Light
*** Crypto and data structure primitives. HTTP and WebSocket JSON RPC clients. Generated source and clients for interacting with onchain programs. ***