Contracts explained
GothamCash is powered by a minimal and transparent smart contract, divided into several sub-contracts called pools. Each pool corresponds to a specific denomination. Below is a breakdown of how the GothamMixer
contract works and what each part is responsible for.
The 0.1 BNB pool smart contract is open-source, and available there.
📌 Constants and Globals
uint256 public constant DEPOSIT_AMOUNT = 0.1 ether;
uint256 public constant FEE_PERCENT = 1;
uint256 public constant EXPIRY_TIME = 365 days;
address public owner;
DEPOSIT_AMOUNT: The fixed amount users can deposit (0.1 BNB / 1 BNB / 10 BNB).
FEE_PERCENT: Optional 1% fee for deposits/withdrawals (activatable).
EXPIRY_TIME: After 365 days, unclaimed deposits can be reclaimed by the owner.
owner: Address with admin rights (can reclaim expired funds, enable fees, etc.).
🧾 State Variables
mapping(bytes32 => bool) public commitments;
mapping(bytes32 => bool) public nullifiers;
mapping(bytes32 => uint256) public commitmentTimestamps;
mapping(bytes32 => address) public withdrawalAuthorizations;
bytes32[] public commitmentList;
bool public feesEnabled;
commitments: Tracks whether a deposit has been made for a given commitment hash.
nullifiers: Prevents double withdrawals (each withdrawal must be unique).
commitmentTimestamps: Stores the deposit time for expiration tracking.
withdrawalAuthorizations: Maps a commitment to the address allowed to withdraw it.
commitmentList: Keeps an indexable list of all deposits (used for reclaim).
feesEnabled: Indicates whether the 1% fee is active or not.
📥 Deposit Function
function deposit(bytes32 commitment) external payable
Requires the exact deposit amount (adjusted if fees are enabled).
Verifies the commitment has not been used before.
Records the deposit and timestamp.
Emits a
Deposited
event.
💡 The commitment is a hash of a secret (nullifier + secret), generated off-chain.
✍️ authorizeWithdrawal()
function authorizeWithdrawal(
bytes32 nullifier,
bytes32 secret,
uint256 deadline,
bytes calldata signature
)
Users sign a message containing:
keccak256(nullifier, secret, msg.sender, deadline)
This prevents front-running and confirms intent.
The contract verifies the ECDSA signature using a helper library.
If valid, the sender is authorized to withdraw the corresponding commitment.
⚠️ This step is required to bind a withdrawal to a specific address.
💸 withdraw()
function withdraw(bytes32 nullifier, bytes32 secret)
Computes
commitment = keccak256(nullifier, secret)
andnullifierHash
.Validates:
The commitment exists.
The nullifier hasn't been used.
An address was authorized to withdraw it.
The caller is that authorized address.
Prevents reuse by marking:
The commitment as spent.
The nullifier as used.
The authorization as cleared.
Sends BNB to the authorized recipient.
🛡️ Withdrawals require both the secrets and a prior authorization to enhance security.
⏳ reclaimExpired() & reclaimExpiredBatch()
function reclaimExpired()
function reclaimExpiredBatch(uint256 start, uint256 end)
Allows the owner to recover funds from deposits older than 365 days.
Works by scanning the commitment list and checking timestamps.
Emits an
ExpiredReclaimed
event for transparency.Those functions only affect the deposits made 365 days ago from now, thanks to
EXPIRY_TIME
.They avoid abandoned funds and reinject those into the project funds.
🔁 The batch variant is more gas-efficient for large pools.
🧠 isExpired()
function isExpired(bytes32 commitment) external view returns (bool)
Helper view function to check if a commitment has expired and can be reclaimed.
👑 Admin Functions
function changeOwner(address newOwner)
function setFeesEnabled(bool enabled)
changeOwner
: Lets the current owner transfer control.setFeesEnabled
: Activates or deactivates the 1% deposit/withdrawal fee.
🧮 View Helpers
solidityCopierModifierfunction getCommitmentCount() external view returns (uint256)
Returns the total number of unique commitments ever submitted.
🛑 Safety Mechanisms
receive() external payable { revert(); }
fallback() external { revert(); }
Rejects direct BNB transfers and undefined function calls to prevent misuse.
🔐 ECDSA Library
This contract includes a minimal internal version of the ECDSA signature library. It provides:
toEthSignedMessageHash
: Adds the standard Ethereum prefix to prevent replay attacks.recover
: Extracts the signer’s address from a signed message.
Summary of Workflow
1️⃣ Deposit
User deposits exactly 0.1 BNB with a commitment hash.
2️⃣ Note storage
Off-chain, user stores the note (nullifier + secret).
3️⃣ Authorize
User signs the note with a deadline and calls authorizeWithdrawal()
.
4️⃣ Withdraw
User (or authorized relayer) calls withdraw()
to redeem funds.
5️⃣ Optional
If funds aren't claimed after 365 days, owner can reclaim them.
This smart contract prioritizes simplicity, auditability, and privacy-by-design. All note logic (nullifier and secret generation) happens client-side, meaning the smart contract itself never holds user metadata.
Last updated