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

Table of Contents
Core Principles
Architecture Overview
Smart Contracts
EightMix.sol
MerkleTreeWithHistory.sol
IHasher.sol
ZK Circuit
Deposit Flow
Withdraw Flow
Frontend
Core Principles
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
Architecture Overview
Smart Contracts
EightMix.sol
The main pool contract. One instance per denomination. Immutable after deployment.
Storage:
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:
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 bothdepositandwithdrawChecks-Effects-Interactions pattern in
deposit(state set beforetransferFrom)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:
TREE_DEPTH
20
2^20 = 1,048,576 max deposits
ROOT_HISTORY
30
Number of historical roots kept
Initialization:
Constructor receives
IHasher(Poseidon)Computes
zeros[0..20]— empty subtree hashes at each levelSets
roots[0]to the empty tree root_deployeris stored asimmutableforinitialize()access control
Two-step linking:
Deploy
MerkleTreeWithHistory→ records_deployer = msg.senderDeploy
EightMixwith the tree addressCall
merkleTree.initialize(poolAddress)— only deployer, only once
Insert algorithm:
O(20) Poseidon hashes per insert
Bitwise
pos & 1for 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)explicitlyReturns
falseif 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.
ZK Circuit
File: circuits/withdraw.circom (circom 2.1.6)
Templates:
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:
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:
commitment = Poseidon(nullifier, secret)nullifierHash = Poseidon(nullifier)— matches public inputMerkleProof(commitment, pathElements, pathIndices) = root— matches public inputpathIndices[i] * (pathIndices[i] - 1) === 0— binary enforcement (prevents proof forgery)recipient²andrelayer²— constrains these public inputs into the proof (prevents front-running substitution)
Proving system: Groth16 over BN254
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.
Withdraw Flow
Frontend
Single-page React application with a dark-themed, minimal UI.
Tech Stack
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
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
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: falsein production build — no source code exposureNo CDN dependencies — all assets bundled and served from IPFS
No analytics, no tracking, no cookies
Base58Check address validation (SHA-256 double-hash checksum)
AbortControlleron all network requests — timeout protectionevent.origincheck onpostMessagehandler — prevents iframe injectionPrivate 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

