The Fee Escrow
The Pons Fee Escrow holds swept fees on behalf of recipients until they are claimed. It is the boundary between Pons and Plouto: everything upstream is Pons's, everything downstream is Plouto's.
Address: 0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e
Verified on Blockscout
Interface
Transcribed from the verified Blockscout ABI:
interface IPonsV2FeeEscrow { event Claimed(address indexed recipient, uint256 amount); event Credited(address indexed recipient, address indexed depositor, uint256 amount); function claim() external returns (uint256 amount); function claim(uint256 amount) external returns (uint256); function claimToken(address token) external returns (uint256 amount); function claimToken(address token, uint256 amount) external returns (uint256); function balanceOf(address recipient) external view returns (uint256); function balanceOfToken(address recipient, address token) external view returns (uint256); function credit(address recipient) external payable; function creditToken(address recipient, address token, uint256 amount) external;}Plouto uses claim() (native ETH, full balance) and balanceOf(). Nothing else.
Pull, not push
The escrow never sends unprompted. A recipient must call claim() themselves, and the escrow pays the caller — msg.sender, not an argument.
That is why the Revenue Router must be the registered creatorFeeRecipient: the credit accrues to that address, and only that address can claim it.
How Plouto claims
function claimPonsFees() public nonReentrant returns (uint256 received) { uint256 balanceBefore = address(this).balance; uint256 reported = feeEscrow.claim(); received = address(this).balance - balanceBefore; if (received == 0) revert NothingClaimed(); totalRevenueClaimed += received; unallocatedRevenue += received; emit PonsFeesClaimed(reported, received, unallocatedRevenue);}Three properties worth noting:
- Permissionless
Anyone can call it. Claiming can only move ETH toward the protocol along a fixed path, so there is nothing to gate.
- Measured, not reported
reportedis emitted for observability.received— the real balance delta — drives the accounting. A test drives a deliberately over-reporting escrow and confirms only the true amount is credited.- Never gated on launch
Claiming works before the registry is initialized. Fees are never stranded in escrow waiting for a token to exist.
The double-counting trap
The escrow pays by sending ETH, which triggers the router's receive(). Without care, that ETH would be booked as a donation and as claimed revenue.
receive() external payable { if (msg.sender == address(feeEscrow)) return; // accounted by measured delta totalDonationsReceived += msg.value; unallocatedDonations += msg.value; emit DonationReceived(msg.sender, msg.value);}This was a real bug caught during development. See build status.
Verified against the real contract
A fork test credits the live escrow on forked mainnet state, calls claim() from a fresh contract, and asserts the exact amount arrives and the credit is consumed. The escrow interface in this repository is therefore known to match the deployed one.