Skip to main content
A Tempo receive policy 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. 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

Choose by what your wallets receive. Attackers create new addresses for free, so only an allowlist stops address poisoning. An allowlist also holds payments from everyone not on it, including your other users, so use one only for wallets that receive from known sources, and keep it current as exchanges rotate their wallets. To stop all incoming payments to a flagged wallet, set its token filter and sender policy to 0n. The wallet can still send.

Prerequisites

  • An admin wallet: a Privy wallet your app controls. It administers the shared lists.
  • Optional: a recovery wallet, a Privy wallet your app controls and protects with a key quorum. You need one only if your app, not the sender, decides what happens to held payments. Keep it separate from the admin wallet, so one leaked key can’t both change who pays your users and move held funds.
  • Gas sponsorship turned on for Tempo. To pay fees from the wallet instead, drop sponsor and set fee_token in the transaction.
  • 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 its to and data from a Privy wallet as a Tempo transaction (type 118). Pass only to and data: the helpers also return fields that Privy rejects. 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 admin wallet. The admin 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.
Then create the sender list the same way, with the addresses allowed to pay user wallets. Read senderPolicyId from its PolicyCreated event.
Save both IDs in your config. To accept any sender, skip the sender list and use policy 1n.

2. Set the policy on each wallet

Send one transaction from each user wallet in the same flow that creates it. A wallet without a policy accepts every payment, and funds can arrive before its first transaction. This example points the wallet at both shared lists and sets the recovery authority to address(0), so each sender can reclaim its own held payment. This is the default: your app doesn’t hold anyone’s funds, and senders can fix a mistaken payment themselves.
To have your app review held payments and credit or return them, set recoveryAuthority to your recovery wallet’s address instead. recoveryAuthority has no default in .call(), so always set it. Then read the policy back to confirm it’s set:
get returns built-in values by name: 'allow-all' for 1n, 'reject-all' for 0n, and a claimer of 'sender' for address(0) or 'self' for the wallet itself. If a user owns the wallet, pass their JWT in authorization_context, as for any other request from that wallet.

Lock the policy

Any signer of the wallet can call setReceivePolicy again and switch back to allow-all. To stop that, attach a Privy policy that denies calls to the TIP-403 registry and allows everything else. Privy checks every call in a type 118 transaction, so the registry call can’t hide inside a batch.
A user who exports their key can still change the policy outside Privy.
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 admin wallet. Every wallet that uses the list picks up the change at once.
Send the call from adminWalletId as in step 1. For a denylist, use Actions.policy.modifyBlacklist.call with restricted instead of allowed. To see who’s on a list, call Tempo’s policy members endpoint.

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. Read them from the last block you processed, so none are missed while your service is down, and read ReceiptClaimed and ReceiptBurned the same way to mark payments released.
With address(0) as recovery authority, the sender reclaims a held payment from its own wallet, sending the funds back to itself or to another address it chooses. With a recovery wallet, your app claims it instead: set to to the user’s wallet to credit it, or to another address to return it.
Send the call from the sender’s wallet, or from recoveryWalletId, as in step 1. A claim from any other wallet reverts, and so does a second claim of the same payment. Leave sanction dust held for compliance review.

Things to know

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 (tokenFilter or receivePolicy). It works on mainnet too.
  • 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.

Using Tempo with Privy

Create wallets and send transactions on Tempo

Tempo policy examples

Control fee tokens, sponsorship, and validity windows with Privy policies

Tempo receive policies

Tempo’s reference for receive policies and the TIP-403 registry

Tempo policies API

List the members of an allowlist or denylist

Balance webhooks

Subscribe to deposit and withdrawal events