---
id: TIP-1028
title: Address-Level Receive Policies
description: Extends TIP-403 with address-level receive policies, token filters, and receipt-based recovery for blocked TIP-20 transfers and mints.
authors: Mallesh Pai, 0xrusowsky, 0xKitsune
status: Approved
related: TIP-403, TIP-1015, TIP-20, TIP-1016, TIP-1022
protocolVersion: T6
---

# TIP-1028: Address-Level Receive Policies

<br>

## Abstract

TIP-1028 extends TIP-403 with address-level receive policies, letting a receiver control which TIP-20 tokens they accept and who can send those tokens to them.

When a user's receive policy blocks an inbound TIP-20 transfer, delivery is redirected to `ReceivePolicyGuard`, a new protocol precompile introduced by this TIP, and a receipt corresponding to that specific transfer is recorded.

The user's receive policy also specifies the recovery authority able to claim a blocked transfer: either the originator of the funds, or a nonzero recovery address chosen by the receiver. That recovery address can be the receiver itself or another address. `ReceivePolicyGuard` acts as a protocol-level extension of the recovery authority's address space for this limited purpose: although the blocked amount is recorded at `RECEIVE_POLICY_GUARD_ADDRESS` until claimed, the recovery authority controls the claim path by submitting a valid receipt, subject to the claim-time checks below.

The token's TIP-403 policy checks continue to revert on failure.

<br>

## Motivation

TIP-403 allows token issuers to control who may use a token, but it does not give receivers control over which transfers or mints they accept. However, receivers may wish to restrict incoming funds based on the sender or the token. For example:

- A regulated entity might wish to receive incoming transfers only from addresses held by individuals they have KYC'ed,
- Orchestrators and exchanges may wish to receive only specific tokens at their deposit addresses to prevent unrecoverable funds.

If a receiver rejects transfers, sending them tokens will fail. This can cause swaps, payouts, and other operations to revert, even though the sender and token are valid.

TIP-1028 lets receivers filter incoming transfers without causing those transfers to fail. If a receiver blocks a transfer or mint, the call still succeeds and delivery is redirected to `ReceivePolicyGuard`.

The originator or the recovery address designated by the receiver can claim those funds later. Each blocked transfer record keeps enough information to identify the original transfer, so it can be handled correctly.

<br>

# Specification

TIP-1028 introduces three main changes:

- The TIP-403 precompile is extended to add per-address receive policies that define which tokens and senders are allowed to send to a given address. Receive policies are configured via newly added `setReceivePolicy(...)` and enforced via `validateReceivePolicy(...)` functions.
- A new precompile for blocked transfers, `ReceivePolicyGuard`, is introduced at `RECEIVE_POLICY_GUARD_ADDRESS`. It records a receipt for each blocked transfer or mint, which can be claimed later by the originator or by the receiver's chosen recovery address.
- TIP-20 transfer and mint flows are updated to check receive policies before crediting a receiver. The existing TIP-20 checks (pause, balance, allowance) and the token's TIP-403 policy checks still run first and continue to revert on failure. Only a receive policy failure redirects delivery to `ReceivePolicyGuard`, where a receipt is recorded.

The sequence diagram below shows the high level flow for a transfer that is blocked by a receive policy and later claimed from `ReceivePolicyGuard`.

```mermaid
sequenceDiagram
    participant Sender
    participant TIP20
    participant TIP403
    participant ReceivePolicyGuard
    participant Receiver

    Receiver->>TIP403: setReceivePolicy(...)
    Sender->>TIP20: transfer(receiver, amount)
    TIP20->>TIP403: check token's TIP-403 policy(sender, receiver)
    TIP403-->>TIP20: allowed
    TIP20->>TIP403: validateReceivePolicy(token, sender, receiver)
    TIP403-->>TIP20: blocked
    TIP20->>ReceivePolicyGuard: storeBlocked(...)
    TIP20-->>Sender: success (funds recorded as blocked)

    RecoveryAuthority->>ReceivePolicyGuard: claim(...)
    ReceivePolicyGuard->>TIP20: release from guard
    TIP20-->>TIP403: check appropriate TIP-403 policies
    TIP403-->>TIP20: allowed
    TIP20-->>Receiver: transfer funds
```

<br>

## TIP-20 Operations

