circle-infoTechnical Overview

Architecture, smart contracts, ZK circuit, security model, API, and deployment guide

folder-grid Table of Contents

  • Core Principles

  • Architecture Overview

  • Smart Contracts

    • EightMix.sol

    • MerkleTreeWithHistory.sol

    • IHasher.sol

  • ZK Circuit

  • Deposit Flow

  • Withdraw Flow

  • Frontend


group-arrows-rotate Core Principles

Principle
Implementation

No owner / admin

No privileged roles, no access control on pool operations

No upgradability

All contracts are immutable after deployment

No emergency shutdown

Anonymity cannot be compromised by any party

Single access path

Funds can only be withdrawn with a valid ZK-proof

Fixed fee

4 USDT relayer fee, hardcoded as constant in the contract

Zero protocol revenue

0% protocol fee — the protocol takes nothing

Relayer subsidized gas

consume_user_resource_percent = 0% — relayer covers 100% of energy costs

Client-side secrets

Private notes (nullifier + secret) never leave the user's browser


sitemap Architecture Overview


file-lock Smart Contracts

EightMix.sol

The main pool contract. One instance per denomination. Immutable after deployment.

Storage:

Variable
Type
Description

token

IERC20 immutable

TRC-20 USDT contract

denomination

uint256 immutable

Pool denomination in sun

verifier

IVerifier immutable

Groth16 verifier contract

merkleTree

IMerkleTree immutable

Merkle tree contract

RELAYER_FEE

uint256 constant

4,000,000 sun (4 USDT)

nullifierHashes

mapping(bytes32 → bool)

Spent nullifiers

commitments

mapping(bytes32 → bool)

Used commitments

Functions:

Function
Access
Description

deposit(bytes32 commitment)

public

Deposit USDT, insert commitment into Merkle tree

withdraw(bytes proof, bytes32 root, bytes32 nullifierHash, address recipient, address relayer)

public

Withdraw with ZK-proof

isSpent(bytes32)

view

Check if nullifier has been used

isKnownRoot(bytes32)

view

Check if root is in history

currentRoot()

view

Get latest Merkle root

canWithdraw(bytes32, bytes32)

view

Composite check (spent + root)

poolBalance()

view

USDT balance held by contract

Security modifiers:

  • nonReentrant — on both deposit and withdraw

  • Checks-Effects-Interactions pattern in deposit (state set before transferFrom)

  • receive() / fallback() revert — no TRX accepted

Constructor validations:

  • All addresses non-zero

  • denomination > RELAYER_FEE (prevents zero or negative payouts)


MerkleTreeWithHistory.sol

Incremental Merkle tree with a ring buffer of historical roots.

Parameters:

Constant
Value
Description

TREE_DEPTH

20

2^20 = 1,048,576 max deposits

ROOT_HISTORY

30

Number of historical roots kept

Initialization:

  1. Constructor receives IHasher (Poseidon)

  2. Computes zeros[0..20] — empty subtree hashes at each level

  3. Sets roots[0] to the empty tree root

  4. _deployer is stored as immutable for initialize() access control

Two-step linking:

  1. Deploy MerkleTreeWithHistory → records _deployer = msg.sender

  2. Deploy EightMix with the tree address

  3. Call merkleTree.initialize(poolAddress) — only deployer, only once

Insert algorithm:

  • O(20) Poseidon hashes per insert

  • Bitwise pos & 1 for left/right selection (gas-efficient)

  • Root stored in ring buffer at (currentRootIndex + 1) % 30

Root validation (isKnownRoot):

  • Iterates backwards through the ring buffer

  • Rejects bytes32(0) explicitly

  • Returns false if root has expired (more than 30 inserts since)


IHasher.sol

Minimal interface for the Poseidon hash function:

Implementation (PoseidonT3.sol) is generated from circomlibjs via scripts/generateHasher.js. This is a pure function — no state, no gas cost for STATICCALL.


user-shield ZK Circuit

File: circuits/withdraw.circom (circom 2.1.6)

Templates:

