Skip to content
SECTION 07 / 15

Feeding

What feed() does step by step, the sweep outcomes, why post-graduation fees can wait for Pons, developer claims, stray tokens and severed feeders.

§The feeder

Every virus has exactly one VirusFeeder. It is an EIP-1167 clone whose immutable arguments fix, forever:

struct Config {
    address host;
    address developer;
    address treasury;
    address registry;
    address ponsFactory;
    address feeEscrow;
    address memeHook;
    address feederFactory;
}

The feeder is the Pons creatorFeeRecipient of its virus. Pons credits it, in host, with the creator part of the base fee and the whole creator tax. The feeder has no owner, no pause, no setter, and no function that moves host tokens anywhere except the burn, the treasury transfer and the developer payout.

One feeder per virus is deliberate (VIRUS_ADAPTATION A10). The Pons escrow keys balances by recipient and asset; a recipient shared by two viruses of the same host would merge their fees.

§feed()

function feed() external returns (uint256 gross);

feed() is permissionless: anyone may call it, at any time, as often as they like. It is nonReentrant, requires the feeder to be bound, and pays the caller nothing. No admin can stop it: it checks no pause flag, and the registry's recordFeed is not pausable (security invariant 11).

§Step by step

  1. Recipient check. Read ponsFactory.getLaunchedToken(virus). If Pons still routes this virus's creator fees to this feeder, continue. If not, mark the feeder severed (see below) and skip to step 3 with outcome NotRecipient.
  2. Sweep, where Pons lets the creator sweep. By Pons phase:
    • NotGraduated: try curve.sweepFees(0). Pons moves the curve's accumulated fee and tax balances into the fee escrow, credited in host to the protocol and to this feeder.
    • PoolCreated: try memeHook.sweepPoolFees(poolId, 0, 0), as the creator. If Pons refuses, the outcome is AwaitingPonsSweep.
    • Swept or Rescued: nothing to sweep. Graduation already swept the curve. The zero minimums are safe because buyback is disabled on every virus and the feeder never asks Pons to convert anything.
  3. Claim. If feeEscrow.balanceOfToken(feeder, host) > 0, try feeEscrow.claimToken(host). A failing claim is caught, so it cannot block distributing host that already reached the feeder by another path.
  4. Measure. gross = host.balanceOf(feeder) − devOwed. If that is zero or less, return 0. No split happens and no VirusFed is emitted.
  5. Split and record. burn = floor(gross × 5,000 / 10,000), dev = floor(gross × 3,000 / 10,000), treasuryAmount = gross − burn − dev. Add dev to devOwed and update totalFed, totalBurned, totalDevBooked, totalTreasury.
  6. Burn. Read host.totalSupply(), call host.burn(burn), read it again. Revert BurnNotReflected(expected, actual) unless supply fell by exactly burn.
  7. Pay the treasury. host.safeTransfer(config.treasury, treasuryAmount), to the treasury fixed in this feeder's config.
  8. Report. registry.recordFeed(gross, burn, dev, treasuryAmount, hostTotalSupplyAfter), then emit VirusFed.

§Sweep outcomes

Each feed stores the outcome of step 1–2 in lastSweep and includes it in the feeder's VirusFed event:

Value Name Meaning
0 None Nothing to sweep in this phase (Swept or Rescued).
1 Swept The Pons curve or pool sweep executed.
2 CurveSweepFailed The curve sweep reverted. Fees remain on the curve; a later feed retries.
3 AwaitingPonsSweep The graduated pool has fees that need conversion by the Pons sweep operator first.
4 NotRecipient Pons no longer routes this virus's creator fees to this feeder.

Whatever the outcome, the feed still attempts the escrow claim and distributes whatever host the feeder holds above devOwed.

§Balance-based accounting

The feeder does not track what Pons "should" have paid it. It splits whatever host it actually holds, minus what it already owes the developer (VIRUS_ADAPTATION A13):

gross = host.balanceOf(feeder) − devOwed

This makes every path for host into the feeder end in the same 50/30/20 split:

  • escrow claims after a curve or pool sweep;
  • Pons sweeping at graduation, which credits pre-graduation fees to the escrow automatically;
  • a Pons owner rescueCurveFees or rescuePoolFees, which pays the creator recipient directly and bypasses the escrow (VERIFIED_PONS F18, H5);
  • anyone sending host to the feeder. A donation is fed like any fee; it cannot be withdrawn or stranded (security invariant 15).

Because the split only ever takes balance − devOwed, neither the burn nor the treasury transfer can touch the developer's booked balance (security invariants 9 and 10). Because it measures real balances, a reentrant claim cannot be counted twice (invariant 16).

§Why AWAITING_PONS_SWEEP exists

After graduation, the Pons hook takes its fee from the swap's unspecified currency. An exact-input buy of the virus pays its fee in the virus token, not the host (VERIFIED_PONS H2). Those fees must be converted to host before the feeder can be paid in host.

Pons does not let the creator trigger that conversion. sweepPoolFees refuses the creator with InternalSwapRequiresOperator whenever any memecoin-denominated fee or buyback earmark is pending (VERIFIED_PONS H3). Only the Pons fee sweep operator (memeHook.feeSweepOperator()) can sweep then, and when it does, Pons credits the creator in the quote token, host, through the escrow (VERIFIED_PONS H4).

