What you’ll be able to do
- Accept or reject a payment using application rules.
- Forward an accepted payment to its configured treasury in the same transaction.
- Recover an accidental direct transfer without recording a routed payment.
route.
The router checks the sender, amount and payment ID, collects the tokens, and
forwards them to the treasury in one transaction. If either transfer fails to
deliver the full amount, the entire call reverts.
Prepare the route
Choose a sender account you control and a treasury address different from the sender’s. Verify the token address and decimals using Read metadata. Convert the payment amount and the maximum permitted amount to the token’s base units. The target network must expose the ReceivePolicy genesis contract at0x0000000000000000000000000000000000000f4d and the current MIP20 interfaces.
Check nonempty code at that address before testing these policy outcomes. Also,
the treasury’s receive policy must accept the router as the sender. The
incoming transfer names the original sender, but the forwarded transfer names
the router. Check both policies before submitting.
Implement the router
PaymentRouter collects the payment and forwards it to the configured treasury.
The application assigns a nonzero bytes32 payment ID and stores it with the
amount before asking the sender to submit. The router records completed IDs so
each ID can be processed once.
Use the Solidity compiler, Foundry project and deployment procedure from
Deploy an application contract.
true. Reverting the router call also removes the quarantine
receipt and restores the allowance, balances and payment ID. A treasury refusal
in REVERT mode propagates its error and has the same rollback effect.
This router serves one sender. recoverUnrouted returns any balance in the
configured token to that sender without increasing routedTotal or marking an
ID paid. Other people must not use this contract as a deposit account because it
keeps no depositor ledger. It cannot recover unrelated tokens. Retain the
sender’s signing access for as long as this router may hold funds.
Test and deploy
Run in-process Foundry tests before deploying. A local token fixture must model ordinary ERC-20 transfers, allowance consumption, policy refusal inREVERT mode and
successful quarantine to a separate escrow balance. It must not call recipient
code during a transfer. Test these observable outcomes:
Deploy
PaymentRouter with the verified token, sender, treasury and maximum
amount using the deployment guide. Verify its code, constructor getters and
canonical deployment finality. Save the source, ABI, compiler settings, address
and transaction hash in the deployment record. Local fixture results do not
establish that the deployed Metal route works.
Approve and route a payment
From the configured sender’s signing account, approve the router for the exact amount using Approve a spender. Save and verify that separate transaction before collecting. The router grants no token approvals of its own. LoadrouterAddress, paymentId and the base-unit amount from the saved
payment instruction. Use the sender’s signing account through wallet.
Read sender, token, treasury, maximumAmount and paid(paymentId) first
to check the deployment and reconcile an ID that already succeeded. Estimate
this exact route(paymentId, amount) call through
Estimate and record fees to obtain
gasLimit, and use the quoted price as maxFeePerGas.
await signed.send().
Verify and recover
Require successful execution and all three events from their expected contracts:- The token’s
Transfer(sender, routerAddress, amount). - The token’s
Transfer(routerAddress, treasury, amount). - The router’s
Routed(paymentId, sender, treasury, amount).
paid(paymentId) prevents replay onchain but does not replace event verification
or the application’s own record.
A reverted route leaves the ID unused and the payment amount with the sender.
Record the transaction fee and correct the failed rule before retrying the same
payment. For an uncertain submission, recover the saved hash before sending
again.
If an ordinary transfer was sent by mistake, reconcile its finalized token event
and call recoverUnrouted() from the configured sender. Verify Recovered, the
token transfer back to the sender and finality. Ensure the sender’s receive
policy accepts the router first.