TIP-1028 applies to the following TIP-20 operations: `transfer`, `transferFrom`, `transferWithMemo`, `transferFromWithMemo`, `systemTransferFrom`, `mint`, `mintWithMemo`.

For each of these operations, TIP-20 first conducts token-level checks controlled by the issuer as before and continues to revert on failure. It then also checks the receiver's policy configuration before crediting them. It verifies whether the token is allowed by the receiver's token filter and whether the sender is allowed by the receiver's policy. For `transfer`, `transferFrom`, `transferWithMemo`, `transferFromWithMemo`, and `systemTransferFrom`, the policy checks the `from` parameter as the sender. For `mint` and `mintWithMemo`, the policy checks `msg.sender` as the sender.

If both checks pass, the transfer or mint proceeds as normal. If either check fails, the call still succeeds but delivery is redirected to `ReceivePolicyGuard` instead.

Note that TIP-1028 only applies to the TIP-20 operations listed above. It does not apply to `approve`, `permit`, or `burn` and does not affect fee deposits or refunds via `transfer_fee_pre_tx` or `transfer_fee_post_tx`. Additionally, TIP-1028 does not interact with TIP-20 rewards or internal balances.

### TIP-1022 Interaction

If `to` is a TIP-1022 virtual address, it is resolved to its master address before any checks run. All receive policy checks use the master address. If resolution fails, the operation reverts as before.

If the transfer or mint is allowed, it follows normal TIP-1022 forwarding behavior. If it is blocked, the receipt is recorded for the master address while preserving the original `to` for attribution.

<br>

## Receive Policies

A receive policy defines which transfers and mints an address accepts. It controls two things:

- Which TIP-20 tokens are allowed.
- Which senders are allowed.

Receive policies are stored per address in the TIP-403 registry. Conceptually, each account has:

```text
ReceivePolicy(account) = (
    senderPolicyId,    // TIP-403 policy ref indicating which senders are allowed
    tokenFilterId,     // TIP-403 policy ref indicating which TIP-20 tokens are allowed
    recoveryAuthority   // address(0) or a claiming address
)
```

`recoveryAuthority` selects who can claim future blocked receipts: `address(0)` means the transfer originator (i.e., where the funds came from), and any nonzero address names the claiming address. If the receiver wants to claim directly, it sets `recoveryAuthority` to its own address. Since uninitialized storage defaults to zero, the originator is the default claimer when a receive policy is set without an explicit recovery authority.

If no receive policy is set, all transfers and mints are allowed.

Each address sets its receive policy using `setReceivePolicy(...)`.

When a TIP-20 transfer or mint executes, it calls `validateReceivePolicy(token, sender, receiver)` on TIP-403. This checks the token against the receiver's token filter and the sender against the receiver's policy.

If both checks pass, the transfer or mint proceeds as normal. If either check fails, delivery is redirected to `ReceivePolicyGuard` and the blocked amount is attributed to the designated recovery authority.

### Receive Policy Storage Layout

TIP-403 stores receive policy configuration per address.

```solidity
mapping(address => uint256) public addressReceiveConfig;
mapping(address => address) public addressRecoveryAuthority;
```

`addressReceiveConfig[account]` is a packed `uint256` with the following layout:

| Bits | Size | Field |
|---|---:|---|
| `0` | 1 | `hasReceivePolicy` |
| `1..64` | 64 | `senderPolicyId` |
| `65..72` | 8 | `senderPolicyType` |
| `73..136` | 64 | `tokenFilterId` |
| `137..144` | 8 | `tokenFilterType` |
| `145-152` | 8 | `recoveryMode` |
| `153..255` | 102 | reserved, MUST be zero |

When `hasReceivePolicy == 0`, the address has no receive policy and all transfers and mints are allowed. The cached type fields are valid because policy type and token filter type are immutable after creation.

`addressReceiveConfig[account]` stores a `recoveryMode` enum (`uint8`), which indicates how the recovery authority is derived: `RecoveryMode::Originator`, `RecoveryMode::Receiver`, or `RecoveryMode::ThirdParty`. When the mode is `ThirdParty`, `addressRecoveryAuthority[account]` stores the nonzero recovery authority address. For `Originator` and `Receiver`, the separate `addressRecoveryAuthority[account]` slot is unused and SHOULD be zero.

