Why permissionless
What is immutable, what the Safe can and cannot do, and why the Base side has no admin.
Token holders need to trust that "fees go to charity" cannot later be quietly undone, and Petal must never be in custody of charity money. The two design decisions below are the whole of the trust model.
Fees pointed at a charity address are locked for the life of the token
- A charity address has no owner, no admin, no rescue, no upgrade, and no code path that calls Pons
transferCreatorFeeRecipient. Once a token points at it, the recipient can never be changed through the relay contracts. - There is no migration mechanism and no deployer exit. The token's deployer has zero powers over the charity address.
- The only thing anyone can do is
claimAndBridge, which can only send ETH to the receiver for the same config ID on Base, andflush, which can only send it to donate.gg with that config ID. - A migration lever or a deployer exit, even behind a delay, would let someone redirect money and undermine the promise. The contracts have neither.
Consequences:
- A creator who changes their mind has no undo.
- Non-ETH balances credited to a charity address in Pons Fee Escrow stay there. The relay does not touch them.
- Trust boundary outside the relay: the Pons protocol owner can propose a recipient change on any token through the factory's
setCreatorFeeRecipient, with a 3-day timelock and permissionless execution. The relay contracts cannot prevent that. It is a Pons-level power, disclosed as a risk, not a lever Petal holds.
The Safe administers the approved list and fee cap only, never funds
The Safe is the registry's safe. It holds exactly three powers on the Robinhood registry:
| Power | Function | Effect |
|---|---|---|
| Add or remove a config ID from the approved list | approve(bytes32), revoke(bytes32) | Whether the registry will deploy a charity address for that config ID. Removal only blocks future deployments; existing charity addresses keep working unchanged. |
| Set the live fee cap | setMaxBridgeFee(uint256) | The largest Across fee a charity address accepts. Never above the hard ceiling of 0.002 ETH. |
| Hand the role to another address, in two steps | proposeSafe(address), then the new address calls acceptSafe() | Changes only who administers. No redeploy, no address change, no effect on any charity address. |
What the Safe cannot do:
- Move, redirect, pause, or withdraw ETH anywhere. No function on any contract lets the Safe, the deployer, or anyone else move ETH other than along the fixed path to donate.gg.
- Change the minimum bridge amount or the hard ceiling. Both are immutable.
- Change an existing charity address's config ID, receiver, or behaviour.
- Upgrade anything. There are no proxies except the EIP-1167 clones, whose implementations are immutable.
The Safe is currently a single-key wallet. The two-step handover exists so the role can move to a multisig without redeploying anything, and it is intended to do so before meaningful volume.
The worst a compromised Safe can do is approve a bad config ID for future deployments or set the live fee cap to the hard ceiling. It cannot touch money already flowing. Every Safe action emits an event, so approvals and cap changes are visible on the explorer.
The Base side has no admin
The Base registry has no admin at all. Deploying a receiver is permissionless for any config ID. A receiver for an unapproved config ID simply never receives anything, because no charity address will target it: a charity address can only exist for an approved config ID, and it can only deposit into Across with the receiver for its own config ID as recipient.
What this means for a verifier
charityAddressFor(configId)on the Robinhood registry gives the one address a token should point at. It is deterministic; nobody can substitute a different contract at that address.approved(configId)is the Safe's vouching that the config ID belongs to a real charity. It is the only guard against a wrong ID, because donate.gg's contract accepts any 32 bytes.- Reading the two contracts' source on the explorer shows two external functions that move money,
claimAndBridgeandflush, and no others.
The relay's fork tests cover each of these guarantees: non-admin callers revert on every admin function, a cap above the ceiling reverts, revoking leaves an existing charity address bridging normally and refuses a fresh deploy, the handover changes only who administers, and a former fee recipient cannot transfer a token back once it points at a charity address.