Atomic constructor — runs exactly once, at deploy: pin both verifying keys and the pooled asset, and seed the empty incremental tree (frontier = zero subtrees, root = the empty-tree root).
H1: this is a __constructor rather than a separate init call, so there is no
post-deploy window in which an attacker could front-run initialization and bind a
malicious verifying key (which would accept forged proofs and drain the pool).
Verifying keys and the asset are fixed in the same transaction that creates the
contract, and cannot be changed afterward.
fn __constructor(
env: soroban_sdk::Env,
token: soroban_sdk::Address,
vk_shield: VerifyingKey,
vk_withdraw: VerifyingKey,
vk_transfer: VerifyingKey,
vk_claim: VerifyingKey,
)
Shield amount of the pooled asset under commitment.
Verifies the shield proof binds commitment to the PUBLIC amount, pulls the
funds from depositor, inserts the commitment into the tree, and emits
DepositCreated. Public inputs (pinned order): [commitment, amount].
fn shield(
env: soroban_sdk::Env,
proof: Proof,
commitment: soroban_sdk::BytesN<32>,
amount: i128,
depositor: soroban_sdk::Address,
) -> Result
Shielded→public withdrawal with PRIVATE CHANGE (join-split, 1-in / 1-public-out / 1-change).
Verifies the withdraw proof (inclusion of the spent note, its nullifier, a well-formed
change commitment, value conservation value == amount + change, a 64-bit range on every
amount, and the C1 recipient binding), spends the nullifier, and pays the PUBLIC amount
out to to. When has_change is true it inserts the change commitment as a new private
note; when false (a FULL EXIT, where the circuit forces change == 0) it inserts nothing and
needs no free leaf — so a note can always be withdrawn even when the tree is full. Only
amount is public; the change value is hidden.
Public inputs (pinned order): [root, nullifier, recipient, amount, change_commitment, has_change]. Emits WithdrawalCompleted; returns the change note's leaf index (0 on a full exit).
fn withdraw(
env: soroban_sdk::Env,
proof: Proof,
root: soroban_sdk::BytesN<32>,
nullifier: soroban_sdk::BytesN<32>,
recipient: soroban_sdk::BytesN<32>,
amount: i128,
change_commitment: soroban_sdk::BytesN<32>,
has_change: bool,
change_ct: soroban_sdk::Bytes,
to: soroban_sdk::Address,
) -> Result
Confidential shielded→shielded transfer (join-split, 1-in / 2-out).
Verifies the transfer proof (which proves, in zero knowledge, inclusion of the spent input note, its nullifier, two well-formed output commitments, value conservation, and a 64-bit range on every amount), spends the input nullifier, and inserts BOTH output commitments (recipient + change). No amount is revealed and NO token moves — value stays in the pool, re-split. Public inputs (pinned order): [root, nullifier, out_commitment1, out_commitment2].
ONE on-chain insert: only the sender's change (out2) is inserted; the recipient's note
(out1) is recorded PENDING for the recipient to insert themselves via claim_insert.
Halving the per-tx Merkle work is what lets the tree be far deeper. Returns out2's leaf.
fn transfer(
env: soroban_sdk::Env,
proof: Proof,
root: soroban_sdk::BytesN<32>,
nullifier: soroban_sdk::BytesN<32>,
out_commitment1: soroban_sdk::BytesN<32>,
out_commitment2: soroban_sdk::BytesN<32>,
change_ct: soroban_sdk::Bytes,
) -> Result
Claim a received private-send note: verify the recipient holds a valid opening of
commitment (the claim circuit — value stays hidden), confirm it is a PENDING transfer
output (backing → no inflation), insert it into the tree, clear the pending flag, and emit
its encrypted opening for cross-device recovery. This is the deferred second insert that
makes transfer a single-insert operation. No token moves — the value is already pooled.
Returns the leaf index.
fn claim_insert(
env: soroban_sdk::Env,
proof: Proof,
commitment: soroban_sdk::BytesN<32>,
note_ct: soroban_sdk::Bytes,
) -> Result
fn current_root(env: soroban_sdk::Env) -> soroban_sdk::BytesN<32>
fn is_spent(env: soroban_sdk::Env, nullifier: soroban_sdk::BytesN<32>) -> bool
fn next_index(env: soroban_sdk::Env) -> u32
Thin contract wrapper around [verify_groth16].
fn verify(
env: soroban_sdk::Env,
vk: VerifyingKey,
proof: Proof,
public_inputs: soroban_sdk::Vec>,
) -> bool