Skip to content

Shielded Pool

The Dilithia shielded pool lets users make private transactions using zero-knowledge proofs. Deposited tokens become unlinkable to the depositor's public address. Withdrawals require a STARK proof (generated by the wallet) that proves ownership without revealing the deposit.

Overview

Operation Layer Requires Wallet?
Deposit Wallet Yes
Withdraw Wallet Yes (generates STARK proof)
Check commitment root ChainClient No
Check nullifier spent ChainClient No
Compliance proof Wallet Yes

Deposit Flow

Depositing moves tokens from your public balance into the shielded pool. The wallet:

  1. Generates a random secret and nonce
  2. Computes a Poseidon hash commitment: commit = Poseidon(secret, nonce, amount)
  3. Stores the secret/nonce locally (needed for future withdrawal)
  4. Submits a transaction that adds the commitment to the on-chain Merkle tree
import { connect, shieldedDeposit, createChainClient } from "@dilithia/browser-sdk";

const session = await connect();
const chain = createChainClient(session.rpcUrl);

const deposit = await shieldedDeposit(500);
console.log("Commitment:", deposit.commitment);

const receipt = await chain.waitForReceipt(deposit.txHash);
console.log("Deposit confirmed at block", receipt.blockHeight);

Back up your wallet

The secret and nonce are stored in the wallet extension's local storage. If you lose access to the wallet, you lose access to your shielded funds. Always keep a backup of your wallet.


Withdraw Flow

Withdrawing moves tokens from the shielded pool back to a public address. The wallet:

  1. Loads the secret/nonce for the specified commitment
  2. Generates a STARK proof (~200 ms via WASM prover) proving ownership
  3. Computes a nullifier to prevent double-spending
  4. Signs and submits the withdrawal transaction
import { shieldedWithdraw, createChainClient, connect } from "@dilithia/browser-sdk";

const session = await connect();
const chain = createChainClient(session.rpcUrl);

// Withdraw commitment at index 0, amount 500, to dili1alice
const withdrawal = await shieldedWithdraw(0, 500, "dili1alice");
console.log("Nullifier:", withdrawal.nullifier);
console.log("Proof:", withdrawal.proof);

const receipt = await chain.waitForReceipt(withdrawal.txHash);
console.log("Withdrawal confirmed at block", receipt.blockHeight);

The commitmentIndex parameter refers to the index within your local list of shielded commitments (managed by the wallet).


Check Shielded Balance

The wallet tracks your unspent commitments locally. This data is not queryable on-chain without the secret.

Note

shieldedBalance() is a wallet-level function documented in the README. It returns commitments tracked by the wallet extension's local storage.


On-Chain Queries via ChainClient

You can inspect the shielded pool's public on-chain state without a wallet using the ChainClient:

Commitment Root

Get the current Merkle root of the commitment tree:

import { createChainClient } from "@dilithia/browser-sdk";

const chain = createChainClient("https://rpc.dilithia.network/rpc");
const root = await chain.getCommitmentRoot();
console.log("Commitment root:", root);

Nullifier Status

Check whether a nullifier has been spent (used in a withdrawal):

const spent = await chain.isNullifierSpent(withdrawal.nullifier);
if (spent) {
  console.log("This commitment has already been withdrawn.");
}

This is useful for verifying withdrawal proofs or building block explorers.


Compliance Proofs

Compliance proofs let users prove statements about their shielded activity without revealing the underlying data. This enables regulatory compliance while preserving privacy.

Proof Types

Type Description Parameters
"not_on_sanctions" Prove the account is not on a sanctions list { sanctionsList: string }
"tax_paid" Prove taxes have been paid on shielded activity { jurisdiction: string, year: number }
"balance_range" Prove the shielded balance falls within a range { min: number, max: number }

Example: Balance Range Proof

Prove that your shielded balance is between 0 and 10,000 DILI without revealing the exact amount:

import { shieldedComplianceProof } from "@dilithia/browser-sdk";

const { proof, publicInputs } = await shieldedComplianceProof("balance_range", {
  min: 0,
  max: 10000,
});

// Send proof to a verifier (e.g., your backend or a smart contract)
await fetch("/api/verify-compliance", {
  method: "POST",
  body: JSON.stringify({ proof, publicInputs }),
});

Example: Sanctions Check

const { proof, publicInputs } = await shieldedComplianceProof("not_on_sanctions", {
  sanctionsList: "ofac_sdn_2024",
});

Example: Tax Compliance

const { proof, publicInputs } = await shieldedComplianceProof("tax_paid", {
  jurisdiction: "US",
  year: 2025,
});

Both deposits and withdrawals support gas sponsorship via a paymaster. See the Gas Sponsor guide for details.

// Sponsored deposit
const deposit = await shieldedDeposit(500, { paymaster: "dili1sponsor" });

// Sponsored withdrawal
const withdrawal = await shieldedWithdraw(0, 500, "dili1alice", {
  paymaster: "dili1sponsor",
});

This is particularly useful for withdrawals, where the recipient may not yet have a public balance to pay gas.