`recoveryAuthority` is stored separately because a 160-bit third-party address does not fit in the packed config slot.

An address that wants to functionally disable filtering SHOULD set `senderPolicyId = 1` and `tokenFilterId = 1`. The slot remains allocated.

Constraints on `setReceivePolicy(...)`:

- `senderPolicyId` MUST reference a simple `WHITELIST` or `BLACKLIST` TIP-403 policy, or built-in policy `0` or `1`. `COMPOUND` policies are not valid.
- `tokenFilterId` MUST reference a simple `WHITELIST` or `BLACKLIST` TIP-403 policy, or built-in policy `0` or `1`. `COMPOUND` policies are not valid.
- The caller MUST NOT be a TIP-1022 virtual address. Virtual addresses are forwarding aliases and the user should instead configure receive policies on their resolved master address.
- `recoveryAuthority` MUST be `address(0)` or a nonzero literal address. Nonzero recovery authorities MUST NOT be `RECEIVE_POLICY_GUARD_ADDRESS`, TIP-1022 virtual addresses, or system precompile addresses that cannot initiate calls.

<br>

## Sender Policies

A receive policy points at an existing TIP-403 policy through `senderPolicyId`. The sender side of receive checks reuses TIP-403 policy evaluation directly. A receiver can reuse an existing simple TIP-403 `WHITELIST` or `BLACKLIST` policy, create a new one through the existing TIP-403 interface, or use built-in policy `0` (reject all) or `1` (allow all).

`COMPOUND` policies are not valid for `senderPolicyId`. This is because a receive check only asks one question: may this sender send to this receiver. A `COMPOUND` policy splits authorization across sender, transfer-recipient, and mint-recipient roles. Allowing `COMPOUND` would conflate those roles.

<br>

## Token Filters

Token filters use the existing TIP-403 `PolicyData` and policy membership set. A receive policy references one by `tokenFilterId`, and the policy's members are interpreted as TIP-20 token addresses.

The registry does not need to validate that policy members are TIP-20 contracts. Non-token addresses in a token filter are inert configuration mistakes.

<br>

## Receive Policy Evaluation

`validateReceivePolicy(token, sender, receiver)` returns whether a transfer or mint to `receiver` is allowed and, if not, why. If the receiver has not configured a receive policy (`hasReceivePolicy == 0`), it returns `(true, NONE)`.

Otherwise, evaluation uses a fixed order and short-circuits on the first failed check:

1. Check `token` against the receiver's token filter. If this rejects, return `(false, TOKEN_FILTER)`.
2. Check `sender` against the receiver's receive policy. If this rejects, return `(false, RECEIVE_POLICY)`.
3. If both checks pass, return `(true, NONE)`.

If both the token filter and sender policy would reject, `TOKEN_FILTER` is returned because the token filter is the first canonical check.

The `BlockedReason` values used in the second return slot are:

```solidity
enum BlockedReason {
    NONE,
    TOKEN_FILTER,
    RECEIVE_POLICY
}
```

`NONE` means that the call is allowed. Blocked events MUST NOT use `NONE`.

<br>

## ReceivePolicyGuard Precompile

`ReceivePolicyGuard` tracks blocked inbound TIP-20 transfers and mints. Every blocked amount remains directly attributable and only claimable by the designated recovery authority, even though all blocked balances are recorded at the shared `RECEIVE_POLICY_GUARD_ADDRESS` until claimed. 

### ReceivePolicyGuard Address

```solidity
address constant RECEIVE_POLICY_GUARD_ADDRESS = 0xB10C000000000000000000000000000000000000;
```

The aggregate blocked balance for each TIP-20 token sits in that token's `balances[RECEIVE_POLICY_GUARD_ADDRESS]` slot.

### Restrictions on `RECEIVE_POLICY_GUARD_ADDRESS`

The following restrictions apply:

