Behaviour flags
Nine bits, their values, what each one costs you, and the two combinations that cannot exist on chain.
All docs
Bit values live in BehaviorFlags.sol. Copy the library verbatim or inline the numbers; there is nothing in it but constants and one pure function.
| Bit | Flag | Value | What it means for you |
|---|---|---|---|
| 0 | FEE_ON_TRANSFER | 1 | The recipient receives less than the amount you named |
| 1 | REBASING | 2 | balanceOf can move without a Transfer event naming the account |
| 2 | TRANSFER_HOOK | 4 | A transfer calls another contract, which can revert it or burn gas |
| 3 | PAUSABLE | 8 | An authority can halt all transfers |
| 4 | BLOCKLIST | 16 | An authority can bar individual accounts |
| 5 | NON_TRANSFERABLE | 32 | Transfers always revert; only mint and burn move value |
| 6 | UPGRADEABLE | 64 | The code can be replaced, so every row above can change |
| 7 | MINTABLE | 128 | An authority can create supply and dilute every holder |
| 8 | SEIZABLE | 256 | An authority can destroy any account's balance |
Two kinds of bit
The division at bit six is the most useful thing on this page. Bits 0 to 5 describe what the token itself does: five of them change what a transfer does and will show up in a simulation, and REBASING shows up instead as a balance that moved with no Transfer event naming the account.
Bits 6 to 8 describe powers an authority holds. Nothing reveals any of them until they are used — no simulation, no test transfer, no amount of watching. Which is exactly what makes them the ones an integrator discovers late, and why they are in the word at all.
Both reference tokens and every runtime clone set MINTABLE and SEIZABLE unconditionally, because mint and burn live on the shared base and no extension set can remove them. Without those bits, a token with no extensions would report zero — which this vocabulary defines as indistinguishable from a plain ERC-20 — while its supply authority could dilute every holder and take any balance.
SEIZABLE is not BLOCKLIST. A freeze stops value moving, is reversible, and leaves the balance where it was. A seizure takes it, and nothing later restores it.
Two rules
A flag is set when the extension is installed, not when it is currently active. And the word never changes: configuration moves, declarations do not. Both rules exist for one reason — so that a single read is enough.
There is one exception, and the vocabulary names it itself. A token declaring UPGRADEABLE can have its code replaced, and the replacement is not bound by the set the old code sealed. The word is safe to cache forever exactly when that bit is clear — and a verified clone of an immutable runtime is the case where forever is a fact about bytecode rather than a promise.
Reserved bits
REBASING is defined here but implemented by no module in this framework. It exists so the vocabulary is complete: a token elsewhere can declare it and you can read it with exactly the same code. Bits outside the assigned set are reserved, and declaring one is rejected at deployment rather than quietly ignored.
Forbidden combinations
Some declarations contradict each other, and a token making both would be lying about at least one. The constructor rejects them, so you will never meet one on chain.
| Combination | Why it cannot exist |
|---|---|
| NON_TRANSFERABLE + FEE_ON_TRANSFER | The transfer path is unreachable, so no fee can ever be charged |
| NON_TRANSFERABLE + TRANSFER_HOOK | The transfer path is unreachable, so no hook can ever fire |
Both share one rationale: a declaration that can never manifest is not a harmless extra, it is a false warning that costs integrators a code path they will never need. The check runs at deployment against BehaviorFlags.conflictingPair — the same pure function you can run yourself, against a token you did not deploy.
All thirty-two subsets of the five modules are deployed in the test suite. The twenty permitted ones must transfer correctly; the twelve forbidden ones must fail inside their own constructor.