Skip to content

Java Examples

Complete, self-contained Java programs demonstrating the Dilithia SDK. Each scenario is a standalone class with a public static void main entry point. All examples target Java 17+ and use the native crypto bridge via JNA.


Prerequisites

Maven dependency

<dependency>
  <groupId>org.dilithia</groupId>
  <artifactId>dilithia-sdk-java</artifactId>
  <version>0.3.0</version>
</dependency>

Native library

Set the DILITHIUM_NATIVE_CORE_LIB environment variable to the path of the compiled Dilithia native core shared library before running any example:

export DILITHIUM_NATIVE_CORE_LIB=/usr/local/lib/libdilithia_core.so

The NativeCryptoBridge constructor reads this variable and loads the library via JNA. If the variable is missing or blank, it throws IllegalStateException.


Scenario 1: Balance Monitor Bot

A bot that recovers its wallet from a mnemonic, checks its balance, and sends tokens to a destination address when the balance exceeds a threshold. Covers: client setup, wallet recovery, balance query, contract call construction, signing, submission, and receipt polling.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;

public class BalanceMonitorBot {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String TOKEN_CONTRACT = "dil1_token_main";
    static final String DESTINATION = "dil1_recipient_address";
    static final long THRESHOLD = 500_000;
    static final long SEND_AMOUNT = 100_000;