- A TIP-20 transfer or mint with `to == RECEIVE_POLICY_GUARD_ADDRESS` MUST revert with `AddressReserved()`. This applies to `transfer`, `transferFrom`, `transferWithMemo`, `transferFromWithMemo`, `systemTransferFrom`, `mint`, and `mintWithMemo`.
- A reroute claim with `to == RECEIVE_POLICY_GUARD_ADDRESS` MUST revert.
- `setReceivePolicy(...)` MUST reject `account == RECEIVE_POLICY_GUARD_ADDRESS`.
- The TIP-20 `burnBlocked` function MUST revert when `from == RECEIVE_POLICY_GUARD_ADDRESS`. Funds leave `ReceivePolicyGuard` only by claiming or burning a specific receipt. Burning the aggregate `RECEIVE_POLICY_GUARD_ADDRESS` balance directly would leave receipts pointing at money that no longer exists.

### Blocked Transfer Model

When a transfer or mint is blocked, the funds are credited to `RECEIVE_POLICY_GUARD_ADDRESS` instead of the receiver. `ReceivePolicyGuard` records one receipt per blocked transfer or mint. Each receipt captures the full context of the blocked operation, including the original sender, the requested recipient, whether it was a transfer or mint, the memo, and the reason for blocking. This gives the authorized claimer enough information to decide whether and how to claim.

The receipt is identified by a `receiptKey` derived from these fields. `ReceivePolicyGuard` stores the keyed amount per receipt, so the aggregate balance at `RECEIVE_POLICY_GUARD_ADDRESS` is always decomposable into amounts attributed to specific receipts. The ABI-encoded receipt is emitted when the receipt is created, and the claimer supplies that same receipt bytes value as a witness at claim time. This keeps onchain state minimal while letting recovery logic stay flexible.

### Blocked Transfer State

```solidity
uint8 public constant RECEIPT_VERSION = 1;
uint64 public nonce = 1;
mapping(bytes32 => uint256) internal balanceOf;
```

Each blocked transfer or mint is identified by a `receiptKey`. The encoded receipt is self-describing so claim and balance lookups bind the token, version, and recovery authority to the witness itself. v1 uses this receipt body:

```solidity
struct ClaimReceiptV1 {
    uint8 version;
    address token;
    address recoveryAuthority;
    address originator;
    address recipient;
    uint64 blockedAt;
    uint64 blockedNonce;
    BlockedReason blockedReason;
    InboundKind kind;
    bytes32 memo;
}
```

The v1 `receipt` is computed as:

```text
receiptKey = keccak256(abi.encode(receipt))
```

where:

- `version`: one-byte discriminator inside the receipt. MUST be `1` for receipts created under this TIP. Future receipt-key formats MUST use a different value and MAY define a different version-specific receipt body.
- `token`: the TIP-20 token whose balance ledger holds the blocked amount at `RECEIVE_POLICY_GUARD_ADDRESS`.
- `recoveryAuthority`: the recovery authority captured when the receipt was created. `address(0)` means originator recovery, where only `originator` may claim. Any nonzero value is the only caller authorized to claim the receipt.
- `originator`: `from` for transfers, `msg.sender` for mints.
- `recipient`: the literal `to` supplied at the TIP-20 entrypoint. For non-virtual inbounds this is the receiver itself; for TIP-1022 inbounds this is the virtual alias. The canonical owner is derived at claim time as `tip1022.resolve(recipient)`, which is deterministic.
- `blockedReason`: `TOKEN_FILTER` or `RECEIVE_POLICY`.
- `kind`: `TRANSFER` or `MINT`.
- `memo`: original memo for memo-bearing paths, `bytes32(0)` otherwise.
- `blockedAt`: block timestamp captured at receipt creation.
- `blockedNonce`: monotonically increasing global disambiguator assigned at receipt creation.

`balanceOf[receiptKey]` stores the full amount for that open receipt.

Storing one fine-grained receipt per blocked transfer or mint is more expensive than aggregating, but it preserves the literal `recipient` for TIP-1022 attribution, the original `originator`, the `blockedAt`, the transfer-vs-mint distinction, and the memo and reason data needed for programmable recovery rules. The resolved master is intentionally not stored: it is a deterministic function of `recipient` and is recomputed when needed at claim time and offchain. Persistent state stays minimal: one keyed amount per receipt. The richer fields live in the receipt witness emitted in guard events.

`ReceivePolicyGuard` does not enumerate receipts onchain. Claimers MUST supply the receipt they want to consume, typically by indexing the blocked events offchain.

### Storing Blocked Transfers and Mints

