Catalog / module / burn
Burn sink
v1Burn tokens, provably.
What it does
burn-sink v1: Burn tokens, provably
A sink for one ERC-20 that destroys whatever reaches it and keeps two honest counters:
| Counter | Meaning | |---|---| | `totalBurned` | Destroyed with the token's own `burn()`. Each burn is checked: total supply and the sink's balance must both fall by exactly the amount. | | `totalSentToDead` | Sent to `0x…dEaD` because a verified burn was not possible (no `burn()`, a `burn()` that reverts, or a fake one). Supply does **not** change; the tokens are only out of reach. |
When the sink is set up it checks the token. If `totalSupply()` cannot be read, a burn can never be verified, so the sink is fixed to dead-address mode (`deadOnly = true`), and both the UI and the presets say so.
Once one real burn has worked (`burnProven`), the sink only burns from then on. If the token later refuses to burn, the call reverts so it can be retried; the sink never quietly switches to the dead address. A burn that runs out of gas also reverts rather than falling back, so nobody can push a burnable token onto the dead address with a tight gas limit.
Config (`BurnSinkConfig`)
`token`: the token to burn. It must be a contract. **Locked forever.**
Use
- `burnFrom(amount)`: approve the sink first. It pulls `amount` (measured by balance delta) and destroys it in the same call. Closed while paused, halted or migrated. - `burnAll()`: destroys every token sitting in the sink, for example from a route, a splitter or a direct transfer. Anyone can call it, in every state. - `burnInfo()`: token, mode, both counters and what is waiting. `history(from, count)` lists every burn.
Roles
- **Owner:** `pause`/`unpause` (blocks `burnFrom` only) and `rescue` for *other* tokens sent by mistake. The burn token is locked against rescue. - **Anyone:** `burnFrom`, `burnAll`.
Fee lanes
None. Burns are never charged; any fee was paid where the tokens came from (for example a burn-mint-nft or a splitter). The module is not flagged `FLAG_CHARGES_INFLOW`.
Risks in plain English
- If the token has no working `burn()`, the sink sends to the dead address instead. That is shown separately and is not a supply reduction. - A token with transfer fees burns what actually arrived, not what you approved. - The sink trusts the token's `totalSupply()` and `balanceOf()` to tell the truth when it verifies a burn.
What if
- **Someone sends tokens straight to the sink?** Anyone calls `burnAll()` and they are burned, even while paused. - **The token cannot burn?** Tokens go to `0x…dEaD` and are counted under `totalSentToDead`, never as burned. - **The owner loses their key?** Nothing changes. Burning is permissionless and the sink holds nothing between calls. - **The token pauses burning after it has worked?** `burnAll` reverts until burning works again; tokens wait in the sink and nobody can take them out.
Implementation notes
`burnFrom` pulls with a plain `transferFrom`, measured by balance delta, and credits the amount with `_creditUncharged`, which is the base's documented path for uncharged entries. The tokens then leave custody in the same call: by `_consume` (verified burn) or by `_pushOut` to the dead address. The verified burn runs as a self-call (`burnHeldVerified`, callable only by the sink), so a fake burn is rolled back before the fallback.
Conformance: the sink has no withdraw-style exit. `_cfExit` runs `burnAll()` and asserts that nothing is stuck, with zero custody and zero balance.
Risks in plain English
- If the token has no working burn(), tokens go to 0x…dEaD. That is not a burn: total supply does not change, the tokens are only out of reach. The sink counts these separately as sent to dead.
- Once one real burn has worked, the sink only burns: if the token later refuses to burn, burns revert until it works again instead of quietly switching to the dead address.
- The owner can never take the burn token out of the sink; rescue only works for other tokens sent by mistake.
What if…
- What if someone sends tokens straight to the sink?
- Anyone can call burnAll() at any time, even while paused, and they are burned. Nothing can ever be withdrawn.
- What if the token cannot burn?
- The sink sends the tokens to the dead address 0x…dEaD and counts them under totalSentToDead, so nobody can claim they were burned.
- What if the owner loses their key?
- Nothing changes. burnFrom and burnAll are permissionless and the sink holds nothing between calls.
- Is there a fee?
- No. Burning is never charged; fees, if any, were paid where the tokens came from.
Fees
No protocol fee on this module.
Evidence
- Form check
- Built from the source files
- Implementation
- —
- Code hash
- —
Settings you choose at deploy
- Token to burnBurns use the token's own burn() and are verified against totalSupply. Tokens that cannot burn go to the dead address and are counted separately.Locked forever