Deposit
A deposit moves public funds into the pool and creates a note, the private value you spend from then on. It is also where the protocol's compliance screening happens: the ASP reviews every deposit before the resulting note becomes spendable, so everything circulating inside the pool has passed that review.
How a deposit becomes spendable
The deposit moves through a few stages before the note is spendable:
- Funds enter the pool. You send ETH or an ERC20 token to the Entrypoint, which forwards it to the PoolVault. The vault inserts the new note's commitment into its state tree and emits two events:
Deposited(the public deposit details) andNote(the encrypted note payload). Your note now exists inPENDINGstate, and because the deposit transaction is public, your wallet is still visibly linked to it. - The ASP opening is registered. In the same transaction, the Entrypoint registers the deposit's encrypted ASP opening (
aspCiphertext) with the ASPRegistry, which emits aLabelRegisteredevent carrying that ciphertext. - The ASP screens the deposit. The ASP service consumes the
LabelRegisteredevent, decrypts the opening with its own key, then recomputes and screens the deposit's label, the identifying value that every note descended from it will carry. - Attestation makes the note spendable. When the deposit is approved, the ASP includes the label in its association-set Merkle root, and an account holding the
POSTMAN_ROLEpublishes that root to the ASPRegistry contract. That published approval is the deposit's attestation. Spending proofs are checked against the latest published root, so your note turnsACTIVEonly once its label is in it. Approval is often complete within an hour, though it can take up to 7 days. You can check a specific deposit's status while you wait.
A deposit the ASP refuses moves to REJECTED instead, and the note's owner can still recover the funds through ragequit, a public exit that works without the ASP's cooperation.
Register before you spend
The deposit path never touches the keystore, so a wallet that has not registered can still deposit successfully. Spending and ragequit both prove keystore membership, though, which means a note owned by an unregistered wallet can be created but neither spent nor recovered until the owner registers. For that reason, register before you spend rather than before you deposit. Depositing first is fine, but the note stays unspendable until registration, so frontends typically handle registration as part of account setup.
How to deposit
- Native ETH
- ERC20
const NATIVE_ETH = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const receipt = await session.deposit({
tokenId: NATIVE_ETH,
value: "0x2386f26fc10000", // 0.01 ETH in wei
});
await session.discoverNotes();
Native ETH uses the placeholder asset address 0xeeee…eeee. The SDK's AddressSchema validates the hex format only, and the deposit path compares the asset case-insensitively, so either casing is accepted.
const USDC_SEPOLIA = "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238";
// session.deposit() checks your ERC20 allowance and, when it is too low,
// submits an approve for the deposit value plus the vetting fee before
// sending the deposit transaction itself.
await session.deposit({
tokenId: USDC_SEPOLIA,
value: "0x4c4b40", // 5 USDC (6 decimals)
});
await session.discoverNotes();
The ERC20-specific constraints are:
- The token must be whitelisted on the
Entrypoint(Sepolia USDC and USDT are pre-configured). valueuses the token's smallest unit.- The
tokenIdis the ERC-20 contract address; it may be checksummed or lowercase, so use consistent casing when filtering local notes.
When your current allowance is too low, the deposit takes two transactions: the SDK first submits an ERC20.approve granting the Entrypoint the deposit value plus the vetting fee, then the deposit itself. With a sufficient allowance already in place it sends only one.
deposit() returns a transaction receipt, not note material. For an owned deposit, the SDK persists the note locally, where it appears in exportAccount().notes as PENDING right away. After ASP attestation, discoverNotes() promotes it to ACTIVE. Use prepareDeposit(...) first if you need the pending note before submitting.
Constraints
- Min amount, vetting fee, and max relay fee are set per asset on the
Entrypoint. Read them live withEntrypoint.assets(asset), which returnsAssetConfig{ enabled, minAmount, vettingFeeBPS, maxRelayFee }, rather than assuming fixed values. There is no max deposit amount. - Pre-attestation visibility: until the ASP attests the deposit label, the note exists on-chain in
PENDINGstate and the depositor's wallet is linked to it. Privacy is realized only after attestation and a subsequent spend.
Check a deposit's attestation status
Each note carries its deposit's label, and the ASP answers status queries per label:
const labelHash = hashService.hash([note.label]);
const decimalLabelHash = BigInt(labelHash).toString();
const attestation = await fetch(
`${aspUrl}/labels/${decimalLabelHash}/status`,
).then(r => r.json());
switch (attestation.status) {
case "approved":
console.log("Note is spendable. ACTIVE.");
break;
case "pending":
console.log("ASP still reviewing. Wait or check back later.");
break;
case "rejected":
console.log("ASP refused this deposit. Use ragequit to recover.");
break;
case "unknown":
console.log("ASP has no record of this label. Keep waiting or re-check.");
break;
}
A rejection is a different state from a pending review: if the ASP explicitly rejects a label (a sanctions hit or compliance flag), the note moves to REJECTED rather than back to PENDING, and ragequit is its recovery path.
Most apps don't call this directly. discoverNotes() integrates the attestation check into its sync, so your local NoteManager status updates from PENDING to ACTIVE automatically when the ASP catches up. Use this query when you need to check one specific deposit. The ASP indexes the Poseidon hash of the label, encoded as a decimal integer (GET {aspUrl}/labels/{decimalLabelHash}/status).
Behind the scenes
session.deposit({tokenId, value}) (same signature for ETH and ERC20)Entrypoint.deposit(...), which calls PoolVault.deposit(...) and then registers the encrypted ASP opening via aspRegistry().registerLabel(aspCiphertext)Entrypoint allowance covering the deposit value plus the vetting fee. session.deposit() submits the approve automatically when the current allowance is too low.deposit Groth16PoolVault emits Deposited(outputCommitment, asset, depositedValue, caller) plus a Note(hint, data) carrying the encrypted payload (the Note event itself does not include the commitment). Entrypoint additionally emits its own Deposited(asset, depositor, value, fee) capturing the depositor and vetting fee, and ASPRegistry emits LabelRegistered(ciphertext) carrying the encrypted opening the ASP decrypts to screen the label.Variants
- Yield deposit: shield the value as ppUSDC shares that earn Aave interest.
- Swap and deposit: enter from any wallet token, with the swap and the deposit in one transaction.
What's next
- Manage notes: discovery promotes the attested note into your spendable set.
- Transfer: spend the note privately inside the pool.
- Withdraw: exit the value to a public address.
- Ragequit: the recovery path if the ASP rejects the deposit.