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.
Rules
Configure receive policy by calling the setters from the account whose rules you want to change. Each setter applies tomsg.sender. An externally owned
account (EOA) calls directly. For a smart contract, call a function that invokes
the setter from that contract’s address.
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 zeroperiod
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: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.
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 emitsTransfer(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 callsclaim 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 astransactionReceipt and
verify its canonical block and finality. Decode ReceivePolicy’s logs with this
ABI, which represents enum values as uint8:
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:
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.
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.