When `validateReceivePolicy(...)` returns blocked, the TIP-20 path credits `RECEIVE_POLICY_GUARD_ADDRESS` instead of the receiver, then calls `storeBlocked(...)`. `ReceivePolicyGuard` assigns the receipt's `blockedAt` and `blockedNonce`, computes `receiptKey`, sets `blockedReceiptAmount[receiptKey] = amount`, and returns the assigned `(blockedNonce, blockedAt)` to the caller. A blocked transfer emits the regular `Transfer` event naming `ReceivePolicyGuard` as the recipient, then emits `TransferBlocked(...)`; a blocked mint emits the regular `Transfer` and `Mint` events naming `ReceivePolicyGuard` as the recipient, then emits `TransferBlocked(...)`. `TransferBlocked.blockedNonce` exposes the assigned nonce for correlation without decoding, while `TransferBlocked.receipt` is the ABI-encoded receipt witness and contains the inbound `kind`, memo, blocked reason, nonce, timestamp, recipient, originator, token, and recovery authority needed to claim. Memo-bearing variants preserve the original memo inside the receipt.

`storeBlocked(...)` MUST be callable only by TIP-20 precompiles or protocol-internal system code. User callers MUST NOT be able to fabricate receipts. Without this restriction, an attacker could mint synthetic receipts by replaying or fabricating witnesses without any backing `ReceivePolicyGuard` balance.

### Claiming

A claim consumes one full receipt and releases the funds to one destination, partial claims are not supported. 

`claim(...)` takes an encoded receipt and a destination `to`. For `receipt.version == 1`, it decodes the witness as `ClaimReceiptV1`, derives the canonical receiver as `tip1022.resolve(receipt.recipient)`, recomputes `receiptKey`, requires `blockedReceiptAmount[receiptKey] > 0`, zeroes the slot to free its storage, and releases the stored amount from `receipt.token`.

Once consumed, a receipt is permanently retired: its `receiptKey` slot is empty and any later claim against the same witness MUST revert with `InvalidReceipt()`. 

The release path inside the TIP-20 token debits `balances[RECEIVE_POLICY_GUARD_ADDRESS]`, credits the destination, emits `Transfer(RECEIVE_POLICY_GUARD_ADDRESS, to, amount)` followed by `ReceiptClaimed(...)`, and treats `ReceivePolicyGuard` as a reward-exempt always-opted-out source. `RECEIVE_POLICY_GUARD_ADDRESS` is not the real TIP-403 policy subject for a claim. The claim-time checks below determine whose TIP-403 status is used.
Releasing a previously blocked mint does not emit a fresh `Mint` event because the claim is a transfer out of `ReceivePolicyGuard`, not a new mint.

The sequence diagram below shows the high level flow for a claim. The details are described below. 

```mermaid
sequenceDiagram
    participant Claimer
    participant ReceivePolicyGuard
    participant TIP20
    participant Destination

    Claimer->>ReceivePolicyGuard: claim(to, receipt)
    ReceivePolicyGuard->>ReceivePolicyGuard: check claimer and claim-time policy rules
    ReceivePolicyGuard->>ReceivePolicyGuard: recompute receiptKey, require amount > 0
    ReceivePolicyGuard->>ReceivePolicyGuard: delete blockedReceiptAmount[receiptKey]
    ReceivePolicyGuard->>TIP20: release amount from guard to `to`
    TIP20-->>Destination: transfer funds
```

#### Claim authority

The receipt only tells `ReceivePolicyGuard` which receipt to consume. It does not grant a right to claim. Blocked events are public, so anyone can build a valid witness, but only the authorized claimer may call `claim(...)`.

Each receipt is governed by the `recoveryAuthority` captured for that receipt at block time:

- if `recoveryAuthority == address(0)`, only `receipt.originator` may call `claim(...)`;
- otherwise, only `recoveryAuthority` may call `claim(...)`.

For transfer-like receipts, `originator` is `from`, including for `transferFrom` where `from` may differ from `msg.sender`. For mint receipts, `originator` is the mint caller. 

Changing `addressRecoveryAuthority[receiver]` affects future receipts only. Existing receipts remain governed by the recovery authority captured in their key. If a receiver configures originator recovery and the originator is a system or protocol address that cannot call `ReceivePolicyGuard`, the receipt may be unclaimable; this is a receiver configuration risk. 

