Security model
Who can do what with a vault, what the contract enforces on every call, and what is still a risk. Feeroute is beta software and has not been audited by a third party.
Who can do what
| Who | Can | Cannot |
|---|---|---|
| Feeroute | Nothing on your vault. The factory has no owner, no admin keys, and holds no funds. | Move funds, change splits, pause, or upgrade anything |
| Vault owner | Propose and cancel split and route changes, take back ERC-20 tokens with rescueToken, hand the vault to another wallet in two steps | Skip the timelock, take ETH owed to recipients, change the creator cap or the timelock |
| Anyone | Call sweep, execute a proposal once its timelock has passed, release unlocked ETH to its recipient, claim from Pons for the vault | Send funds anywhere except the recipients |
| Recipient | Release its unlocked share, or send it to another address with releaseTo | Touch any other recipient's share |
| Pons admin | Change a token's fee recipient through Pons' own 72-hour timelock. The report shows such a request while it waits. | Take funds already in the vault |
Rules the contract enforces
- Shares add up to exactly 10,000 basis points, with 1 to 10 recipients, no zero shares, and no duplicates. The zero address and the vault itself can't be recipients.
- The owner's own address never holds more than the creator cap. A proposal is checked again when it executes, in case ownership changed in between.
- Vesting is at most 365 days and the timelock at most 30 days. The creator cap and the timelock are immutable.
- Split and route changes apply only after the timelock, and are announced by
SplitsProposedandRouteProposedevents that the report shows. - Every function that calls another contract or sends funds is protected against reentrancy (OpenZeppelin ReentrancyGuard).
- Ownership changes take two steps (Ownable2Step): the new owner has to accept, so a typo can't hand the vault to a dead address.
- Recipient labels live on-chain, so the public report never depends on a database Feeroute controls.
Safe payouts
A sweep never fails because of one recipient. Each payment forwards 50,000 gas; a recipient that reverts or needs more is credited instead (PaymentDeferred) and releases later. ETH owed to recipients, whether deferred or still vesting, is tracked in totalOwed and is never swept twice.
The escrow claim inside a sweep is limited to 500,000 gas, so a misbehaving escrow can't eat the whole transaction. If a sweep is sent with too little gas for a full claim, it reverts with InsufficientGas rather than succeeding without claiming. claimFromPons() claims with all available gas as a fallback.
Known limitations
- The creator cap only covers the owner's own address. An owner can add a second wallet they control, or transfer ownership, which needs no timelock.
- A proposal that has passed its timelock doesn't expire; anyone can execute it later. The report keeps showing it as ready until it is executed or cancelled.
rescueTokensends every ERC-20 balance in the vault, including fees paid in tokens, to the owner without a timelock. Only ETH is split by the rules.- Renouncing ownership would freeze the split and route for good. The app doesn't offer it.
- If Pons moves its escrow or factory, credit left in the old escrow can't be claimed by the vault. Later fees still reach the vault, since the route points at it.
- Fees still in the curve, not yet swept by the Pons operator, when a new split executes are paid under the new split.
- The Pons admin can move a token's route through its own 72-hour timelock.
- How fees flow after a token graduates to its Uniswap v4 pool hasn't been verified yet. The vault can claim token-denominated credit with
claimTokenFromPons, but that credit isn't split automatically.
Internal review
Before mainnet the vault, the factory, and this site were reviewed line by line. It was an internal review, not a professional audit. What it changed:
| Finding | Fix |
|---|---|
| The vault couldn't claim ERC-20 credit from the Pons escrow, which pool fees may use after graduation | claimTokenFromPons(token) |
| An escrow that burns gas in claim() could stall sweeps | Claim inside sweep capped at 500,000 gas, plus a full-gas claimFromPons() |
| A sweep sent with low gas could succeed while its claim silently ran out of gas | Sweep reverts with InsufficientGas |
| The impersonating dev wallet could appear in production if dev settings leaked | Shown only when the RPC is localhost |
| Token status was cached right after fees were routed | Token lookups are never cached |
| A new owner after transferOwnership couldn't find their vault in the app | Vault lookup reads owner() on every vault |
| The route could be pointed at a contract that can't claim from Pons or move the route again | Split editor warns when the target is a contract |