Anmu contracts
Solidity for Anmu (安幕), the CauseLaunch protocol on BNB Smart Chain. The compiler is 0.8.28. The optimizer runs 200 times. OpenZeppelin is remapped from lib/openzeppelin-contracts. Foundry is the test and deployment tool. The protocol specification is in ../docs. The public reading is https://docs.anmu.pro.
A launch is a fixed-supply token and a public campaign record. The record stores commitments for the creator, the beneficiary, and the fee receiver. Wallets and legal names are not fields on that record. Donations, trades, allocations, and proof checks are public. The protocol is a funding system. It does not promise a return.
Proof verification does not make the surrounding transactions anonymous.
Contracts
| Contract | Duty |
| --- | --- |
| CampaignFactory | Checks the creator-auth proof, the configuration digest, the current authorization root, and an unused nullifier. Registers the campaign and, in the same transaction, deploys the token and the vault and opens the curve. Does not store msg.sender |
| CampaignRegistry | Public records, the lifecycle, and the spent-nullifier map |
| CauseToken | One ERC-20. The full supply is minted once, to the vault, at 18 decimals. No owner, pause, blacklist, or further mint |
| CampaignVault | Credits donations, pays a proof-bound beneficiary claim, holds the bonding curve, and holds creator trade fees |
| PrivateFeeVault | Holds the standard-token creation fee and the platform trade share. Pays a proof-bound fee-receiver claim. There is no admin withdrawal |
| PancakeLiquidityModule | At migration, receives the remaining tokens and the real BNB, burns 95% of the LP token, and leaves 5% for a creator claim |
| BnbTradeRouter | Trades the curve while the launch is Launched. After migration, collects the BNB fee and swaps the rest on PancakeSwap |
| TokenDeployer, VaultDeployer | CREATE2 deployers. The factory is their only caller. Their creation code lives outside the factory so the factory stays under the 24,576-byte limit |
| CreatorVerifier, ClaimVerifier | Check the seven public inputs, then call the generated UltraHonk verifiers |
contracts/privacy/honk/CreatorAuthHonkVerifier.sol and contracts/privacy/honk/ClaimHonkVerifier.sol are generated by Barretenberg. The only edit after generation is the contract name. Do not regenerate them as part of an unrelated change. contracts/privacy/README.md records the generator.
Interfaces live in contracts/interfaces. The shared structs and the public-input layouts live in contracts/types/CauseLaunchTypes.sol. ConfigHash, ClaimBinding, BondingCurve, and LaunchRules are the libraries a reviewer should read before the contracts that call them.
Two launches
LaunchRules freezes the split. A caller cannot choose another allocation.
| | Campaign | Standard token |
| --- | --- | --- |
| launchKind | 1 | 2 |
| Creation fee | None | 0.005 BNB, credited by depositCreationFee |
| Donation split | 10000 / 0 / 0 beneficiary, liquidity, protocol | Donations revert |
| Trade fee | 50 bps creator, 50 bps added at migration | 70 bps plus a creator tax of 0, 100, 200, or 300, and 30 bps platform |
| Creator tax | 0 | 0%, 1%, 2%, or 3% |
Name, symbol, supply, metadata URI, commitments, basis points, launch kind, and creator tax are bound into one configuration digest. They do not change after register.
A campaign may store an invite hash instead of a beneficiary commitment. Donations can start that day. The creator's claim can withdraw them until the beneficiary locks and then publishes. After acceptance the creator's proof no longer withdraws that bucket. No administrator role can withdraw it.
Curve
BondingCurve uses a virtual quote of 4 ether and a migration quote of 16 ether. The invariant is (tokenReserve) (realQuote + 4 ether) = totalSupply 4 ether. Fees are taken outside that product. The opening market cap is 4 BNB for every supply. The market cap at migration is 100 BNB. Twenty percent of the supply remains for the PancakeSwap pool.
The buy that reaches 16 BNB of real quote is capped. Unused BNB is refunded. Migration happens in that same transaction: the remaining tokens, the real reserve, and a campaign's parked 0.5% are added to PancakeSwap V2. Ninety-five percent of the LP token is sent to 0x000000000000000000000000000000000000dEaD. Five percent stays in the vault for a creator claim with role 3.
Launched means the curve is open. Active means the curve has migrated. Closing a campaign stops new donations. A curve that has not migrated still allows sells while curveQuote is greater than zero.
The router is 0x10ED43C718714eb63d5aA57B78B54704E256024E. PancakeSwap's own 0.25% pool fee is separate from the BNB fee above. After the pair is registered, a transfer to or from that pair reverts unless BnbTradeRouter opened the trade. Wallet-to-wallet transfers stay whole.
Claims
zk/private_claim opens one commitment and binds the campaign, the recipient, the amount, and the role. ClaimBinding checks those public inputs before the verifier runs, so a substituted recipient never consumes a nullifier.
| Role | Value | Opens | Paid by |
| --- | --- | --- | --- |
| Beneficiary | 1 | Separator 6 | CampaignVault |
| Fee receiver | 2 | Separator 7 | PrivateFeeVault |
| Creator | 3 | Separator 1, the creator-auth commitment | CampaignVault |
The recipient is public in the payment transaction. The record still has no recipient field.
Creator authorization is zk/creator_auth: a creator commitment, a depth-16 Merkle membership under the current authorization root, and a nullifier bound to the configuration digest. The factory accepts the proof only when that root, that digest, and an unused nullifier all match. The authorization root on mainnet is 0x205dc578df7131fb3779050cc02073adb79457cd13660700db37a99720de4666.
CREATE2
The vault salt is bytes32(campaignId). The token salt is keccak256(abi.encode(campaignId, uint256(1))). The CREATE2 origin is the deployer contract, not the factory. The factory predicts the vault, deploys the token to that address, then deploys the vault with the same salt. A failure reverts both. The full supply is minted to the vault once.
Roles
DEFAULT_ADMIN_ROLE, VERIFIER_ROLE, and PAUSER_ROLE are held by the deployer 0x6A39c2229ba13732068ED93Bc5FA02528c2e6B42. Those three roles should move to a timelocked multisig. FACTORY_ROLE is the factory. The relayer 0xc086a238AFb5bf1DE9D56d9b83213D3Abd94ab3C has no protocol role. The factory does not allowlist the createCampaign sender.
A pause stops the action it names. It is not a withdrawal and it is not a seizure of a vault balance.
Mainnet
Broadcast on 20 October 2026, chain id 56, blocks 128647492–128647493. The deployment order and the transaction hashes are in ../docs/deployment.md. script/Deploy.s.sol deploys this set. script/deploy.ts derives the authorization root from PLATFORM_AUTH_SECRET and broadcasts the script. Keys stay in the environment.
| Contract | Address | Sourcify |
| --- | --- | --- |
| CreatorAuthHonkVerifier | 0x8b6e8F7B7cc46FEe428AA3BE5437fB4C5D2e0967 | match |
| CreatorVerifier | 0x4d8fa488a519bF293b00763C1f53b4181579135A | exact match |
| ClaimHonkVerifier | 0xd107ab3DF4a7cE6a0D2403FCe0db39885C5947d0 | match |
| ClaimVerifier | 0x513de405D23d90bB79d45365738611048B276A49 | exact match |
| CampaignRegistry | 0x26Fa379809809bCef5C12b5eBa30b59eb1f0b75A | exact match |
| PrivateFeeVault | 0xA1034F4776683e9546CE1c9E770673bD898cdcC2 | exact match |
| PancakeLiquidityModule | 0xc53550E133e3EE3Aed985ee738B5A31C7de9e853 | exact match |
| BnbTradeRouter | 0xD272996546Ba6e976C90E9643cDCC698b5BB5065 | exact match |
| CampaignFactory | 0xae6b42Da848144ceA61CE19eA838fcFf5D6Cfb32 | exact match |
| VaultDeployer | 0x3aC61Bea0c2c50A1ec20291E7439eAF0c505c37c | exact match |
| TokenDeployer | 0xFD96c6c504bd82a996Bb871465F2814439B4C6A0 | exact match |
Each launch deploys one CauseToken and one CampaignVault. Those instance addresses are on the campaign record. They were not submitted to Sourcify. BscScan's verified badge was not submitted for this deployment.
Tests
forge test
| Command | What it checks |
| --- | --- |
| forge test --match-path test/PrivacyVerifier.t.sol | The generated UltraHonk verifiers |
| forge test --match-path test/CampaignFactory.t.sol | The digest, the current authorization root, and an unused nullifier |
| forge test --match-path test/ConfigDigest.t.sol | The locked configuration digest |
| forge test --match-path test/CampaignVault.t.sol | A donation is credited entirely to the beneficiary |
| forge test --match-path test/PrivateClaim.t.sol | A claim pays the recipient and the amount bound in the proof |
| forge test --match-path test/TradingFees.t.sol | Campaign and standard trade fees, and the 0.005 BNB creation fee |
| forge test --match-path test/Liquidity.t.sol | Migration, the 95% burn, and later trade-fee BNB added to the pair |
| forge test --match-path test/BondingCurve.t.sol | The virtual reserve, the cap, and the refund |
A passing proof test means the relation holds for that witness. It does not authorize a campaign. The public fixtures are format checks. They are not live credentials.