Template
Description

CommitmentHasher

Poseidon(nullifier, secret) → commitment; Poseidon(nullifier) → nullifierHash

MerkleProof(20)

Verify inclusion of commitment in Merkle tree

Withdraw(20)

Main circuit — combines both, constrains public inputs

Signals:

Signal
Visibility
Type
Description

root

public

bytes32

Merkle root

nullifierHash

public

bytes32

Hash of nullifier

recipient

public

address

Funds recipient (constrained via square)

relayer

public

address

Relayer address (constrained via square)

nullifier

private

field

Random nullifier (31 bytes)

secret

private

field

Random secret (31 bytes)

pathElements[20]

private

bytes32[]

Merkle proof siblings

pathIndices[20]

private

bit[]

Left/right path (binary-constrained)

Key constraints:

  1. commitment = Poseidon(nullifier, secret)

  2. nullifierHash = Poseidon(nullifier) — matches public input

  3. MerkleProof(commitment, pathElements, pathIndices) = root — matches public input

  4. pathIndices[i] * (pathIndices[i] - 1) === 0 — binary enforcement (prevents proof forgery)

  5. recipient² and relayer² — constrains these public inputs into the proof (prevents front-running substitution)

Proving system: Groth16 over BN254

money-bill-transfer Deposit Flow

Critical: The note (8mix-{denom}-0x{nullifier}{secret}) is the only way to withdraw funds. Loss of the note means permanent loss of funds.


file-lock Withdraw Flow


bring-forward Frontend

Single-page React application with a dark-themed, minimal UI.

Tech Stack

Layer
Technology

Framework

React 18

Build

Vite 5 + esbuild

Styling

Pure CSS (no frameworks)

Wallet

TronLink (direct) + WalletConnect v2 (QR code)

Crypto

circomlibjs (Poseidon hash), snarkjs (Groth16 proofs), Web Crypto API (SHA-256 checksums)

State

React Context (WalletProvider, ToastProvider, PoolProvider)

ZK

snarkjs in-browser — proof generation via Web Workers (WASM + ZKEY ~5 MB lazy-loaded)

Hosting

IPFS + ENS (8mix.eth → eth.limo gateway)

API

api.8mix.gg (relayer, pool queries)

Components

Component
Purpose

App.jsx

Shell — pool selector, deposit/withdraw tabs, locked state during operations

Header.jsx

Logo, wallet connect button, address display

ConnectModal.jsx

TronLink / WalletConnect selection with loading state

PoolSelector.jsx

Denomination chips (100/1000/3000)

DepositView.jsx

3-step flow: generate note → approve → deposit

WithdrawView.jsx

3-step flow: parse note → verify → withdraw

NoteModal.jsx

Display private note with copy button and auto-close timer

PoolStats.jsx

Pool balance display

StepsList.jsx

Reusable step-by-step progress indicator

ErrorBoundary.jsx

Catches unhandled render errors, shows fallback UI with "Try Again" button

Hooks

Hook
Purpose

useWallet

Manages TronLink + WalletConnect connections, signAndBroadcast

usePool

Contract read/write operations (deposit, withdraw, approve, balance)

useToast

Toast notification system with timer cleanup

PoolContext / usePoolContext

React Context wrapping usePool — single instance shared by DepositView, WithdrawView, PoolStats (prevents duplicate API calls)

Security Measures

  • Content Security Policy (CSP) — restricts scripts, connections, frames

  • sourcemap: false in production build — no source code exposure

  • No CDN dependencies — all assets bundled and served from IPFS

  • No analytics, no tracking, no cookies

  • Base58Check address validation (SHA-256 double-hash checksum)

  • AbortController on all network requests — timeout protection

  • event.origin check on postMessage handler — prevents iframe injection

  • Private notes saved to sessionStorage (auto-expire 30 min, cleared on deposit)

  • ZK proofs generated entirely client-side — no secrets leave the browser

  • Separate TronGrid API keys for frontend and API server

  • React Error Boundary — catches unhandled render errors

Last updated