VIRUS treats this as a Pons limitation and does not work around it (BLOCKED_BY_PONS B4):

  • The feeder calls sweepPoolFees(poolId, 0, 0) only as the creator. When Pons refuses, the feeder records AwaitingPonsSweep and carries on with the claim and split.
  • The feeder never forces a price-sensitive conversion (security invariant 28).
  • Host-denominated pool fees (from sells) are also held back while virus-denominated fees are pending, because Pons refuses the whole creator sweep.
  • Once the operator sweeps, the host lands in the feeder's escrow balance, and the next feed() claims and distributes it.

VIRUS cannot say when the operator will sweep. The virus page shows the virus-denominated amount waiting for it.

§Events of one feed

A feed that distributes a non-zero amount emits, in order of the calls:

Emitter Event Fields
Registry, only if recipient status changed VirusRecipientStatus virus (indexed), active
Feeder, same condition RecipientStatus active
Pons curve or hook, if a sweep ran FeesSwept / PoolFeesSwept Pons-defined
Host token, if the escrow paid out Transfer(…, feeder, amount) The escrow claim
Host token Transfer(feeder, 0x0, burn) The burn
Host token Transfer(feeder, treasury, treasuryAmount) The treasury payment
Registry VirusFed virus (indexed), host (indexed), grossReceived, burned, devBooked, treasuryAmount, hostTotalSupplyAfter
Registry, if burn > 0 HostBurned host (indexed), virus (indexed), amount, hostTotalSupplyAfter
Registry, if dev > 0 DevBooked virus (indexed), developer (indexed), amount
Registry, if treasuryAmount > 0 TreasuryPaid virus (indexed), treasury (indexed), amount
Feeder VirusFed virus (indexed), host (indexed), grossReceived, burned, devBooked, treasuryAmount, hostTotalSupplyAfter, sweep

The feeder's VirusFed and the registry's VirusFed share a name but have different signatures (the feeder's has the extra sweep field), so their topic hashes differ. Escrow events are not listed, and the sender of the claim transfer is not stated, because the Pons escrow implementation is not published (UNVERIFIED T4).

§claimDev()

function claimDev() external returns (uint256 amount);

Pays the developer's entire booked balance, devOwed, in host, to the developer address fixed in the feeder's config. Anyone may call it; the recipient cannot change (security invariant 4). It reverts with NothingToClaim when devOwed is zero, records the claim in the registry (DevClaimed(virus, developer, amount)), and emits the feeder's own DevClaimed(developer, amount).

The developer share is booked, not pushed (VIRUS_ADAPTATION A14). There is no admin path to it, the treasury cannot pull it, and it cannot be redirected. Like feed(), claimDev() is not pausable (invariant 12).

§burnStrayVirus()

function burnStrayVirus() external returns (uint256 amount);

A Pons owner rescuePoolFees pays the creator recipient in whatever currency is pending, including the virus token itself (VERIFIED_PONS H5). The feeder cannot price or sell virus tokens safely and no one may withdraw them, so burnStrayVirus() burns the feeder's entire virus balance through the virus's own burn, and emits StrayVirusBurned(amount). Anyone may call it. It reverts with NothingToClaim if the balance is zero. It is the only token-recovery function the feeder has, and it destroys rather than recovers (VIRUS_ADAPTATION A15).

§Severed feeders

The Pons owner can replace any launch's creator fee recipient: setCreatorFeeRecipient(token, new), then a 3-day timelock, then a 3-day window in which anyone may call executeCreatorFeeRecipientChange (VERIFIED_PONS F13). This is a standing Pons power over creator fee routing, and VIRUS cannot prevent it (BLOCKED_BY_PONS B5).

If it happens to a virus:

  • On the next feed(), the feeder sees creatorFeeRecipient != feeder, sets severed = true and reports it to the registry, which sets the virus record's active flag to false and emits VirusRecipientStatus(virus, false); the feeder emits RecipientStatus(false) and records NotRecipient as its sweep outcome.
  • It stops sweeping, because Pons would now credit someone else.
  • It still claims whatever the escrow already holds for it and still splits any host it holds. Fees credited to the feeder before the change remain the feeder's (security invariant 6).
  • Future fees go to the new recipient, outside VIRUS.

The change is not final from VIRUS's side: if Pons later routes fees back to the feeder, the next feed() clears severed and emits RecipientStatus(true).

A proposed change is visible before it takes effect, through factory.pendingCreatorFeeRecipient(virus) (new recipient, effective time, expiry) and the Pons CreatorFeeRecipientChangeProposed event. The virus page shows it. During the timelock, calling feed() still sweeps pre-graduation curve fees into the feeder's escrow credit.

§Fee pipeline stages

The virus page follows each unit of fee through four stages, read live from Pons and the feeder:

Stage Label What it counts
ACCRUED UNSWEPT ON PONS The feeder's share still sitting on the Pons curve (quoteFeeBalance, creatorTaxBalance) or hook (pendingFees, pendingCreatorTax in host), estimated as fee − floor(fee × share) + tax with the virus's frozen protocol share. After graduation, the virus-denominated amount waiting for the Pons operator is shown next to it.
SWEPT BY PONS CLAIMABLE BY FEEDER Host credited to the feeder in the Pons escrow, plus host already held by the feeder and not yet distributed: state().escrowed + state().undistributed.
FED BY VIRUS ALREADY FED Cumulative host distributed by feed(): totalFed.
BURNED HOST BURNED Cumulative host permanently burned: totalBurned.

Alongside these, the page shows devOwed, devClaimed and totalTreasury.

§When to call feed()

Whenever there is something in ACCRUED or SWEPT BY PONS. The feeder does nothing on its own; fees sit on Pons or in the escrow until someone calls feed(). The caller pays the gas and receives nothing, so developers, holders of the host, and anyone who wants the burn to happen have a reason to call it. A feed that finds nothing returns 0 without error.