The five-minute version
Establish the tier, read the word, branch on the bits. Everything else in these pages is elaboration on those three steps.
All docs
Everything else in these pages is optional. This is the part that is not.
Step one: which tier
Before the declaration means anything, establish whether the code producing it is code you know. This is one bytecode read and no external call.
if (BERCVerification.isClonedFrom(token, BERC_RUNTIME_V1)) {
// Verified — the declarations below are guaranteed.
} else if (_answersBehaviorFlags(token)) {
// Self-declared — the declarations below are a claim.
} else {
// Unknown — you have learned nothing. Apply your existing policy.
}Step two: read the word
uint256 flags = IERC20Behavior(token).behaviorFlags();
if (flags & FEE_ON_TRANSFER != 0) { /* quote against arrival */ }
if (flags & TRANSFER_HOOK != 0) { /* budget spare gas */ }
if (flags & (PAUSABLE | BLOCKLIST) != 0) { /* screen first */ }
if (flags & NON_TRANSFERABLE != 0) { /* do not list */ }
if (flags & UPGRADEABLE != 0) { /* all of the above can change */ }
if (flags & (MINTABLE | SEIZABLE) != 0) { /* an authority can dilute or take */ }A word of zero, returned by a call that succeeded, is a statement: this token departs from a plain ERC-20 in no way at all, and the fast path is safe.
The one mistake that costs money
A token outside the framework has no behaviorFlags at all, and the call reverts. The tempting move is to catch that and continue with zero. Do not.
// WRONG. Do not do this.
try IERC20Behavior(token).behaviorFlags() returns (uint256 f) {
flags = f;
} catch {
flags = 0; // collapses 'unclassified' into 'ordinary'
}Zero means the token declared nothing. A revert means the token declared nothing about declaring nothing. Two things go wrong when those are treated alike.
- Silence is not a denial
- Plenty of ERC-20s take a cut of every transfer and have never heard of this framework. Their behaviorFlags reverts, and mapping that to zero tells you they are clean.
- An uninitialised clone also reverts
- Its extension set is not sealed yet, so every discovery view refuses to answer. Anyone can initialise it afterwards, with a fee. A zero you cached today can be wrong tomorrow.
Map a revert to unknown and let your existing policy for arbitrary tokens handle it. You already have one — every token was in that category before this framework existed.
Where to go from here
- Bit 0
- Fee on transfer. Quoting against the amount you send rather than the amount that arrives is the single most common integration failure.
- Bit 2
- Transfer hook. Transfers can revert for reasons unrelated to balances, and you need spare gas to survive one.
- Bits 3 and 4
- Pause and blocklist. Screen before submitting, and treat a frozen counterparty as a risk signal rather than a transfer-time check.
- Bit 5
- Non-transferable. If your protocol has to move the token to function, you cannot list it.
- Bit 6
- Upgradeable. Everything above holds only as long as the upgrade authority allows it.
- Bits 7 and 8
- Mintable and seizable. Supply can grow and balances can vanish, and neither shows up in a simulation because neither is a transfer you could have screened.