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

Architecture

PloutoRevenueRouter

The address registered with Pons as creatorFeeRecipient. It pulls fees from the escrow and splits them, and it does nothing else.

Immutable wiring

solidity
IPloutoRegistry  public immutable registry;IPonsV2FeeEscrow public immutable feeEscrow;address public immutable gravityStaking;address public immutable buybackExecutor;address public immutable reserve;

All five are constructor arguments and none can be changed. There is no admin function that redirects any part of the split.

Claiming

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);}

Permissionless, and not gated on initialization — fees are never stranded in escrow waiting for a launch.

Routing

solidity
function routeUnallocatedRevenue()    public nonReentrant whenNotPaused    returns (uint256 toBuybacks, uint256 toStakers, uint256 toReserve){    if (!registry.initialized()) revert NotInitialized();    uint256 amount = unallocatedRevenue;    if (amount == 0) revert NothingToRoute();     toBuybacks = (amount * BUYBACK_BPS) / BPS;    toStakers  = (amount * STAKER_BPS) / BPS;    toReserve  = amount - toBuybacks - toStakers;   // dust falls here    if (toBuybacks + toStakers + toReserve != amount) revert SplitMismatch(amount, 0);     // effects    unallocatedRevenue = 0;    totalRevenueRouted += amount;    totalSentToBuybacks += toBuybacks;    totalSentToStakers  += toStakers;    totalSentToReserve  += toReserve;    _routes.push(RouteRecord(uint64(block.timestamp), amount, toBuybacks, toStakers, toReserve));     emit RevenueRouted(amount, toBuybacks, toStakers, toReserve, totalRevenueRouted, _routes.length - 1);     // interactions    if (toBuybacks != 0) IBuybackExecutor(buybackExecutor).fundBuyback{value: toBuybacks}();    if (toStakers  != 0) IGravityStaking(gravityStaking).notifyRewardETH{value: toStakers}();    if (toReserve  != 0) IPloutoReserve(reserve).depositRevenue{value: toReserve}();}

Strict checks-effects-interactions plus nonReentrant. A downstream contract cannot re-enter and observe a partially-written split.

Donation classification

solidity
receive() external payable {    if (msg.sender == address(feeEscrow)) return;   // claim payout, already measured    totalDonationsReceived += msg.value;    unallocatedDonations += msg.value;    emit DonationReceived(msg.sender, msg.value);}

Donations are excluded from the split entirely and can only be moved to the reserve:

solidity
function sweepDonationsToReserve() external onlyRole(KEEPER_ROLE) nonReentrant returns (uint256 amount);

A fixed destination, so even a compromised keeper cannot redirect them.

Accounting surface

solidity
totalRevenueClaimed   totalRevenueRouted   unallocatedRevenuetotalSentToBuybacks   totalSentToStakers   totalSentToReservetotalDonationsReceived  unallocatedDonationspendingInEscrow()     localBalances()      routesLength() / getRoute()

Two invariants hold at all times:

text
totalSentToBuybacks + totalSentToStakers + totalSentToReserve == totalRevenueRoutedtotalRevenueClaimed - totalRevenueRouted                      == unallocatedRevenue

What it deliberately lacks

  • No arbitrary-call function.
  • No percentage setter, even behind a timelock.
  • No withdrawal function.
  • No pause on claiming — a pause can never strand fees in escrow.