#### Policy subject

Claim authority and TIP-403 policy status are separate. The authorized claimer is determined by `recoveryAuthority`: `address(0)` resolves to `receipt.originator`, and any nonzero value resolves to itself. The policy subject is the address whose TIP-403 sender status is checked for reroutes and receipt burns:

- for originator recovery, the policy subject is `receipt.originator`;
- for any nonzero `recoveryAuthority`, the policy subject is `receiver`.

A nonzero recovery authority acts as the receiver's delegate for this purpose. Its own TIP-403 policy status does not control whether the receipt can be rerouted or burned.

#### Resume claims

A claim resumes the original inbound when `recoveryAuthority != address(0)` and `to == receiver`. A resume:

- MUST NOT recheck the receiver's receive policy;
- bypasses AccountKeychain spending-limit metering;
- MUST still require the receiver to be currently authorized under the token's TIP-403 policy as the destination of the release.

A blocked mint, for example, has already passed the token's original TIP-403 policy check on the inbound path, but the issuer's current TIP-403 policy for the receiver still applies before release. A nonzero recovery authority may use this resume path because the receiver selected that authority.

For previously blocked TIP-1022 transfers to a virtual address, `receiver` is the resolved `master`, so resume releases directly to `master` without a forwarding leg. The original virtual target is preserved in `ReceiptClaimed.receipt.recipient` for attribution.

#### Reroute claims

All other claims are reroutes. This includes every originator-authorized claim, even if `to == receiver`.

A reroute is treated as a new movement by the policy subject. It MUST:

- reject `to == RECEIVE_POLICY_GUARD_ADDRESS`;
- resolve `to` to its master address if `to` is a TIP-1022 virtual address, or revert with `InvalidClaimAddress()` if resolution fails;
- require the policy subject to be authorized as a sender under the token's TIP-403 policy;
- require the resolved destination to be authorized as a recipient under the token's TIP-403 policy; and
- require the resolved destination's receive policy to accept the policy subject as sender.

If any of these checks fails, the claim MUST revert. The `ReceiptClaimed` event preserves the literal `to` supplied by the caller for attribution and includes the consumed receipt bytes.

If the receiver is the authorized claimer and a reroute is initiated through an access key, the claim MUST meter the claimed amount against the receiver's AccountKeychain spending limit as an ordinary TIP-20 spend by the receiver. If an originator-authorized reroute is initiated through an access key, it MUST meter against the originator. If the receiver uses a recovery authority other than itself, any equivalent delegation, timelock, multisig, or key-policy enforcement is the responsibility of that authority.

#### Burning receipts

`burnBlockedReceipt(...)` is not a claim. It consumes one full receipt and burns the stored amount from `receipt.token` at `RECEIVE_POLICY_GUARD_ADDRESS`.

This path exists because `burnBlocked(RECEIVE_POLICY_GUARD_ADDRESS, amount)` cannot safely burn the aggregate guard balance. The caller MUST hold `BURN_BLOCKED_ROLE` for `receipt.token`. A receipt is burnable only when its policy subject, as defined above, is currently unauthorized as a sender under the token's TIP-403 policy.



<br>

## Events and Errors

### Receive Policy Events

```solidity
event ReceivePolicyUpdated(
    address indexed account,
    uint64 senderPolicyId,
    uint64 tokenFilterId,
    address recoveryAuthority
);
```

### Token Filter Events

Token filters use the existing TIP-403 policy events: `PolicyCreated`, `PolicyAdminUpdated`, `WhitelistUpdated`, and `BlacklistUpdated`. TIP-1028 does not add dedicated token-filter events.

### ReceivePolicyGuard Events

```solidity
event TransferBlocked(
    address indexed token,
    address indexed from,
    address indexed receiver,
    uint64 blockedNonce,
    uint8 receiptVersion,
    uint256 amount,
    bytes receipt
);

event ReceiptClaimed(
    address indexed token,
    address indexed receiver,
    uint8 receiptVersion,
    uint64 indexed blockedNonce,
    uint64 blockedAt,
    address originator,
    address recipient,
    address recoveryAuthority,
    address caller,
    address to,
    uint256 amount
);

event ReceiptBurned(
    address indexed token,
    address indexed receiver,
    uint8 receiptVersion,
    uint64 indexed blockedNonce,
    uint64 blockedAt,
    address originator,
    address recipient,
    address recoveryAuthority,
    address caller,
    uint256 amount
);
```

