Skip to content
Protocol contracts not yet configured for this build
Enter app

Pons V2 integration

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:

solidity
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

solidity
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

reported is 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.

solidity
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.