<!--
Sitemap:
- [Welcome to Kakushi 隠し](/index): Kakushi is a private execution layer for institutions building on stablecoin rails.
- [What is Kakushi](/introduction/what-is-kakushi): Money is private.
- [Using Kakushi with AI](/introduction/using-kakushi-with-ai): Every page in these docs is available as plain markdown for use with language models.
- [Quickstart](/quickstart): This is the ten minute path.
- [Zones and the trust model](/concepts/zones-and-trust): A zone is a private blockchain only you can see inside, with a public chain's proofs underneath.
- [The portal](/concepts/the-portal): Money walks in as public USDC and becomes private the moment it crosses.
- [Accounts and custody](/concepts/accounts-and-custody): Your existing wallet address is already a Kakushi account. Nothing to deploy, nothing to enroll.
- [Bring your own address](/concepts/bring-your-own-address): A zone account is just an address, the one an auth key recovers to, with nothing deployed.
- [Virtual addresses](/concepts/virtual-addresses): Hand every customer their own address, the way a bank hands out virtual account numbers.
- [Private transfers](/concepts/private-transfers): Pay fifty thousand people and publish zero salaries.
- [Settlement and composability](/concepts/settlement-and-composability): Two institutions settle in real time without sharing a ledger or seeing each other's books.
- [Private deposits and withdrawals](/concepts/private-deposits-and-withdrawals): Soon, even the edges go dark.
- [Proofs and the verifier](/concepts/proofs-and-the-verifier): You cannot fake a balance, and you can prove you are solvent without showing a single customer.
- [Fees and gas](/concepts/fees-and-gas): Your users never hold ETH and never see gas. They pay in the dollars they are already sending.
- [Privacy and disclosure](/concepts/privacy-and-disclosure): The public sees nothing, you see everything, the regulator sees what the law entitles them to.
- [Zone policies](/concepts/zone-policies): Compliance is built into the zone. You configure your program; the substrate enforces it on every transfer.
- [Onboard a customer](/guides/onboard-a-customer): Two calls and a webhook, and your customer has a private on-chain account.
- [Accept deposits](/guides/accept-deposits): Anyone with funds on the host chain can pay into your zone, and it lands as a private balance for your user.
- [Make private transfers](/guides/private-transfers): One call moves money privately. One call pays a whole payroll.
- [Process withdrawals](/guides/withdrawals): Burn inside, release canonical USDC outside, in one call.
- [Settle between zones](/guides/settle-between-zones): Send money to another institution's zone like it is a withdrawal. It is interbank settlement.
- [Handle webhooks](/guides/webhooks): React to money moving. Do not poll for it.
- [Reconcile](/guides/reconcile): Two numbers that must always match. Assert it on a schedule.
- [Non-custodial integration](/guides/non-custodial-integration): The user signs on their device. You never hold the key, and you still cannot move their money.
- [Compliance and disclosure](/guides/compliance): You hold the whole record. Disclosure is an export, not a negotiation with the protocol.
- [SDK reference](/sdk-reference): The look-it-up layer.
- [Connect and EVM reference](/connect-and-evm-reference): The chain-level integration detail.
- [Run a zone](/run-a-zone): Most operators never run any infrastructure.
- [Protocol spec](/protocol-spec): The deep technical layer, for auditors and engineers integrating below the SDK.
- [Resources](/resources): See Key concepts for the working vocabulary, expanded throughout Core concepts.
-->

# Accept deposits

This is one of the most powerful things a zone does, and it is worth seeing plainly. Anyone holding USDC on Base can send it to a virtual address, and it lands as a private balance inside your zone, credited to your user. The payer does nothing special: no Kakushi account, no wallet change, no bridge screen. They make an ordinary transfer on the public chain, and it arrives inside the zone, private. The same path lets a user bring their own Base funds into the zone to make them private.

![USDC on Base sent to a virtual address, arriving as a private balance inside the zone](/images/guides/accept-deposits.svg)

## How a deposit crosses the boundary

A deposit is a one-way bridge from the public chain into your private zone. Three steps, and your code only touches the last one:

1. The payer sends USDC to the virtual address from any wallet on the host chain, exactly like any other transfer.
2. The portal locks the canonical USDC on the host chain and the zone mints a balance backed one to one to your user inside the zone.
3. From that moment the balance is private. It is visible to you and the account holder, and invisible to the public chain it came from.

The payer can be a customer, an employer, an exchange, or the user themselves moving their own funds in. None of them need to know the zone exists. They see a normal address and make a normal payment.

## Issue a virtual address

Give out a fresh virtual address per payer or invoice. The deposit that lands on it is then self-attributing, and distinct addresses keep the public chain from linking all of a customer's deposits together.

```ts
import { privateKeyToAccount } from "viem/accounts";

// acme's account; the key lives in your secret store, the SDK never sees it
const acmeAccount = privateKeyToAccount(process.env.ACME_PRIVATE_KEY as `0x${string}`);

const { virtualAddress } = await kakushi.accounts.createVirtualAddress({
  account: acmeAccount,
  metadata: { reference: "invoice_4471", dueDate: "2026-07-01" },
});
// hand `virtualAddress` to whoever is paying; they send to it from any host-chain wallet
```

## React when funds land

You get two events: `deposit.received` when the payment is seen on the host chain, then `deposit.credited` when it is minted into the zone account as a private balance. Both carry the `virtualAddress` and the `metadata` you attached at claim time (the node stores it against the account), so you match the payment to the invoice with no lookup table.

```ts
if (event.type === "deposit.credited") {
  markInvoicePaid(event.metadata.reference, event.amount); // now a private in-zone balance
}
```

Need the funder for compliance? Pull attribution directly:

```ts
const attribution = await kakushi.deposits.getAttribution(event.depositId);
// funder address, amount, host-chain tx hash, block, time
```

:::tip
The account materializes on first deposit, so you can hand out a virtual address before the recipient has ever touched the zone. You can fund a user who does not yet "exist," and the sender never needs to know whether they do.
:::

:::note
This is the deposit side of the [portal](/concepts/the-portal): canonical assets are locked on the host chain and a one-to-one backed balance is minted inside the zone. To send that balance back out to the host chain, see [Process withdrawals](/guides/withdrawals).
:::