    public static void main(String[] args) {
        try {
            // 1. Initialize client with builder and crypto adapter
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover wallet from saved mnemonic
            var mnemonic = System.getenv("BOT_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new IllegalStateException("Set BOT_MNEMONIC env var");
            }
            var account = crypto.recoverHdWallet(mnemonic);
            System.out.println("Bot address: " + account.address());

            // 3. Check current balance — returns Balance with typed fields
            Balance balance = client.balance(Address.of(account.address())).get();
            System.out.println("Current balance: " + balance.value()
                    + " (raw: " + balance.rawValue() + ")");

            if (balance.value().lessThan(TokenAmount.dili(String.valueOf(THRESHOLD)))) {
                System.out.printf("Balance below threshold %d. Nothing to do.%n", THRESHOLD);
                return;
            }

            // 4. Build a contract call to transfer tokens, sign, and submit
            DilithiaSigner signer = payload -> new SignedPayload(
                    "mldsa65",
                    PublicKey.of(account.publicKey()),
                    crypto.signMessage(account.secretKey(), payload).signature());

            // 5. Send the contract call — returns Receipt directly
            Receipt receipt = client.contract(TOKEN_CONTRACT)
                    .call("transfer", java.util.Map.of(
                            "to", DESTINATION,
                            "amount", SEND_AMOUNT))
                    .send(signer);
            System.out.printf("Confirmed at block %d, status: %s, tx: %s%n",
                    receipt.blockHeight(), receipt.status(), receipt.txHash());

        } catch (HttpException e) {
            System.err.printf("HTTP error %d: %s%n", e.statusCode(), e.getMessage());
            System.exit(1);
        } catch (RpcException e) {
            System.err.printf("RPC error %d: %s%n", e.code(), e.getMessage());
            System.exit(1);
        } catch (TimeoutException e) {
            System.err.println("Timeout: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 2: Multi-Account Treasury Manager

A service that manages multiple HD wallet accounts derived from a single mnemonic. It derives accounts 0 through 4, checks each balance, and consolidates all funds into account 0. Covers: HD derivation loop, multiple balance queries, and batch transaction construction.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;
import java.util.ArrayList;

public class TreasuryManager {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String TOKEN_CONTRACT = "dil1_token_main";
    static final int NUM_ACCOUNTS = 5;

    public static void main(String[] args) {
        try {
            // Initialize client with builder
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            var mnemonic = System.getenv("TREASURY_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new IllegalStateException("Set TREASURY_MNEMONIC env var");
            }

            // 1. Derive all accounts
            var accounts = new ArrayList<DilithiaAccount>();
            for (int i = 0; i < NUM_ACCOUNTS; i++) {
                accounts.add(crypto.recoverHdWalletAccount(mnemonic, i));
            }
            var treasuryAccount = accounts.getFirst();
            System.out.println("Treasury address (account 0): " + treasuryAccount.address());

            // 2. Check balances — balance() returns Balance with typed fields
            var balances = new ArrayList<Balance>();
            for (int i = 0; i < NUM_ACCOUNTS; i++) {
                Balance bal = client.balance(Address.of(accounts.get(i).address())).get();
                balances.add(bal);
                System.out.printf("  Account %d: %s -> %s%n",
                        i, bal.address(), bal.value());
            }

            // 3. Consolidate from accounts 1-4 to account 0
            for (int i = 1; i < NUM_ACCOUNTS; i++) {
                if (balances.get(i).value().isZero()) {
                    System.out.printf("  Account %d: zero balance, skipping.%n", i);
                    continue;
                }

                final int accountIdx = i;
                DilithiaSigner signer = payload -> new SignedPayload(
                        "mldsa65",
                        PublicKey.of(accounts.get(accountIdx).publicKey()),
                        crypto.signMessage(
                                accounts.get(accountIdx).secretKey(), payload).signature());

                System.out.printf("  Consolidating %s from account %d...%n",
                        balances.get(i).value(), i);

                // contract().call().send() returns Receipt
                Receipt receipt = client.contract(TOKEN_CONTRACT)
                        .call("transfer", java.util.Map.of(
                                "to", treasuryAccount.address(),
                                "amount", balances.get(i).rawValue()))
                        .send(signer);
                System.out.printf("  Done. Block %d, status: %s%n",
                        receipt.blockHeight(), receipt.status());
            }

            // 4. Final balance check
            Balance finalBalance = client.balance(Address.of(treasuryAccount.address())).get();
            System.out.println("\nTreasury final balance: " + finalBalance.value());

        } catch (HttpException | RpcException | TimeoutException e) {
            System.err.println("SDK error: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 3: Signature Verification Service

An API endpoint that receives a signed message and verifies the signature against the claimed public key and address. Covers: address validation, public key validation, signature verification, and structured error handling.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

public class SignatureVerifier {

    record VerifyRequest(String publicKey, String address, String message, String signature) {}
    record VerifyResult(boolean valid, String error) {}

    static VerifyResult verify(NativeCryptoBridge crypto, VerifyRequest req) {
        // 1. Validate the public key format
        try {
            crypto.validatePublicKey(req.publicKey());
        } catch (Exception e) {
            return new VerifyResult(false, "Invalid public key: " + e.getMessage());
        }

        // 2. Validate the signature format
        try {
            crypto.validateSignature(req.signature());
        } catch (Exception e) {
            return new VerifyResult(false, "Invalid signature: " + e.getMessage());
        }

        // 3. Validate the claimed address format using Address.of
        try {
            Address.of(req.address());
            crypto.validateAddress(req.address());
        } catch (Exception e) {
            return new VerifyResult(false, "Invalid address: " + e.getMessage());
        }

        // 4. Verify the public key maps to the claimed address
        try {
            var derived = crypto.addressFromPublicKey(req.publicKey());
            if (!Address.of(derived).equals(Address.of(req.address()))) {
                return new VerifyResult(false, "Address does not match public key");
            }
        } catch (Exception e) {
            return new VerifyResult(false, "Address derivation failed: " + e.getMessage());
        }

        // 5. Verify the cryptographic signature
        try {
            boolean valid = crypto.verifyMessage(req.publicKey(), req.message(), req.signature());
            if (!valid) {
                return new VerifyResult(false, "Signature verification failed");
            }
        } catch (Exception e) {
            return new VerifyResult(false, "Verification error: " + e.getMessage());
        }

        return new VerifyResult(true, null);
    }

    public static void main(String[] args) {
        try {
            var crypto = new NativeCryptoBridge();

            // Generate a keypair and sign a message to test verification
            var keypair = crypto.keygen();
            var message = "Login nonce: 98765";
            var sig = crypto.signMessage(keypair.secretKey(), message);
            var address = crypto.addressFromPublicKey(keypair.publicKey());

            System.out.println("Testing with generated keypair:");
            System.out.println("  Address:   " + Address.of(address));
            System.out.println("  Public key: " + keypair.publicKey().substring(0, 32) + "...");

            // Verify with correct data
            var goodResult = verify(crypto, new VerifyRequest(
                    keypair.publicKey(), address, message, sig.signature()));
            System.out.println("Valid signature result: " + goodResult.valid());

            // Verify with wrong message
            var badResult = verify(crypto, new VerifyRequest(
                    keypair.publicKey(), address, "tampered message", sig.signature()));
            System.out.println("Tampered message result: " + badResult.valid()
                    + (badResult.error() != null ? " (" + badResult.error() + ")" : ""));

        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 4: Wallet Backup and Recovery

Create a new wallet, save the encrypted wallet file to disk, then recover it later from the saved file. Covers the full wallet lifecycle: generate mnemonic, create encrypted wallet file, serialize, write to disk, read from disk, deserialize, and recover.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.util.Map;

public class WalletBackup {
    static final String WALLET_PATH = "./my-wallet.json";
    static final String PASSWORD = "my-secure-passphrase";

    public static void main(String[] args) {
        try {
            var mapper = new ObjectMapper();
            var crypto = new NativeCryptoBridge();

            var walletFile = new File(WALLET_PATH);

            if (!walletFile.exists()) {
                // ---- CREATE NEW WALLET ----
                System.out.println("No wallet found. Creating a new one...");

                // 1. Generate a fresh mnemonic
                var mnemonic = crypto.generateMnemonic();
                System.out.println("SAVE THIS MNEMONIC SECURELY:");
                System.out.println(mnemonic);
                System.out.println();

                // 2. Validate the generated mnemonic
                crypto.validateMnemonic(mnemonic);
                System.out.println("Mnemonic validated successfully.");

                // 3. Create an encrypted wallet file
                var account = crypto.createHdWalletFileFromMnemonic(mnemonic, PASSWORD);
                System.out.println("Address:    " + Address.of(account.address()));
                System.out.println("Public key: " + account.publicKey());

                // 4. Save to disk
                mapper.writerWithDefaultPrettyPrinter()
                        .writeValue(walletFile, account.walletFile());
                System.out.println("Wallet saved to " + WALLET_PATH);

                // 5. Verify a round-trip sign/verify
                var sig = crypto.signMessage(account.secretKey(), "hello from new wallet");
                boolean ok = crypto.verifyMessage(account.publicKey(), "hello from new wallet", sig.signature());
                System.out.println("Sign/verify round-trip: " + (ok ? "PASS" : "FAIL"));

            } else {
                // ---- RECOVER EXISTING WALLET ----
                System.out.println("Wallet file found. Recovering...");

                // 6. Read and deserialize
                @SuppressWarnings("unchecked")
                var savedWallet = mapper.readValue(walletFile, Map.class);

                // 7. Recover using mnemonic + password
                var mnemonic = System.getenv("WALLET_MNEMONIC");
                if (mnemonic == null || mnemonic.isBlank()) {
                    throw new IllegalStateException("Set WALLET_MNEMONIC env var to recover");
                }

                @SuppressWarnings("unchecked")
                Map<String, Object> walletMap = (Map<String, Object>) savedWallet;
                var account = crypto.recoverWalletFile(walletMap, mnemonic, PASSWORD);
                System.out.println("Recovered address:    " + Address.of(account.address()));
                System.out.println("Recovered public key: " + account.publicKey());
                System.out.println("Wallet recovered successfully. Ready to sign transactions.");
            }

        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 5: Gas-Sponsored Meta-Transaction

Submit a transaction where the gas fee is paid by a sponsor contract instead of the sender. Useful for onboarding new users who have no tokens to pay for gas. Covers: client setup, building a paymaster-attached call, signing with the native adapter, and submission.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;
import java.util.Map;

public class GasSponsoredMint {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String SPONSOR_CONTRACT = "dil1_gas_sponsor_v1";
    static final String PAYMASTER = "dil1_paymaster_addr";
    static final String TARGET_CONTRACT = "dil1_nft_mint";

    public static void main(String[] args) {
        try {
            // 1. Initialize client with builder and crypto adapter
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover the user's wallet
            var mnemonic = System.getenv("USER_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new IllegalStateException("Set USER_MNEMONIC env var");
            }
            var account = crypto.recoverHdWallet(mnemonic);
            System.out.println("User address: " + Address.of(account.address()));

            // 3. Check if the sponsor accepts this call — query returns QueryResult
            QueryResult acceptResult = client.contract(SPONSOR_CONTRACT)
                    .query("accept", Map.of(
                            "user", account.address(),
                            "contract", TARGET_CONTRACT,
                            "method", "mint"))
                    .get();
            System.out.println("Sponsor accepts: " + acceptResult);

            // 4. Check remaining quota — query returns QueryResult
            QueryResult quotaResult = client.contract(SPONSOR_CONTRACT)
                    .query("remaining_quota", Map.of("user", account.address()))
                    .get();
            System.out.println("Remaining quota: " + quotaResult);

            // 5. Get gas estimate — returns GasEstimate
            GasEstimate gasEstimate = client.network().gasEstimate();
            System.out.println("Current gas estimate: " + gasEstimate);

            // 6. Build signer
            DilithiaSigner signer = payload -> new SignedPayload(
                    "mldsa65",
                    PublicKey.of(account.publicKey()),
                    crypto.signMessage(account.secretKey(), payload).signature());

            // 7. Send the sponsored call — returns Receipt
            Receipt receipt = client.contract(TARGET_CONTRACT)
                    .call("mint", Map.of(
                            "token_id", "nft_001",
                            "metadata", "ipfs://QmSomeHash"))
                    .send(signer);
            System.out.printf("Sponsored tx confirmed at block %d, status: %s%n",
                    receipt.blockHeight(), receipt.status());

        } catch (HttpException e) {
            System.err.printf("HTTP error %d: %s%n", e.statusCode(), e.getMessage());
            System.exit(1);
        } catch (RpcException e) {
            System.err.printf("RPC error %d: %s%n", e.code(), e.getMessage());
            System.exit(1);
        } catch (TimeoutException e) {
            System.err.println("Timeout: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 6: Cross-Chain Message Sender

Send a message to another Dilithia chain via the messaging contract. Useful for bridging data or triggering actions on a remote chain. Covers: client setup, building outbound messages, signing, submission, and receipt polling.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

public class CrossChainSender {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String MESSAGING_CONTRACT = "dil1_bridge_v1";
    static final String PAYMASTER = "dil1_bridge_paymaster";
    static final String DEST_CHAIN = "dilithia-testnet-2";

    public static void main(String[] args) {
        try {
            // 1. Initialize client with builder
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover sender wallet
            var mnemonic = System.getenv("SENDER_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new IllegalStateException("Set SENDER_MNEMONIC env var");
            }
            var account = crypto.recoverHdWallet(mnemonic);
            System.out.println("Sender address: " + Address.of(account.address()));

            // 3. Resolve a name — returns NameRecord
            NameRecord resolved = client.names().resolve("alice.dili").get();
            System.out.println("Resolved alice.dili -> " + resolved);

            // 4. Build the cross-chain message payload
            Map<String, Object> messagePayload = new LinkedHashMap<>();
            messagePayload.put("action", "lock_tokens");
            messagePayload.put("sender", account.address());
            messagePayload.put("amount", TokenAmount.dili("50000"));
            messagePayload.put("recipient", Address.of("dil1_remote_recipient").value());

            // 5. Build signer
            DilithiaSigner signer = payload -> new SignedPayload(
                    "mldsa65",
                    PublicKey.of(account.publicKey()),
                    crypto.signMessage(account.secretKey(), payload).signature());

            // 6. Send the cross-chain message call — returns Receipt
            Map<String, Object> sendArgs = new LinkedHashMap<>();
            sendArgs.put("dest_chain", DEST_CHAIN);
            sendArgs.put("payload", messagePayload);
            Receipt receipt = client.contract(MESSAGING_CONTRACT)
                    .call("send_message", sendArgs)
                    .send(signer);
            System.out.printf("Message tx confirmed at block %d, status: %s, tx: %s%n",
                    receipt.blockHeight(), receipt.status(), receipt.txHash());

            // 7. Optionally wait for the receipt with explicit polling
            TxHash txHash = TxHash.of(receipt.txHash().value());
            Receipt polled = client.receipt(txHash).waitFor(12, Duration.ofSeconds(1));
            System.out.println("Polled receipt status: " + polled.status());

        } catch (HttpException | RpcException | TimeoutException e) {
            System.err.println("SDK error: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 7: Contract Deployment

Deploy a WASM smart contract to the Dilithia chain. Reads the WASM binary, hashes the bytecode, builds and signs a canonical deploy payload, assembles the full DeployPayload, sends the deploy request, and waits for confirmation.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;

public class ContractDeployer {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String CONTRACT_NAME = "my_contract";
    static final String WASM_PATH = "./my_contract.wasm";
    static final String CHAIN_ID = "dilithia-mainnet";

    public static void main(String[] args) {
        try {
            // 1. Initialize client with builder and crypto adapter
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(30)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover wallet from mnemonic
            var mnemonic = System.getenv("DEPLOYER_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new IllegalStateException("Set DEPLOYER_MNEMONIC env var");
            }
            var account = crypto.recoverHdWallet(mnemonic);
            System.out.println("Deployer address: " + Address.of(account.address()));

            // 3. Read the WASM file as hex
            String bytecodeHex = Dilithia.readWasmFileHex(WASM_PATH);
            System.out.println("Bytecode size: " + (bytecodeHex.length() / 2) + " bytes");

            // 4. Get the current nonce — returns Nonce with .nextNonce()
            Nonce nonceResult = client.nonce(Address.of(account.address())).get();
            System.out.println("Current nonce: " + nonceResult.nextNonce());

            // 5. Hash the bytecode hex for the canonical payload
            String bytecodeHash = crypto.hashHex(bytecodeHex);
            System.out.println("Bytecode hash: " + bytecodeHash);

            // 6. Build signer
            DilithiaSigner signer = payload -> new SignedPayload(
                    "mldsa65",
                    PublicKey.of(account.publicKey()),
                    crypto.signMessage(account.secretKey(), payload).signature());

            // 7. Build the deploy payload
            var deployPayload = new DeployPayload(
                    CONTRACT_NAME,
                    bytecodeHex,
                    Address.of(account.address()).value(),
                    "mldsa65",
                    account.publicKey(),
                    null, // signature applied by signer
                    nonceResult.nextNonce(),
                    CHAIN_ID,
                    1
            );

            // 8. Deploy — returns Receipt
            Receipt receipt = client.deploy(deployPayload).send(signer);
            System.out.printf("Contract deployed at block %d, status: %s, tx: %s%n",
                    receipt.blockHeight(), receipt.status(), receipt.txHash());

            // 9. Verify with explicit receipt lookup using TxHash typed identifier
            Receipt verified = client.receipt(TxHash.of(receipt.txHash().value()))
                    .waitFor(30, Duration.ofSeconds(3));
            System.out.println("Verified deployment status: " + verified.status());

            // 10. Shielded deposit example (bonus)
            // Receipt shielded = client.shielded()
            //         .deposit(commitment, TokenAmount.dili("100.5"), proof)
            //         .send(signer);

        } catch (HttpException e) {
            System.err.printf("HTTP error %d: %s%n", e.statusCode(), e.getMessage());
            System.exit(1);
        } catch (RpcException e) {
            System.err.printf("RPC error %d: %s%n", e.code(), e.getMessage());
            System.exit(1);
        } catch (TimeoutException e) {
            System.err.println("Timeout: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Deploy error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 8: Shielded Pool Deposit & Withdraw

Deposit tokens into the shielded pool using a Poseidon commitment, then withdraw them to a recipient address using a nullifier and zero-knowledge proof. Covers: ZK commitment computation, shielded deposit, commitment root lookup, nullifier derivation, proof generation, shielded withdrawal, and receipt polling.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;
import org.dilithia.sdk.zk.NativeZkBridge;

import java.time.Duration;

public class ShieldedPoolFlow {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String RECIPIENT = "dil1_withdraw_recipient";
    static final long DEPOSIT_VALUE = 250_000;

    public static void main(String[] args) {
        try {
            // 1. Initialize client, crypto, and ZK bridges
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();
            var zk = new NativeZkBridge();

            // 2. Generate random secret and nonce for the commitment
            var secretHex = crypto.hashHex("user_secret_entropy_1234");
            var nonceHex = crypto.hashHex("user_nonce_entropy_5678");
            System.out.println("Secret: " + secretHex.substring(0, 16) + "...");
            System.out.println("Nonce:  " + nonceHex.substring(0, 16) + "...");

            // 3. Compute the Poseidon commitment
            Commitment commitment = zk.computeCommitment(DEPOSIT_VALUE, secretHex, nonceHex);
            System.out.println("Commitment hash: " + commitment.hash());
            System.out.println("Commitment value: " + commitment.value());

            // 4. Generate a preimage proof for the deposit
            StarkProof depositProof = zk.generatePreimageProof(
                    new long[]{DEPOSIT_VALUE, Long.parseLong(secretHex.substring(0, 15), 16)});
            System.out.println("Deposit proof generated, vk: " + depositProof.vk().substring(0, 32) + "...");

            // 5. Submit the shielded deposit
            Receipt depositReceipt = client.shielded()
                    .deposit(commitment.hash(), DEPOSIT_VALUE, depositProof.proof());
            System.out.printf("Deposit confirmed at block %d, status: %s, tx: %s%n",
                    depositReceipt.blockHeight(), depositReceipt.status(),
                    depositReceipt.txHash());

            // 6. Poll to confirm the deposit receipt
            Receipt polledDeposit = client.receipt(depositReceipt.txHash())
                    .waitFor(20, Duration.ofSeconds(2));
            System.out.println("Deposit receipt status: " + polledDeposit.status());

            // 7. Query the current commitment root
            String commitmentRoot = client.shielded().commitmentRoot();
            System.out.println("Commitment root: " + commitmentRoot);

            // 8. Compute the nullifier for withdrawal
            Nullifier nullifier = zk.computeNullifier(secretHex, nonceHex);
            System.out.println("Nullifier hash: " + nullifier.hash());

            // 9. Verify the nullifier has not been spent
            boolean spent = client.shielded().isNullifierSpent(nullifier.hash());
            if (spent) {
                System.err.println("Nullifier already spent. Aborting withdrawal.");
                System.exit(1);
            }
            System.out.println("Nullifier is unspent. Proceeding with withdrawal.");

            // 10. Generate a preimage proof for the withdrawal
            StarkProof withdrawProof = zk.generatePreimageProof(
                    new long[]{DEPOSIT_VALUE, Long.parseLong(nonceHex.substring(0, 15), 16)});
            System.out.println("Withdraw proof generated.");

            // 11. Submit the shielded withdrawal
            Receipt withdrawReceipt = client.shielded()
                    .withdraw(nullifier.hash(), DEPOSIT_VALUE, RECIPIENT,
                            withdrawProof.proof(), commitmentRoot);
            System.out.printf("Withdrawal confirmed at block %d, status: %s, tx: %s%n",
                    withdrawReceipt.blockHeight(), withdrawReceipt.status(),
                    withdrawReceipt.txHash());

            // 12. Final confirmation via receipt polling
            Receipt polledWithdraw = client.receipt(withdrawReceipt.txHash())
                    .waitFor(20, Duration.ofSeconds(2));
            System.out.println("Withdrawal receipt status: " + polledWithdraw.status());

        } catch (HttpException e) {
            System.err.printf("HTTP error %d: %s%n", e.statusCode(), e.getMessage());
            System.exit(1);
        } catch (RpcException e) {
            System.err.printf("RPC error %d: %s%n", e.code(), e.getMessage());
            System.exit(1);
        } catch (TimeoutException e) {
            System.err.println("Timeout: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Shielded pool error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 9: ZK Proof Generation & Verification

Generate and verify zero-knowledge proofs using the native ZK bridge. Demonstrates Poseidon hashing, preimage proof generation and verification, range proof generation and verification, and commitment/nullifier computation. Covers: all standalone ZK operations without network interaction.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;
import org.dilithia.sdk.zk.NativeZkBridge;

public class ZkProofDemo {

    public static void main(String[] args) {
        try {
            var crypto = new NativeCryptoBridge();
            var zk = new NativeZkBridge();

            // ---- POSEIDON HASHING ----

            // 1. Hash a set of field elements with Poseidon
            String hash1 = zk.poseidonHash(new long[]{42, 100, 7});
            System.out.println("Poseidon hash [42, 100, 7]: " + hash1);

            String hash2 = zk.poseidonHash(new long[]{42, 100, 8});
            System.out.println("Poseidon hash [42, 100, 8]: " + hash2);
            System.out.println("Hashes differ: " + !hash1.equals(hash2));

            // ---- COMMITMENT & NULLIFIER ----

            // 2. Compute a commitment from value, secret, and nonce
            var secretHex = crypto.hashHex("my_secret_data");
            var nonceHex = crypto.hashHex("my_nonce_data");
            long value = 500_000;

            Commitment commitment = zk.computeCommitment(value, secretHex, nonceHex);
            System.out.println("\nCommitment hash:   " + commitment.hash());
            System.out.println("Commitment value:  " + commitment.value());
            System.out.println("Commitment secret: " + commitment.secret().substring(0, 16) + "...");
            System.out.println("Commitment nonce:  " + commitment.nonce().substring(0, 16) + "...");

            // 3. Compute the nullifier from the same secret and nonce
            Nullifier nullifier = zk.computeNullifier(secretHex, nonceHex);
            System.out.println("Nullifier hash:    " + nullifier.hash());

            // 4. Verify determinism — same inputs produce same outputs
            Commitment commitment2 = zk.computeCommitment(value, secretHex, nonceHex);
            Nullifier nullifier2 = zk.computeNullifier(secretHex, nonceHex);
            System.out.println("\nCommitment deterministic: " + commitment.hash().equals(commitment2.hash()));
            System.out.println("Nullifier deterministic:  " + nullifier.hash().equals(nullifier2.hash()));

            // ---- PREIMAGE PROOF ----

            // 5. Generate a preimage proof
            long[] preimageInputs = new long[]{value, 42, 7};
            StarkProof preimageProof = zk.generatePreimageProof(preimageInputs);
            System.out.println("\nPreimage proof size: " + preimageProof.proof().length() + " chars");
            System.out.println("Preimage vk:        " + preimageProof.vk().substring(0, 32) + "...");
            System.out.println("Preimage inputs:    " + preimageProof.inputs().length + " elements");

            // 6. Verify the preimage proof — should pass
            boolean preimageValid = zk.verifyPreimageProof(
                    preimageProof.proof(), preimageProof.vk(), preimageProof.inputs());
            System.out.println("Preimage proof valid: " + preimageValid);

            // 7. Verify with tampered inputs — should fail
            long[] tamperedInputs = new long[]{value + 1, 42, 7};
            boolean tamperedValid = zk.verifyPreimageProof(
                    preimageProof.proof(), preimageProof.vk(), tamperedInputs);
            System.out.println("Tampered proof valid: " + tamperedValid);

            // ---- RANGE PROOF ----

            // 8. Generate a range proof — prove value is within [min, max]
            long rangeValue = 750;
            long rangeMin = 0;
            long rangeMax = 1_000_000;
            StarkProof rangeProof = zk.generateRangeProof(rangeValue, rangeMin, rangeMax);
            System.out.println("\nRange proof size: " + rangeProof.proof().length() + " chars");
            System.out.println("Range vk:        " + rangeProof.vk().substring(0, 32) + "...");

            // 9. Verify the range proof — should pass
            boolean rangeValid = zk.verifyRangeProof(
                    rangeProof.proof(), rangeProof.vk(), rangeProof.inputs());
            System.out.println("Range proof valid (750 in [0, 1000000]): " + rangeValid);

            // 10. Generate a range proof for a boundary value
            StarkProof boundaryProof = zk.generateRangeProof(rangeMax, rangeMin, rangeMax);
            boolean boundaryValid = zk.verifyRangeProof(
                    boundaryProof.proof(), boundaryProof.vk(), boundaryProof.inputs());
            System.out.println("Boundary proof valid (1000000 in [0, 1000000]): " + boundaryValid);

            System.out.println("\nAll ZK operations completed successfully.");

        } catch (Exception e) {
            System.err.println("ZK proof error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 10: Name Service & Identity Profile

A utility that registers a .dili name, configures profile records, resolves names, and demonstrates the full name service lifecycle. Covers: getRegistrationCost, isNameAvailable, registerName, setNameTarget, setNameRecord, getNameRecords, lookupName, resolveName, reverseResolveName, getNamesByOwner, renewName, transferName, releaseName.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;
import java.util.List;
import java.util.Map;

public class NameServiceFlow {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String NAME = "alice";
    static final String TRANSFER_TO = "dil1_bob_address";

    public static void main(String[] args) {
        try {
            // 1. Initialize client and crypto adapter
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover wallet from mnemonic
            String mnemonic = System.getenv("WALLET_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new DilithiaException("Set WALLET_MNEMONIC env var");
            }
            var account = crypto.recoverHdWallet(mnemonic);
            System.out.println("Address: " + account.address());

            // 3. Query registration cost for the name
            var cost = client.getRegistrationCost(NAME);
            System.out.printf("Registration cost for \"%s\": %s%n", NAME, cost.formatted());

            // 4. Check if name is available
            boolean available = client.isNameAvailable(NAME);
            System.out.printf("Name \"%s\" available: %s%n", NAME, available);
            if (!available) {
                throw new DilithiaException("Name \"" + NAME + "\" is already taken");
            }

            // 5. Register name
            String regHash = client.registerName(NAME, account.address(), account.secretKey());
            var regReceipt = client.waitForReceipt(regHash, 20, Duration.ofSeconds(2));
            System.out.printf("Name registered in block %d%n", regReceipt.blockHeight());

            // 6. Set target address
            String targetHash = client.setNameTarget(NAME, account.address(), account.secretKey());
            client.waitForReceipt(targetHash, 20, Duration.ofSeconds(2));
            System.out.printf("Target address set to %s%n", account.address());

            // 7. Set profile records
            var records = Map.of(
                    "display_name", "Alice",
                    "avatar", "https://example.com/alice.png",
                    "bio", "Builder on Dilithia",
                    "email", "alice@example.com",
                    "website", "https://alice.dev"
            );
            for (var entry : records.entrySet()) {
                String hash = client.setNameRecord(NAME, entry.getKey(), entry.getValue(), account.secretKey());
                client.waitForReceipt(hash, 20, Duration.ofSeconds(2));
                System.out.printf("  Set record \"%s\" = \"%s\"%n", entry.getKey(), entry.getValue());
            }

            // 8. Get all records
            var allRecords = client.getNameRecords(NAME);
            System.out.println("All records: " + allRecords);

            // 9. Resolve name to address
            String resolved = client.resolveName(NAME);
            System.out.printf("resolveName(\"%s\") -> %s%n", NAME, resolved);

            // 10. Reverse resolve address to name
            String reverseName = client.reverseResolveName(account.address());
            System.out.printf("reverseResolveName(\"%s\") -> %s%n", account.address(), reverseName);

            // 11. List all names by owner
            List<String> owned = client.getNamesByOwner(account.address());
            System.out.printf("Names owned by %s: %s%n", account.address(), owned);

            // 12. Renew name
            String renewHash = client.renewName(NAME, account.secretKey());
            var renewReceipt = client.waitForReceipt(renewHash, 20, Duration.ofSeconds(2));
            System.out.printf("Name renewed in block %d%n", renewReceipt.blockHeight());

            // 13. Transfer name to another address
            String transferHash = client.transferName(NAME, TRANSFER_TO, account.secretKey());
            var transferReceipt = client.waitForReceipt(transferHash, 20, Duration.ofSeconds(2));
            System.out.printf("Name transferred to %s in block %d%n", TRANSFER_TO, transferReceipt.blockHeight());

        } catch (DilithiaException e) {
            System.err.println("Name service error: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Unexpected error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Scenario 11: Credential Issuance & Verification

An issuer creates a KYC credential schema, issues a credential to a holder, and a verifier checks it with selective disclosure. Covers: registerSchema, issueCredential, getSchema, getCredential, listCredentialsByHolder, listCredentialsByIssuer, verifyCredential, revokeCredential.

package com.example;

import org.dilithia.sdk.*;
import org.dilithia.sdk.model.*;
import org.dilithia.sdk.crypto.NativeCryptoBridge;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;

public class CredentialFlow {
    static final String RPC_URL = "https://rpc.dilithia.network/rpc";
    static final String HOLDER_ADDRESS = "dil1_holder_address";

    public static void main(String[] args) {
        try {
            // 1. Initialize client and crypto adapter
            var client = Dilithia.client(RPC_URL).timeout(Duration.ofSeconds(15)).build();
            var crypto = new NativeCryptoBridge();

            // 2. Recover issuer wallet
            String mnemonic = System.getenv("ISSUER_MNEMONIC");
            if (mnemonic == null || mnemonic.isBlank()) {
                throw new DilithiaException("Set ISSUER_MNEMONIC env var");
            }
            var issuer = crypto.recoverHdWallet(mnemonic);
            System.out.println("Issuer address: " + issuer.address());

            // 3. Register a KYC schema
            var schema = new SchemaDefinition("KYC_Basic_v1", List.of(
                    new SchemaAttribute("full_name", "string"),
                    new SchemaAttribute("country", "string"),
                    new SchemaAttribute("age", "u64"),
                    new SchemaAttribute("verified", "bool")
            ));
            String schemaHash = client.registerSchema(schema, issuer.secretKey());
            var schemaReceipt = client.waitForReceipt(schemaHash, 20, Duration.ofSeconds(2));
            String registeredHash = schemaReceipt.logs().get(0).get("schema_hash").toString();
            System.out.println("Schema registered: " + registeredHash);

            // 4. Issue credential to holder with commitment hash
            String commitmentInput = HOLDER_ADDRESS + ":KYC_Basic_v1:" + Instant.now().getEpochSecond();
            String commitmentHash = crypto.hashHex(commitmentInput.getBytes());

            String issueHash = client.issueCredential(new CredentialParams(
                    registeredHash,
                    HOLDER_ADDRESS,
                    commitmentHash,
                    Map.of("full_name", "Alice Smith", "country", "CH", "age", 30, "verified", true)
            ), issuer.secretKey());
            var issueReceipt = client.waitForReceipt(issueHash, 20, Duration.ofSeconds(2));
            System.out.printf("Credential issued in block %d%n", issueReceipt.blockHeight());

            // 5. Get schema by hash
            var fetchedSchema = client.getSchema(registeredHash);
            System.out.println("Schema: " + fetchedSchema);

            // 6. Get credential by commitment
            var credential = client.getCredential(commitmentHash);
            System.out.println("Credential: " + credential);

            // 7. List credentials by holder
            var holderCreds = client.listCredentialsByHolder(HOLDER_ADDRESS);
            System.out.printf("Holder has %d credential(s)%n", holderCreds.size());

            // 8. List credentials by issuer
            var issuerCreds = client.listCredentialsByIssuer(issuer.address());
            System.out.printf("Issuer has %d credential(s)%n", issuerCreds.size());

            // 9. Verify selective disclosure — prove age > 18 without revealing exact age
            var proof = crypto.generateSelectiveDisclosureProof(
                    commitmentHash,
                    List.of("age"),
                    Map.of("age", Map.of("operator", "gt", "threshold", 18))
            );
            boolean verified = client.verifyCredential(commitmentHash, proof);
            System.out.printf("Selective disclosure (age > 18) verified: %s%n", verified);

            // 10. Revoke the credential
            String revokeHash = client.revokeCredential(commitmentHash, issuer.secretKey());
            var revokeReceipt = client.waitForReceipt(revokeHash, 20, Duration.ofSeconds(2));
            System.out.printf("Credential revoked in block %d%n", revokeReceipt.blockHeight());

            // 11. Verify revocation by fetching credential again
            var revokedCred = client.getCredential(commitmentHash);
            System.out.printf("Credential status after revocation: %s%n", revokedCred.status());

        } catch (DilithiaException e) {
            System.err.println("Credential error: " + e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Unexpected error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}