Skip to main content
Receive policy lets an account choose which MIP20 tokens it accepts, from whom, and in what amounts. The receive policy contract, ReceivePolicy, stores each account’s rules at 0x0000000000000000000000000000000000000f4d. MIP20 calls it to check those rules before crediting the account. For an ordinary transfer, a refusal either reverts the transfer or sends the funds to quarantine, where ReceivePolicy holds them until an authorized account releases them. The recipient chooses the refusal mode and who can recover quarantined funds. Refused mints, MIP20 hold captures, and payouts from a token’s own balance always use quarantine.
Maximum amounts and sender limits are not on Metal Testnet yet. Its ReceivePolicy is an earlier version: setMaxAmount, setSenderLimit, setSenderFrequency, maxAmount, senderLimitOf, senderFrequencyOf, receivedFrom, and arrivalsFrom revert, the ABOVE_MAXIMUM, SENDER_OVER_LIMIT, and SENDER_TOO_FREQUENT reasons never occur, and policyOf returns five fields without limited.

Rules

Configure receive policy by calling the setters from the account whose rules you want to change. Each setter applies to msg.sender. An externally owned account (EOA) calls directly. For a smart contract, call a function that invokes the setter from that contract’s address.
The sender is the account whose balance is debited. For transferFrom and ERC-3009 authorizations, this is the account that owns the tokens. A mint’s sender is the zero address, so an account in allowlist-only mode must allow address(0) to accept mints. An unconfigured account accepts all credits and uses REVERT mode. Self-transfers are accepted.

Sender limits

Sender limits count each sender separately for each token. A zero period counts over the account’s lifetime. Otherwise, counts use fixed windows of period seconds from the Unix epoch; an 86400-second window starts at UTC midnight. A zero max sets no limit. Only accepted, nonzero credits from another account count. A screened quarantine claim counts against its destination’s limits as a credit from the originator. A claim accepted for the original receiver by a receiver or THIRD_PARTY authority is unscreened and does not count. Changing max preserves existing counts. Changing period preserves a count only if the new window starts at the same time as the counted window; otherwise, the count starts at zero.

Screening order

ReceivePolicy checks the rules in this order and refuses on the first failure:

Check before sending

Applications can check whether the stored rules would refuse a transfer before submitting it:
A nonzero result identifies the refusal under the current rules. The transfer path and refusal mode determine whether it reverts or credits quarantine. Read the configuration through policyOf(account), senderBlocked(account, sender), senderAllowed(account, sender), tokenAccepted(account, token), minAmount(account, token), maxAmount(account, token), senderLimitOf(account, token), and senderFrequencyOf(account, token). receivedFrom(account, sender, token) and arrivalsFrom(account, sender, token) return the window start and the amount or arrival count used in that window. The final limited field in policyOf indicates whether the account has enabled limit tracking.

Refusal modes

For ordinary transfers, the recipient selects one of these modes: Three paths always send refused credits to quarantine, whatever the recipient’s mode:
  • MIP20 mints.
  • MIP20 hold captures (captureHold), such as a scheduled payment’s hold.
  • Payouts a token makes from its own balance, such as a yield token’s redirected yield, which settles inside other parties’ transfers.
Contracts paying multiple recipients can check reasonFor before each transfer and handle refusals explicitly. Clearing defers a payout that reverts in REVERT mode and lets the destination withdraw it later. A payout refused in QUARANTINE mode goes to ReceivePolicy escrow.

Recovery modes

setPolicy selects the recovery authority, the account allowed to release quarantined funds. Each receipt records the authority chosen when it was created. Changes to recovery settings apply to new receipts. Set authority to a nonzero address for THIRD_PARTY and to the zero address for the other modes. Any other combination reverts with InvalidAuthority(). In ORIGINATOR mode, a nonzero sender with no code or an EIP-7702 delegation becomes the recovery authority. For a mint or a sender with other contract code, the receipt names the recipient instead.

Quarantine and receipts

When a refused credit goes to quarantine, the token emits Transfer(from, 0x…0f4d, amount) and ReceivePolicy emits:
receiptId is keccak256(abi.encode(receipt)). Save the full receipt from the event to pass to claim. open(receiptId) returns true while that receipt’s funds remain unclaimed. Transfers that name the escrow as the recipient revert with ERC20InvalidReceiver. Only a quarantine can credit it.

Claiming

The recovery authority recorded in the quarantine receipt releases the funds. An EOA submits directly. A contract authority must expose an entry point that calls claim from that contract:
The recipient can also call claim when the receipt names its originator as authority and that originator now has contract code other than an EIP-7702 delegation. This covers a contract that sent funds during construction, before its code was deployed. Recovery uses the original receipt and hash. A successful claim emits Claimed(receiptId, to) and the token emits Transfer(0x…0f4d, to, amount). Each receipt can be claimed once.

Read and recover a quarantined credit

Use the public client, wallet client and transaction procedure from Send a direct payment. Verify the network and code at the configured ReceivePolicy address. Read the saved transaction’s successful receipt as transactionReceipt and verify its canonical block and finality. Decode ReceivePolicy’s logs with this ABI, which represents enum values as uint8:
For the expected payment, match all of receipt.token, receipt.originator, receipt.receiver and receipt.amount against the instruction. Require the event’s indexed receiver and originator to agree with its receipt, and a matching Transfer(originator, policy, amount) from the expected token in the same transaction. Together, these checks tie the policy event to the payment’s token and transfer. Select the event that matches that evidence as quarantine. Its args.receipt contains the full quarantine receipt. Recompute the ID before retaining it:
Require receiptId to equal quarantine.args.receiptId. Save the full receipt, encoding integers as decimal strings for JSON, along with the chain ID, transaction hash and log index. Record the operation as executed with funds in quarantine, retaining any consumed hold, authorization or spent-note state. Keep the payment pending until recovery establishes its outcome. Read open(receiptId) before recovery. Submit claim(receipt, destination) from receipt.authority, or the recipient under the fallback above. For an EOA, configure the wallet client with its signing account. For a contract, simulate and submit its supported recovery entry point so the contract calls ReceivePolicy itself. Choose destination from the authorized recovery instruction and apply the screening rules above. Estimate the call and save a separate signed attempt before submission. Recover an uncertain response by that saved hash. After finality, match Claimed(receiptId, destination) from ReceivePolicy and Transfer(policy, destination, receipt.amount) from the receipt’s token. Your application marks the original payment as delivered if the destination is its intended recipient. Otherwise, it records the refund or redirection. If open(receiptId) is false before your attempt, find the existing claim and verify its destination.

Send-side rules

To enforce spending limits, required approvals, mandates, or destination allowlists, hold the funds in a send policy contract. Apply the rules to every function that can spend or authorize spending:
  • Check the rule and update counters in the same call that transfers.
  • Apply the rules to ERC-20 approvals and ERC-1271 authorizations as well as direct transfers.
  • Test that a refused call leaves balances and counters unchanged.
An EOA’s private key can sign a direct transfer or ERC-3009 authorization, including after an EIP-7702 delegation. Hold the funds in a separately deployed send policy contract when spending rules must also constrain the key holder.