What you’ll be able to do
- Limit incoming MIP20 transfers to configured senders and a token.
- Deploy a RestrictedAccount to hold the funds governed by these rules.
- Enforce outgoing destination rules, an amount limit and a spending pause.
- Verify rejected transfers and recover credits placed in quarantine.
RestrictedAccount, a send policy contract for a MIP20 token.
At deployment, choose its controller, initial allowed sender and destination,
and maximum amount per payment. The controller submits payments, updates the
sender and destination lists, and pauses outgoing payments. Its address and
the maximum amount per payment are fixed at deployment.
Funds must be held at this contract’s address for its outgoing rules to apply.
For delegated spending from Metal’s smart account, use
mandates.
ReceivePolicy checks configured incoming-transfer rules independently of that choice.
Choose the account and rules
Verify the token and its decimals using Direct payment. Choose an account controller, an initial allowed sender, an initial allowed destination and a maximum amount in token base units. Use USDZ and test accounts you control for this procedure. The account verifies each outgoing transfer by checking that its balance falls by the requested amount and the recipient’s balance rises by the same amount. Verify that ReceivePolicy has code at0x0000000000000000000000000000000000000f4d on the target network.
Each account configures its own receive policy by calling ReceivePolicy from
its address. The contract below makes those calls through its constructor and
controller-only methods. To change its rules, the controller calls the contract,
which then calls ReceivePolicy.
The constructor accepts incoming transfers only from allowed senders and in
the configured token. It selects REVERT refusal mode and RECEIVER recovery,
so ordinary refused transfers revert and the controller can recover forced
quarantine credits on the account’s behalf. Outgoing payments check that the
destination receives the full amount, reverting the call if its receive policy
refuses or quarantines the credit.
Implement the controlled account
Use the compiler and Foundry setup from Deploy an application contract. ImplementRestrictedAccount in src/RestrictedAccount.sol. The contract holds
funds at its deployed address and sends them through send, which enforces its
destination rules, amount limit and pause.
uint8 in the ABI. REVERT = 0 makes an ordinary refused
transfer fail without crediting the account. RECEIVER = 1 makes the account
the recovery authority for forced quarantine receipts. The contract checks
that the genesis contract exists before configuring these rules.
Keep funds at the deployed contract address so outgoing transfers pass through
send. If you extend the contract, apply the same controls to any additional
spending or authorization methods. An ordinary signing account retains the
ability to spend its own balance directly, including after EIP-7702 delegation.
Verify the rules locally
Test the account with an EVM fixture that implements the documented ReceivePolicy behavior at0x0000000000000000000000000000000000000f4d and a
token that applies its screening results. Include account configuration,
sender and token filters, REVERT and QUARANTINE outcomes, forced quarantine and receipt
claims.
Inbound rules apply when funds arrive, while
pauseSending controls outgoing
calls. If you extend the administrative interface with a sender blocklist,
verify that it takes precedence over the allowlist. Refused mints and hold
captures always create quarantine receipts, regardless of the refusal mode.
Deploy and configure
Deploy with the verified token, account controller, initial sender, initial destination and maximum using the deployment guide. Check for code at the deployed address, compare constructor getters with those inputs, and verify the deployment block hash and finality. ReadpolicyOf(accountAddress),
senderAllowed(accountAddress, initialSender) and
tokenAccepted(accountAddress, tokenAddress) on ReceivePolicy. The expected
policy is (true, true, 0, 1, address(0), false). The final limited field
is false because this example sets no receive amount or frequency limits.
To administer the account, use a wallet client connected to the controller’s
signing account to call allowSender(sender, allowed),
allowDestination(destination, allowed) or pauseSending(paused). Simulate and
estimate each exact call using
Estimate and record fees, save the signed
attempt, and verify the relevant setter event and resulting state after
finality. allowSender emits SenderAllowedSet from ReceivePolicy for the
deployed RestrictedAccount. Destination and pause events come from that contract.
Fund the account with a small direct transfer
from its allowed sender. The contract supplies the payment amount for outgoing
transfers. Fund the controller’s signing account separately so it can pay the
transaction fees for administration and spending calls from its balance in the
gas token.
Send and verify a controlled payment
Store a nonzeropaymentId, destination and amount in the payment instruction.
Use the controller’s signing account through wallet.
Load accountAddress from the verified deployment record. Estimate the exact
send(paymentId, destination, amount) call to obtain gasLimit and
maxFeePerGas.
await signed.send(). Require the token’s exact
Transfer(accountAddress, destination, amount) and the account’s matching
Sent(paymentId, destination, amount). Verify successful execution, the
receipt’s block hash and finality before recording the payment. Use its
paymentId to prevent duplicate records.
Recover refusals and forced credits
A refused ordinary credit reverts withCreditRefused(reason). Record its
transaction fee and inspect reasonFor(token, sender, accountAddress, amount)
before changing the rule or retrying. When send reverts, its ID and funds
remain available, including when the destination chooses quarantine. Resolve
uncertain submissions by their saved transaction hash before retrying.
When a mint or hold capture succeeds with a refused credit, the funds go to
ReceivePolicy. Save the full quarantine receipt from Quarantined, verify its
token, originator, receiver and amount, and mark it pending review. To
accept it, the controller calls
acceptQuarantined(receipt) on the account. The account claims the funds to its
own address as the receipt’s recovery authority, bypassing its inbound rules
for that reviewed credit. Verify Claimed, the token transfer from
ReceivePolicy to the account and finality before recording the recovery. Each
receipt can be claimed once.
Use the exact receipt tuple and claim rules
when encoding the call. This contract supports recovery only for its configured
token, so direct other tokens to accounts that can receive and recover them.
Preserve access to the controller’s signing account for administration, spending and
receipt recovery. Complete an allowed payment and a refused transfer on the
target network to verify the deployed account’s behavior.