Integrations
What aggregators, wallets, exchanges, explorers and analytics tools need to know to support NOIR and the private side of noircash.
NOIR is an ordinary ERC-20. It is a Pons V2 token (OpenZeppelin ERC20 and ERC20Burnable, 18 decimals,
1,000,000,000 supply), with no owner, no hook of its own, no transfer rules and no special balance logic. Holding,
sending, trading, listing, bridging and custody need no special handling.
The private side is a separate contract, the PrivateVault, that holders opt into. NOIR sent into the vault is
held there against encrypted notes; NOIR taken out is ordinary NOIR again. Integrators only need the vault if they
show private balances, index private activity, or count circulating supply.
Addresses are on Deployments.
Trading NOIR
NOIR trades on its Pons bonding curve until the curve has collected 4.2 ETH, then on the Pons Uniswap v4 pool (native ETH / NOIR, fee 0, tick spacing 200, the Pons meme hook). Both are standard Pons venues:
- Aggregators already route them. 0x and KyberSwap route Pons V2 tokens on Robinhood Chain, on the curve and on graduated pools. Nothing noircash-specific is needed to buy or sell NOIR.
- Fees are Pons fees. Every trade pays Pons' 1% standard fee, on the curve and on the pool alike; NOIR has no
creator tax. The creator share is paid in ETH to the noircash
FeeHarvester, which turns it into NOIR for private holders. See Fees. - Only the Pons pool counts. Only the Pons factory can initialize a pool with its hook. Other ETH/NOIR pools can exist with another fee, tick spacing or hook; they have another pool id, and noircash ignores them for trading and pricing.
Quoting and private buys
PrivateRouter.quote gives the output of a trade on whichever venue is live, Pons fees included:
function quote(bool isBuy, uint256 amountIn) external returns (uint256 amountOut);isBuy = true: ETH in, NOIR out. isBuy = false: NOIR in, ETH out. On the pool it runs the real swap and
reverts it, so it is not a view; call it with eth_call:
import { parseAbi, parseEther } from "viem";
const router = "0x…"; // PrivateRouter, see Deployments
const abi = parseAbi(["function quote(bool isBuy, uint256 amountIn) returns (uint256 amountOut)"]);
const { result: tokensOut } = await publicClient.simulateContract({
address: router,
abi,
functionName: "quote",
args: [true, parseEther("0.01")],
});PrivateRouter.buy buys straight into a private note in one transaction. An aggregator that wants to offer that
needs the buyer's note stub and ciphertext, which only the buyer's keys can make; see Notes.
Any other purchase delivers ordinary NOIR, which the holder can shield later.
Wallets and exchanges
A NOIR holder can have two balances:
| Balance | How to read it | Who can read it |
|---|---|---|
| Public | NOIR.balanceOf(address) | Anyone |
| Private (notes) | Rebuild the note tree from the vault's events and decrypt the notes with the user's viewing key | Only the owner |
A standard token integration shows the public balance correctly. Registering privacy keys does not change it:
NOIR only becomes private when the holder shields it (approve the vault, then shield), and becomes public again
when they unshield or sell privately to an address.
To show the private balance, a wallet must derive the user's privacy keys (one signature) and scan the notes. See The note tree and Your balance.
The keys that read the private balance come from a signature over a Sign-In with Ethereum message naming one domain and the vault. Do not ask users to sign another site's key-derivation message: the result reveals all their notes. See Keys.
Deposits and withdrawals of an exchange are plain NOIR transfers. An unshield or a private sale to an exchange
deposit address arrives as a normal Transfer from the vault (for an unshield) or ETH from the router (for a
sale).
Token metadata: name noircash, symbol NOIR, decimals 18.
The private side
Supply
NOIR in the vault is not circulating. It backs the notes, and only the holder of a note can take it out.
NOIR.balanceOf(vault) is the sum of:
totalBacking: NOIR behind all notes, including the seed note locked at deployment, which nobody can spend;gasReserve: NOIR collected as network fees, waiting for the harvester;pendingSaleTotal: NOIR of ERC-4337 sales validated but not yet settled (pendingSales);- NOIR sent to the vault by plain transfer, until the next
absorbadds it tototalBacking.
The share of supply that is private is totalBacking / totalSupply.
Events
All on the vault unless noted.
| Event | Use it for |
|---|---|
VaultUpdated(totalBacking, totalShares) | The share value (B + 1) / (S + 1e6) over time, and how much NOIR is private. Emitted after every vault change |
Donation(from, amount) | NOIR given to private holders: harvested creator fees and surplus gas fees from the harvester, and NOIR sent by plain transfer from the zero address (absorb) |
NoteAdded(index, commitment, ciphertext) | Tree size: the set every private balance hides among |
Shielded(from, index, amount, shares) | NOIR entering the vault: from is the user, or the router for a private purchase |
NullifierSpent(nullifier) | Private transactions: two per transaction |
Exited(nullifier, recipient, amount, shares) | NOIR leaving the vault: unshields and router sales |
SaleSettled(saleId, swapped) | ERC-4337 sales; swapped = false means the NOIR went to the recipient instead |
GasFeeCollected(amount) | Network fees paid in NOIR by private transactions |
PriceUpdated(tokensPerEthEma) | The vault's price average |
KeysRegistered(owner, opk, viewKey) | Addresses that can receive private notes |
Bought, Sold on the router | Private purchases and sales |
Harvested, ReserveSold, ReserveDonated, CallerTipped on the harvester | Fee harvests, gas deposit top-ups and reserve donations |
The app computes its headline numbers (notes, private transactions, share of supply that is private, holder yield, NOIR distributed, price, trade and holder fee rates, gas fund) from these events and a few reads.
A private transaction's public inputs are the root, two nullifiers, two new commitments, the exit shares,
extDataHash(ext) and the vault's EIP-712 domain separator in two 128-bit halves. publicInputs(t) on the vault
returns them in circuit order. See Contracts.
What not to assume
- The ERC-4337 sender is not the user. Private transactions are UserOperations whose
senderis the vault, submitted by a bundler. The EntryPoint'sUserOperationEventnames the vault for every one of them. - A transfer into the vault is not a sale or a burn. It is a shield (with
Shielded) or a donation (withDonation). A plain transfer to the vault is counted as a donation by the nextabsorb. - A transfer out of the vault is an exit. An unshield or sale (with
Exited), a settled or claimed ERC-4337 sale (withSaleSettled), or the gas reserve going to the harvester. - Holder counts miss private holders. A note does not show its owner or amount, so everything in the vault counts as one address.
- Harvester buys come from fees. Whenever someone calls
harvest, the harvester buys NOIR with the creator fees it has collected and donates it to the vault (Harvestedon the harvester,Donationon the vault).