### Errors

```solidity
error InvalidReceivePolicyType();
error InvalidRecoveryAuthority();
error InvalidReceipt();
error InvalidClaimAddress();
error UnauthorizedClaimer();
error AddressReserved();
```

`TransferBlocked.receipt` MUST be the ABI-encoded receipt witness for the affected receipt, including the receipt's encoded `recoveryAuthority` field. For originator recovery, this field is `address(0)`; the actual authorized caller is `originator`. The emitted `receipt` field MUST be directly usable as the `receipt` argument to `balanceOf`, `claim`, and `burnBlockedReceipt`. `TransferBlocked.blockedNonce`, `ReceiptClaimed.blockedNonce`, and `ReceiptBurned.blockedNonce` MUST equal the `blockedNonce` encoded in the corresponding receipt.

A successful claim MUST emit exactly one `ReceiptClaimed` event for the consumed receipt. A successful receipt burn MUST emit exactly one `ReceiptBurned` event for the consumed receipt.

<br>

## Interfaces

### TIP-403 Receive Policy Interface

```solidity
interface IReceivePolicies {
    function setReceivePolicy(
        uint64 senderPolicyId,
        uint64 tokenFilterId,
        address recoveryAuthority
    ) external;

    function receivePolicy(address account)
        external
        view
        returns (
            bool hasReceivePolicy,
            uint64 senderPolicyId,
            PolicyType senderPolicyType,
            uint64 tokenFilterId,
            PolicyType tokenFilterType,
            address recoveryAuthority
        );

    function validateReceivePolicy(address token, address sender, address receiver)
        external
        view
        returns (bool authorized, BlockedReason blockedReason);
}
```

Implementations SHOULD read `addressRecoveryAuthority[receiver]` only after `validateReceivePolicy(...)` returns `authorized = false`.

Token filters are managed through the existing TIP-403 policy interface. TIP-1028 does not add a dedicated token-filter interface.

### ReceivePolicyGuard Interface

```solidity
interface IReceivePolicyGuard {
    enum InboundKind {
        TRANSFER,
        MINT
    }

    struct ClaimReceiptV1 {
        uint8 version;
        address token;
        address recoveryAuthority;
        address originator;
        address recipient;
        uint64 blockedAt;
        uint64 blockedNonce;
        BlockedReason blockedReason;
        InboundKind kind;
        bytes32 memo;
    }

    function balanceOf(bytes calldata receipt) external view returns (uint256 amount);

    function claim(address to, bytes calldata receipt) external;

    function burnBlockedReceipt(bytes calldata receipt) external;

    function storeBlocked(
        address token,
        address originator,
        address receiver,
        address recipient,
        address recoveryAuthority,
        uint256 amount,
        BlockedReason blockedReason,
        InboundKind kind,
        bytes32 memo
    ) external returns (uint64 blockedNonce, uint64 blockedAt);
}
```

<br>
## Alternatives Considered

During the design of this TIP, two alternate designs were considered that would not involve a smart contract, namely:

- reverting the transaction that effects a send which is disallowed by the receiver's policy, and
- bouncing back a transfer when disallowed by the receiver's policy.

While these would be conceptually simpler, they raise several issues. For example, the standard ERC-20 (and by extension TIP-20) model is that when the sender has sufficient balance and the parties are allowed by the token issuer, a transfer succeeds. As a result, several smart contracts may break in unexpected ways from reverts when failing receive policy. Similarly a single receiver setting a receive policy may invalidate a batch transfer, e.g., a payroll run. Bouncebacks suffer from similar issues. Having the transfer succeed but as a new receipt in `ReceivePolicyGuard` rather than the address intended by the sender is thus both less disruptive and unlocks additional functionality. For example, as described above, a regulated party can individually screen, off-chain, every blocked transfer and then proceed to claim only the ones that succeed this screening.

## Invariants

- For every TIP-20 token, `balances[RECEIVE_POLICY_GUARD_ADDRESS]` equals exactly the sum of `blockedReceiptAmount[receiptKey]` over all open receipts for that token.
