PrivacyPoolsContract is the on-chain execution boundary for the Arcane Compliance privacy pool on Stellar/Soroban. It receives a zero-knowledge proof and its associated public signals from your application, verifies the proof against the current pool state, applies any public token movements, updates the Merkle tree and nullifier registry, and emits an encrypted audit event. Every deposit, withdrawal, transfer, and mixed transaction your users perform flows through this single contract.
The transact entrypoint
The contract exposes one public transaction entrypoint. All operation types — deposits, withdrawals, transfers, and mixed transactions — are expressed through this single method.
pub fn transact(
env: &Env,
from: Address,
proof_bytes: Bytes,
pub_signals_bytes: Bytes,
encoded: Bytes,
) -> Result<(), Error>
| Argument | Source | Meaning |
|---|
from | Stellar wallet | The authenticated transaction signer. The contract calls from.require_auth() before doing anything else. |
proof_bytes | Arcane SDK | Serialized Groth16 proof generated client-side from the witness. |
pub_signals_bytes | Arcane SDK | Serialized public inputs and outputs for the circuit, including the state root, nullifier hashes, and output commitments. |
encoded | Application / SDK flow | Encrypted audit payload that the contract emits verbatim as AuditEncodedDigest.digest. Your application prepares this using the SDK. |
Execution sequence
When you call transact, the contract performs the following steps in order. A failed check at any step causes the entire transaction to revert.
The contract stores either zero output commitments or exactly two output commitments. Submitting exactly one non-zero output commitment is rejected. This constraint keeps the contract aligned with the circuit’s two-output transaction model.
Transaction types
The user-facing operation names are protocol-level concepts. They are not separate Soroban methods — they are all represented as different shapes of transact inputs.
| User-facing operation | transact representation |
|---|
| Deposit | Public deposit token leg plus one or two private output commitments |
| Withdrawal | Private input commitment spend plus a public withdrawal token leg |
| Transfer | Private input commitment spend plus private output commitments — no public token movement |
| Mixed transaction | Combination of private input spends, private output commitments, public deposits, and public withdrawals |
What the contract validates
transact enforces the following checks before accepting a transaction:
- Authentication —
from.require_auth() must pass. The signer must authorize the invocation.
- Root history membership — the
stateRoot embedded in pub_signals_bytes must be present in the contract’s root history ring buffer. Proofs built against a root that has aged out of history will be rejected.
- Nullifier uniqueness — every nullifier hash extracted from
pub_signals_bytes must not already be marked as consumed. Any reuse causes an immediate revert.
- Groth16 proof validity — the proof must verify against the stored verification key using the provided public signals. An invalid or malformed proof is rejected.
If the stateRoot in your proof has aged out of the root history window, you must rebuild the witness against a more recent root and re-generate the proof before resubmitting. See On-Chain State for details on root history.
Public getters
The contract exposes read-only getter methods for all pool state your application needs to construct witnesses, monitor pool health, or display balances.
| Getter | Purpose |
|---|
get_merkle_root | Returns the current Merkle root of the commitment tree |
get_merkle_depth | Returns the configured depth of the Merkle tree |
get_commitment_count | Returns the number of leaves currently stored in the tree |
get_commitments | Returns all commitment leaf hashes |
get_leaf_ephemeral(index) | Returns the ephemeral BabyJubJub public key stored alongside a given leaf, used for recipient-side shared-secret recovery |
get_pairwise_frontier | Returns the current pairwise frontier state used by the LeanIMT insertion algorithm |
is_nulifier_hash_consumed(hash) | Returns true if the given nullifier hash has already been spent |
is_known_root(root) | Returns true if the given root is still present in the root history ring buffer |
get_token_balance | Returns the current token balance held by the pool contract |
get_public_slot_config | Returns the public input and output slot configuration for the circuit |
get_admin | Returns the contract admin address |
Before building a withdrawal or transfer witness, call get_merkle_root, get_commitment_count, and get_commitments to get a consistent snapshot of the current tree state. Use the root returned by get_merkle_root as the stateRoot in your proof to maximize the chance that the root is still in history when your transaction lands.