> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive policies

> Control which tokens and senders a Privy wallet accepts on Tempo, and claim payments that Tempo holds.

A Tempo [receive policy](https://tempo.xyz/developers/docs/accounts/policies/receive-policies) controls which stablecoins a wallet accepts and who can pay it. Tempo doesn't revert a payment that breaks the policy. The transfer succeeds, but Tempo holds the funds in the `ReceivePolicyGuard` contract instead of crediting the wallet. Only the wallet's recovery authority can release or return them.

Apps use receive policies to keep address poisoning, sanction dusting, and spam tokens out of user wallets.

## How receive policies work

Each wallet's receive policy has three parts.

| Part | Decides | Values |
| - | - | - |
| Token filter | Which stablecoins the wallet accepts. | `1` (allow all), `0` (reject all), or a list of token addresses. |
| Sender policy | Who can pay the wallet. | `1` (allow all), `0` (reject all), or an allowlist or denylist of senders. |
| Recovery authority | Who can release or return held payments. | Any address. `address(0)` lets each payment's sender reclaim it. |

Lists live in Tempo's TIP-403 registry, and each list has one admin. Many wallets can share a list, so one update applies to every wallet that uses it.

## Choose a setup

| Goal | Token filter | Sender policy |
| - | - | - |
| Block spam tokens. | Supported stablecoins | `1` (allow all). |
| Stop poisoning and sanction dusting (recommended). | Supported stablecoins | Allowlist of trusted senders, such as treasury wallets, on-ramps, exchanges, and bridges. |
| Block known bad actors. | Supported stablecoins | Denylist. |
| Freeze a flagged wallet. | `0` (reject all) | `0` (reject all). |

Attackers create new addresses for free, so only an allowlist stops address poisoning.

## Prerequisites

* An operations wallet: a Privy wallet your app controls. It administers the shared lists and acts as recovery authority.
* [Gas sponsorship](https://docs.privy.io/wallets/gas-and-asset-management/gas/setup.md) turned on. To pay fees from the wallet instead, drop `sponsor` and set `fee_token`.
* `viem` 2.57 or later, which exports the TIP-403 helpers from `viem/tempo`.

Each step builds a call with a `viem/tempo` helper and sends it from a Privy wallet as a Tempo transaction (type `118`). The examples use Tempo mainnet (`eip155:4217`). For Tempo Moderato (testnet), use `eip155:42431`.

## 1. Create the shared lists

Create each list once, from the operations wallet. The operations wallet becomes the list admin, so only it can edit the list.

Start with the token list: the stablecoins the wallets accept. viem names an allowlist `'whitelist'` and a denylist `'blacklist'`, after the TIP-403 policy types.

```typescript {skip-check} theme={"system"}
import {PrivyClient} from '@privy-io/node';
import {createPublicClient, http, type Hex} from 'viem';
import {tempo} from 'viem/chains';
import {Actions} from 'viem/tempo';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID,
  appSecret: process.env.PRIVY_APP_SECRET
});
const publicClient = createPublicClient({chain: tempo, transport: http()});

const opsWalletId = 'insert-ops-wallet-id';
const opsWalletAddress = 'insert-ops-wallet-address';
const USDC_E = '0x20c000000000000000000000b9537d11c60e8b50';

const {to, data} = Actions.policy.create.call({
  admin: opsWalletAddress,
  type: 'whitelist',
  addresses: [USDC_E]
});

const {hash} = await privy
  .wallets()
  .ethereum()
  .sendTransaction(opsWalletId, {
    caip2: 'eip155:4217',
    params: {transaction: {type: 118, calls: [{to, data}]}},
    sponsor: true
  });

const receipt = await publicClient.waitForTransactionReceipt({hash: hash as Hex});
const tokenPolicyId = Actions.policy.create.extractEvent(receipt.logs).args.policyId;
```

Then create the sender list the same way, with the addresses allowed to pay user wallets. Read `senderPolicyId` from its `PolicyCreated` event.

```typescript {skip-check} theme={"system"}
const {to, data} = Actions.policy.create.call({
  admin: opsWalletAddress,
  type: 'whitelist', // or 'blacklist' for a denylist of specific senders
  addresses: [treasuryAddress, onrampAddress, exchangeAddress, bridgeAddress]
});
```

To accept any sender, skip the sender list and use policy `1`.

## 2. Set the policy on each wallet

Send one transaction from each user wallet, ideally right after creating it. This example points the wallet at both shared lists and makes the operations wallet its recovery authority.

```typescript {skip-check} theme={"system"}
const {to, data} = Actions.receivePolicy.set.call({
  senderPolicyId,
  tokenFilterId: tokenPolicyId,
  recoveryAuthority: opsWalletAddress
});

await privy
  .wallets()
  .ethereum()
  .sendTransaction(userWalletId, {
    caip2: 'eip155:4217',
    params: {transaction: {type: 118, calls: [{to, data}]}},
    sponsor: true
  });
```

If a user owns the wallet, pass their JWT in `authorization_context`, as for any other request from that wallet. If the wallet's [Privy policies](https://docs.privy.io/controls/policies/overview.md) restrict which contracts the wallet can call, allow calls to the TIP-403 registry (`Addresses.tip403Registry` in `viem/tempo`).

> [!NOTE]
>
> Virtual addresses use their master address's receive policy and can't set their own.

## 3. Update the sender list

Add or remove a sender from the operations wallet. Every wallet that uses the list picks up the change at once.

```typescript {skip-check} theme={"system"}
const {to, data} = Actions.policy.modifyWhitelist.call({
  policyId: senderPolicyId,
  address: newSenderAddress,
  allowed: true // false removes the sender
});
```

Send the call from `opsWalletId` as in step 1. For a denylist, use `Actions.policy.modifyBlacklist.call` with `restricted` instead of `allowed`.

## 4. Review blocked payments

Each blocked payment emits `TransferBlocked` from `ReceivePolicyGuard`. Blocked payments can't be listed onchain, so your app must index these events. The `claimReceipt` bytes identify the payment.

```typescript {skip-check} theme={"system"}
Actions.receivePolicy.watchBlocked(publicClient, {
  args: {receiver: userWalletAddress},
  onBlocked: ({token, amount, claimReceipt}) => {
    // Store the payment for review
  }
});
```

To release a payment, claim it from the operations wallet. Set `to` to the user's wallet to credit it, or to another address to return it.

```typescript {skip-check} theme={"system"}
const {to, data} = Actions.receivePolicy.claim.call({
  to: userWalletAddress,
  receipt: claimReceipt
});
```

Send the call from `opsWalletId` as in step 1. Leave sanction dust held for compliance review.

## Things to know

> [!WARNING]
>
> A blocked payment still emits a `Transfer` event, with `ReceivePolicyGuard` as the recipient.
> Credit a deposit only when the confirmed `Transfer` event names the wallet itself.

* **Test on Moderato first.** `Actions.receivePolicy.validate(publicClient, {token, sender, receiver})` shows whether the policy accepts a payment, and why not.
* **A claim releases the full amount.** To check a payment's amount first, call `Actions.receivePolicy.getBlockedBalance(publicClient, {receipt})`.
* **A claim to the receiving wallet skips the receive policy.** Only the token's own policy applies, so claim only approved payments.
* **A new recovery authority applies only to later payments.** Payments already blocked keep their original authority.

## Related resources

> [card] [Using Tempo with Privy](https://docs.privy.io/recipes/tempo/send-transactions.md)
>
> Create wallets and send transactions on Tempo

> [card] [Tempo policy examples](https://docs.privy.io/controls/policies/example-policies/tempo.md)
>
> Control fee tokens, sponsorship, and validity windows with Privy policies

> [card] [Tempo receive policies](https://tempo.xyz/developers/docs/accounts/policies/receive-policies)
>
> Tempo's reference for receive policies and the TIP-403 registry

> [card] [Balance webhooks](https://docs.privy.io/wallets/gas-and-asset-management/assets/balance-event-webhooks.md)
>
> Subscribe to deposit and withdrawal events
