<!-- tempo-docs-context -->
> Source: [/docs/accounts/custodians/omnibus-accounts](https://tempo.xyz/developers/docs/accounts/custodians/omnibus-accounts.md)
> Section: [Accounts](https://tempo.xyz/developers/docs/accounts.md). Create accounts, set up signing methods, policies, access keys, key rotation, and fees, and follow guides for users, automations, agents, and teams.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

# Hold Customer Funds in an Omnibus Account

Take deposits for every customer into one account, and still know who paid.

## Overview

An omnibus account holds many customers' stablecoins in one account, such as an exchange's or a payments processor's. Each customer's balance lives in your own ledger.

On Tempo, you give each customer a [virtual address](https://tempo.xyz/developers/docs/accounts/virtual-addresses). It's a deposit address that pays straight into your account, with no per-customer wallet and no sweep job. Each deposit names the address it was sent to, so you know which customer to credit.

Virtual addresses and receive policies are live on mainnet and testnet. The code on this page runs on Moderato testnet.

## Setup

::::steps
<span id="choose-the-account" />

<span id="choose-the-account-that-receives-deposits" />

### Choose the Receiving Account

Registering an account binds every deposit address you derive from it to that account permanently. Pick one whose keys you can change without changing its address.

* **Single-Key Account:** On mainnet and testnet, its deposit addresses work only while you keep that one key. If you expect to rotate keys, register a multisig or upgradeable contract instead.
* **Configurable Account:** [Add](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#add-a-signer) and [remove signers](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#remove-a-signer) at the same address. Until T14, it runs only on the [Tempo Accounts devnet](https://tempo.xyz/developers/docs/accounts/create#connect-to-the-devnet).

<span id="register-and-derive" />

<span id="register-the-account-and-derive-deposit-addresses" />

### Register and Derive Addresses

Register the account once. From it, call `registerVirtualMaster` on the virtual-address registry, with a salt you mine offchain with `VirtualMaster.mineSaltAsync`. Registration returns a 4-byte master ID. Register more than once to get separate ranges of addresses, such as one per business line.

Then derive each customer's address offchain from the master ID and a 6-byte customer tag you choose. Deriving an address sends no transaction.

```ts [virtualAddress.ts]
import { VirtualAddress } from 'viem/tempo'

const masterId = process.env.TEMPO_MASTER_ID
if (!masterId || !/^0x[0-9a-fA-F]{8}$/.test(masterId)) {
  throw new Error('Set TEMPO_MASTER_ID to your registered 4-byte master ID')
}

const virtualAddress = VirtualAddress.from({ // [!code hl]
  masterId: masterId as `0x${string}`, // [!code hl]
  userTag: '0x000000000001', // [!code hl]
}) // [!code hl]
```

Keep the mapping from customer tag to customer in your own records. To try registration first, use the [live deposit address demo](https://tempo.xyz/developers/docs/guide/payments/virtual-addresses).

<span id="set-a-receive-policy" />

<span id="accept-only-the-stablecoins-you-list" />

### Accept Listed Stablecoins Only

Set a [receive policy](https://tempo.xyz/developers/docs/accounts/policies/receive-policies) on the registered account. Tempo checks deposits to its virtual addresses against the account's policy, because a virtual address can't set its own. This policy accepts one stablecoin from any sender. With `claimer: 'self'`, your account claims anything the policy holds back.

:::code-group
```ts [receive-policy.ts]
import { client } from './client'

// The stablecoins you list, by token contract address
const { policyId: tokenPolicyId } = await client.policy.createSync({
  account,
  type: 'whitelist', // [!code hl]
  addresses: [stablecoin], // [!code hl]
})

await client.receivePolicy.setSync({
  account,
  senderPolicyId: 'allow-all', // [!code hl]
  tokenPolicyId, // [!code hl]
  claimer: 'self', // [!code hl]
})
```

```ts [client.ts]
import { tempoModerato } from 'viem/chains'
import { createClient } from 'viem/tempo'

export const client = createClient({ chain: tempoModerato })
```
:::

`account` is the registered account and `stablecoin` is the token contract address you accept. Only TIP-20 stablecoins are forwarded to your account. Other tokens sent to a virtual address can't be recovered. Tell customers to send only the stablecoins you list on Tempo.

<span id="credit-each-deposit" />

### Credit Each Deposit Once

A deposit to a virtual address emits [two `Transfer` events](https://tempo.xyz/developers/docs/protocol/tip20/virtual-addresses) in one transaction. The first goes from the sender to the virtual address. The second goes from the virtual address to your account. Treat the pair as one deposit to your account, not two. Then:

* **Read the Customer Tag:** The virtual address's last 6 bytes are the tag you chose. Look up the customer in your records.
* **Check the Recipient:** A deposit your receive policy blocks still succeeds and still emits `Transfer`. Its recipient is `ReceivePolicyGuard`, not your account. Credit only when the confirmed event names your account.
* **Record It Once:** Store the network, transaction hash, and log index as a unique key. Credit the customer in the same database transaction.

Read live deposits with [webhooks](https://tempo.xyz/developers/docs/api/webhooks) or [transfer events](https://tempo.xyz/developers/docs/guide/payments/accept-a-payment#detect-payments), and history with the [transfers API](https://tempo.xyz/developers/docs/api/transfers).

### Handle Held Payments

A deposit in a stablecoin you don't list stays in `ReceivePolicyGuard` until your account claims it. Index the guard's `TransferBlocked` events for your account to find held deposits. Decide whether to return or accept each one, then [claim it](https://tempo.xyz/developers/docs/accounts/policies/receive-policies#claim-blocked-receipts) with `client.receivePolicy.claimSync`. A claim releases the full amount.

To let senders recover their own held payments instead, set the recovery authority to the sender (`claimer: 'sender'`).
::::

## Omnibus Account Limits

* A virtual address never holds a balance. `balanceOf` on it returns `0`. Show customers the balance from your ledger.
* Some protocols record ownership against the literal address they're given. Don't send LP shares, receipts, or rewards to a virtual address unless that protocol supports it.
* The customer tag tells you whose address received a deposit, not who sent it.

## Related

[Virtual Addresses](https://tempo.xyz/developers/docs/accounts/virtual-addresses): Covers deposit addresses that pay straight into one account.

[Receive Policies](https://tempo.xyz/developers/docs/accounts/policies/receive-policies): Covers which stablecoins and senders an account accepts, and claiming held payments.

[Customer Deposit Addresses](https://tempo.xyz/developers/docs/guide/payments/virtual-addresses): Use for trying registration in a live deposit address demo.

[Virtual Addresses for TIP-20 Deposits](https://tempo.xyz/developers/docs/protocol/tip20/virtual-addresses): Covers the two Transfer events a deposit emits.
