Skip to content
BaseHub by wbnns Updated

B20 Precompile Reference

This page is the call-level companion to the B20 Native Token Standard writeup. That page explains how the standard behaves; this one gives you the wire details — every function with its four-byte selector, every event with its topic0, and every revert with the selector you’ll see come back.

The same three addresses apply on every network that has B20 turned on.

SurfaceAddress
B20Factory0xB20f000000000000000000000000000000000000
ActivationRegistry0x8453000000000000000000000000000000000001
PolicyRegistry0x8453000000000000000000000000000000000002

The base token surface. Every B20 token exposes it, whichever variant it was created as.

FunctionSelectorWhat it returns
DEFAULT_ADMIN_ROLE0xa217fddfThe top-level admin role, which is bytes32(0). Holders can manage the role graph, policy slots, and the supply cap.
MINT_ROLE0xe9a9c850The role a caller needs for mint and mintWithMemo.
BURN_ROLE0xb930908fThe role a caller needs for burn and burnWithMemo.
BURN_BLOCKED_ROLE0x32ad9be8The role behind the deprecated burnBlocked path, kept for older integrations.
SEIZE_ROLE0x3c7e9ba5The role a caller needs for seizeWithMemo.
PAUSE_ROLE0x389ed267The role a caller needs for pause.
UNPAUSE_ROLE0x309756fbThe role a caller needs for unpause.
METADATA_ROLE0x38841782The role behind updateName, updateSymbol, and updateContractURI.

Each of these names one slot on the token. The slot holds a uint64 policy ID, and the token consults it against the account listed below.

FunctionSelectorAccount checked
TRANSFER_SENDER_POLICY0xd116fc21from, on every transfer — transferFrom included.
TRANSFER_RECEIVER_POLICY0x210f521bto, on every transfer.
TRANSFER_EXECUTOR_POLICY0x724e9c53msg.sender on transferFrom, but only where it differs from from.
MINT_RECEIVER_POLICY0x6e5b013dto, on every mint.
SEIZE_HOLDER_POLICY0xb279d311from, when seizeWithMemo runs.
SEIZE_RECEIVER_POLICY0xb31da27fto, when seizeWithMemo runs.
FunctionSelectorBehavior
name0x06fdde03The token name. Fixed at creation, but updateName can change it later.
symbol0x95d89b41The token symbol. Fixed at creation, but updateSymbol can change it later.
decimals0x313ce567The decimal count. Each variant fixes this and it never moves.
totalSupply0x18160dddHow much of the token is in circulation right now.
balanceOf0x70a08231The balance held by account.
allowance0xdd62ed3eHow much spender is still cleared to move on behalf of owner.
transfer0xa9059cbbMoves amount from the caller to to, emitting Transfer.
transferFrom0x23b872ddMoves amount from from to to, drawing on the caller’s allowance. Emits Transfer.
approve0x095ea7b3Rewrites spender’s allowance to amount. Neither policies nor the pause state apply. Emits Approval.

Each of these does exactly what its plain counterpart does, then emits Memo in the log slot directly after the primary event.

FunctionSelectorWraps
transferWithMemo0x95777d59transfer
transferFromWithMemo0x929c2539transferFrom
mintWithMemo0xe44f0b12mint
burnWithMemo0x38f23b0bburn
FunctionSelectorBehavior
mint0x40c10f19Issues amount to to and emits Transfer(address(0), to, amount).
burn0x42966c68Destroys amount out of the caller’s own balance. No policy applies.
burnBlocked0xec0cf3dcDeprecated. Destroys amount belonging to a from that TRANSFER_SENDER_POLICY currently denies.
seizeWithMemo0xf916d81bTakes amount from from and reassigns it to to in one privileged step.
supplyCap0x8f770ad0The ceiling mint enforces. It tops out at type(uint128).max, which doubles as the no-cap sentinel.
updateSupplyCap0xe5a97f07Moves the cap up or down. It can never sit below the supply already outstanding.
FunctionSelectorBehavior
hasRole0x91d14854Whether account belongs to role. The graph accepts user-defined roles, though the token itself enforces none of them.
getRoleAdmin0x248a9ca3Which role can hand out or take away role. Falls back to DEFAULT_ADMIN_ROLE where nothing was set.
grantRole0x2f2ff15dAdds account to role and emits RoleGranted.
revokeRole0xd547741fDrops account from role and emits RoleRevoked.
setRoleAdmin0x1e4e0091Reassigns which role administers role. Emits RoleAdminChanged.
renounceRole0x36568abeLets the caller give up a role they hold. Emits RoleRevoked.
renounceLastAdmin0x6f79e3d7The one route to a permanently admin-less token. It strips DEFAULT_ADMIN_ROLE from the last holder for good.
FunctionSelectorBehavior
pausedFeatures0xde9997e3The set of PausableFeature values paused on this token. Don’t rely on the ordering.
isPaused0x165c44bcWhether one specific feature is paused. Constant time.
pause0x9f98bd2dPauses everything in features. It adds to the existing set, so repeats are harmless.
unpause0xb61b2ebcClears the pause on everything in features, leaving anything unlisted alone. Repeats are harmless.
FunctionSelectorBehavior
policyId0xdb3de624The policy currently wired into policyScope. An unset slot reads back as 0, the always-allow built-in.
updatePolicy0xadf9c4eaPoints policyScope at a different policy ID. The next gated call already sees it.
FunctionSelectorBehavior
nonces0x7ecebe00The permit nonce for owner, which climbs by one after each permit that lands.
permit0xd505accfThe EIP-2612 signed approval. It recovers owner from (v, r, s) by ECDSA, so only EOA signatures work.
DOMAIN_SEPARATOR0x3644e515The token’s live EIP-712 domain separator, recomputed per call.
eip712Domain0x84b0196eERC-5267 introspection over that domain.
contractURI0xe8a3d485Where the token’s contract-level metadata lives offchain, per ERC-7572.
updateContractURI0x7e5b1e24Repoints contractURI. Per ERC-7572 the resulting event carries no arguments.
updateName0x84da92a7Rewrites name. Emits NameUpdated, then EIP712DomainChanged, because the separator rotates with it.
updateSymbol0x537f5312Rewrites symbol and emits SymbolUpdated.

Extra surface carried only by tokens created as the ASSET variant.

FunctionSelectorBehavior
OPERATOR_ROLE0xf5b541a6The role behind announce and the multiplier operations.
WAD_PRECISION0x664808a8The fixed-point scale the multiplier uses, equal to 1e18.
MAX_UI_MULTIPLIER0x785c0cf0The largest multiplier the setters will take.
multiplier0x1b3ed722The multiplier in force, at WAD_PRECISION. Stored balances stay raw underneath it.
uiMultiplier0xa60bf13dThe ERC-8056 UI multiplier as it stands now.
newUIMultiplier0xdc767007The UI multiplier waiting to take over, if one is scheduled.
effectiveAt0x97a4064fWhen that pending multiplier is due to apply.
updateUIMultiplier0x628e600fQueues a UI-multiplier change for the effectiveAt timestamp.
cancelUIMultiplierUpdate0x2c97a0f0Drops the one pending change and returns the token to having none queued.
updateMultiplier0x5ffe6146The emergency override. It sets the multiplier on the spot rather than scheduling it.
toScaledBalance0x04f04c99Scales a raw balance up: rawBalance * multiplier / WAD_PRECISION.
toRawBalance0x0ca06c44Scales a scaled balance back down to its raw form.
scaledBalanceOf0x1da24f3eShorthand for toUIAmount(balanceOf(account)).
balanceOfUI0x437a9958An account’s balance with the UI adjustment already applied.
totalSupplyUI0x9bea6429Total supply with the UI adjustment already applied.
toUIAmount0x3248d4ffConverts a raw amount into its UI form.
fromUIAmount0x65cd9b3cConverts a UI amount back into its raw form.
announce0x595135ddPublishes an announcement that affects holders and fires off each bundled call atomically.
isAnnouncementIdUsed0xc0da474eWhether announce has already spent id. IDs never come back around.
batchMint0x68573107Issues amounts[i] to recipients[i] across the arrays. It’s all or nothing — one bad element reverts the lot.
extraMetadata0x4ddf9da0Reads a named metadata entry, giving back an empty string where none is set.
updateExtraMetadata0xb2851ef5Writes a metadata entry, or clears it when you pass an empty value.

Tokens created as the STABLECOIN variant add one call.

FunctionSelectorBehavior
currency0xe5a6b10fThe currency code the stablecoin says it tracks, such as "USD", "EUR", or "JPY".

Every B20 token comes into existence through this one precompile.

FunctionSelectorBehavior
createB200x62975e6aDeploys a token of the requested variant at the address derived deterministically for it.
getB20Address0x8c30260fTells you which address createB20 would pick for (variant, sender, salt). It never reverts.
isB200xfa19b927Whether this factory made token, read straight off the address prefix. It never reverts.
isB20Initialized0x6a45ba98Whether createB20 finished at token. It flips once and stays there.

The singleton registry holding every policy, keyed by uint64 policyId.

FunctionSelectorBehavior
createPolicy0xca5d55f6Opens an empty simple policy. Anyone can call it.
createPolicyWithAccounts0xa2d3044fOpens a simple policy with accounts already in it. Anyone can call it.
createCompositePolicy0x6fdd1491Opens a composite that joins existing simple policies under a logic rule.
updateAllowlist0x3388fb5bSets each of accounts to allowed on an ALLOWLIST policy, in one batch.
updateBlocklist0x5c4e51b8Sets each of accounts to blocked on a BLOCKLIST policy, in one batch.
updateComposite0xbfe142c0Swaps a composite’s children out wholesale for childPolicyIds.
stageUpdateAdmin0x1d7ae695Nominates a replacement admin for policyId. The sitting admin keeps control until the handover completes.
finalizeUpdateAdmin0x33031a9cCloses out the two-step handover. The caller becomes admin and the pending slot empties.
renounceAdmin0xefdb7fa3Gives up the policy permanently, which freezes its membership and children from then on.
isAuthorized0x55a1179eWhether account clears policyId. It never reverts, and an unknown ID behaves as an empty set.
policyExists0x330f5637Whether policyId is a built-in sentinel or an ID that was actually handed out. It never reverts.
policyAdmin0x09dd0a47The sitting admin for policyId, or the zero address for built-ins.
pendingPolicyAdmin0x017548b7The nominated admin for policyId, or the zero address when nothing is staged.
compositePolicyChildIds0x7c40df74The children a composite policyId is built from.
MIN_COMPOSITE_CHILD_POLICIES0xb3ae29f7The fewest children a composite can reference, inclusive. It never reverts.
MAX_COMPOSITE_CHILD_POLICIES0x54309870The most children a composite can reference, inclusive. It never reverts.

The gate that decides which features are live on the chain.

FunctionSelectorBehavior
isActivated0xba87af80Whether feature is on right now. It never reverts.
checkActivated0xde5bfd9bThe asserting form — it reverts with FeatureNotActivated(feature) when the feature is off.
admin0xf851a440The only address cleared to call activate and deactivate.
activate0x59db6e85Switches feature on and emits FeatureActivated.
deactivate0x22eee84cSwitches feature off and emits FeatureDeactivated.

Look these up by topic0 when you’re decoding logs.

EventInterfaceTopic0
TransferIB200xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
ApprovalIB200x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
MemoIB200x6989f5818dcfd11f8cd53b27c94cec33dae1589735f03e639cba54553a1825e8
SeizedIB200xa9aec5d8b86e2fa2fd6ac3af62f2622e3dfdab1967d4cbbb56a5df7d74cb887c
BurnedBlockedIB200x0b552e96653fd6842da37c477005d3b5c08a8c7d3631b1f43787b2dc9a1006a3
RoleGrantedIB200x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d
RoleRevokedIB200xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b
RoleAdminChangedIB200xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff
LastAdminRenouncedIB200xe8d3a9872e7ca325571ff1e4c51ddd69090a0345240cc605ccde365ec867cc67
PausedIB200x43e072977b8112813d7c2aa0b63d3c121c1fe3e714e6d2eacb5735fe4027e976
UnpausedIB200xdc6281474ea3dea2a856e225c7ba3edf427de9164495817c4d49a595e683fed4
PolicyUpdatedIB200x8b4790f7ff717fc8f60f07ae099e47ef318dc04b37ae98056b50a22b79056626
SupplyCapUpdatedIB200x6d14f44808ce024f263432bc38d019a9951fbe674e9898b54844dbc8dc09c23a
ContractURIUpdatedIB200xa5d4097edda6d87cb9329af83fb3712ef77eeb13738ffe43cc35a4ce305ad962
NameUpdatedIB200x74321da206c1b9fa34367f7ece59ca49371dcd13820b9a5c3767ae1ecceed51a
SymbolUpdatedIB200x64e8b5c6dcea43dd79766bb3b8af7c45968d12b68c960cf2da23856f34d598d4
EIP712DomainChangedIB200x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31
UIMultiplierUpdatedIB20Asset0x2205df4534432b2f60654a3fdb48737ffdaf3e9edb1a498bd985bc026b15b055
MultiplierUpdatedIB20Asset0x4dbe4840d7465bd162f67814cea0b519567a2e0e578bcde61e7f4ced361e5a3d
UIMultiplierUpdateCancelledIB20Asset0x883856335ba5f60c18b9817c4505d3c7d3f6223dcf39516b30c508c46a5e1cad
ExtraMetadataUpdatedIB20Asset0xd7bb345be29e78d635203d40fe0567e7ef19d5cd5cc5fcd25f768b8063e82aa1
AnnouncementIB20Asset0xccebf8218a62875909564adef86a6f4df81503cb617221e793357d62f8e813f7
EndAnnouncementIB20Asset0x96d64dafe2c790596430196b982ad1da3221cb3b0f4e6e2df77f2e4f71a90037
B20CreatedIB20Factory0xfd9bf2730513a1709722ff379a0844dfd8f997d600693c2bcc659e188bbdba0d
PolicyCreatedIPolicyRegistry0xdc870ce85be577234b8548f42b93f84bbff6d0c1f38ee725c809c49932a13885
PolicyAdminStagedIPolicyRegistry0xdbf3b34a4c956c56ca05cd4b8f9293a4347ad61994445a4b89817d4a19561136
PolicyAdminUpdatedIPolicyRegistry0x98925cfb1bc09c5b43dd0dd56d3d95aa04fb3300927580cc588c3f5dd58c15e1
AllowlistUpdatedIPolicyRegistry0x18c46532f90187ba11e436e21da087b684801d7f0787f2043f26f079c91e9ef0
BlocklistUpdatedIPolicyRegistry0x2ff63c102b1b9fd7f5d39f83039c5d6aaf50a414a4f2def2704e41be2628f1e3
CompositePolicyUpdatedIPolicyRegistry0x4ff6adaab31b0df87aa7b8b7320c52b8b3b5eede3bf28a6baaaa8b8b7e1d6363
FeatureActivatedIActivationRegistry0x8c7a0ecdbb8d96e867e43ec1aef80027976ee493c18bef399fa799ed19752451
FeatureDeactivatedIActivationRegistry0x15bf65a782c3258c63268ba9d7aed710cf9f9315d3687d9a4632ccdad7926c84

Reverse lookup for revert selectors. A few names appear more than once — NonPayable and Unauthorized are shared across surfaces, and Unauthorized does not even carry the same selector everywhere, so read the interface column.

ErrorSelectorCause
NonPayable0x6fb1b0e9ETH rode along with a call to a nonpayable token selector.
AccessControlUnauthorizedAccount0xe2517d3faccount is missing neededRole.
AccessControlBadConfirmation0x6697b232renounceRole got a callerConfirmation that wasn’t msg.sender.
Unauthorized0x82b42900A positional authorization check failed — one that doesn’t reduce to a missing role.
ContractPaused0xf9df5ac9The PausableFeature this operation falls under is paused.
InsufficientAllowance0x192b9e4eThe transferFrom asked for more than spender is allowed to move.
InsufficientBalance0xdb42144dThe transfer or burn asked for more than sender holds.
InvalidSender0x4c14f64cThe source address on the transfer is not valid, typically address(0).
InvalidReceiver0x9cfea583The destination address on the transfer is not valid, typically address(0).
InvalidApprover0x8bc146c4The owner on the approval is not valid, typically address(0).
InvalidSpender0x4e15efdaThe spender on the approval is not valid, typically address(0).
InvalidAmount0x2c5211c6An amount came through as zero where a nonzero one is required. ERC-20 amount arguments are exempt.
EmptyFeatureSet0x4861ff45An empty array reached a function that needs at least one entry.
InvalidSupplyCap0x0a3780ceThe proposed cap sits outside the permitted band — under current supply, or over the maximum.
SupplyCapExceeded0x4b344b11The mint would carry totalSupply past the cap.
PolicyForbids0xa43fec12One of the policy slots turned the operation down.
PolicyNotFound0xcccad523The policy ID given isn’t in the registry.
UnsupportedPolicyType0xcdd98a4aThis token, or this variant of it, has no such policyScope.
AccountNotSeizable0x91dbbc8dseizeWithMemo targeted a from that SEIZE_HOLDER_POLICY still authorizes.
AccountNotBlocked0x64a5cb46The deprecated burnBlocked targeted a from that is still authorized.
ExpiredSignature0xbd2a913cThe permit arrived with a deadline already behind block.timestamp.
InvalidSigner0x7ba5ffb5ECDSA recovery on the permit produced a signer that isn’t the owner claimed.
LastAdminCannotRenounce0x361513e7The sole remaining admin tried to leave via renounceRole(DEFAULT_ADMIN_ROLE, ...).
NotSoleAdmin0x2a98e73brenounceLastAdmin() was called while other accounts still hold DEFAULT_ADMIN_ROLE.
ErrorSelectorCause
AnnouncementIdAlreadyUsed0xd10b3c9eThe id handed to announce was already spent.
AnnouncementInProgress0x5c5f0829A call dispatched from inside announce tried to call announce again.
InternalCallMalformed0x4e2f143eA call dispatched by announce came in under four bytes.
InternalCallFailed0xb288a127A call dispatched by announce reverted normally, and its reason is wrapped here.
InvalidMetadataKey0x86ea3abbupdateExtraMetadata got an empty key.
InvalidMultiplier0x6f12f3dcA multiplier setter was handed a value it won’t accept.
EffectiveAtInPast0x14119cf6The effectiveAt on updateUIMultiplier isn’t in the future.
EffectiveAtTooFar0x1ce214faThe effectiveAt on updateUIMultiplier overran type(uint64).max.
UIMultiplierUpdateExists0x4481a68eupdateUIMultiplier was called while another update is still pending.
UIMultiplierUpdateDoesNotExist0xa7d6a5cacancelUIMultiplierUpdate found nothing pending to cancel.
LengthMismatch0xab8b67c6A batched call got parallel arrays of unequal length.
EmptyBatch0xc2e5347dA batched call got empty arrays.
ErrorSelectorCause
NonPayable0x6fb1b0e9ETH rode along with a call to a nonpayable factory selector.
TokenAlreadyExists0x15ef3a57Something already occupies the deterministic address this creation derives.
InvalidVariant0xf10e8e43The variant given isn’t a recognized B20Variant.
UnsupportedVersion0xc0d8b4e0The leading version byte in params matches no known encoding for that variant.
MissingRequiredField0x4a43ae87A required string argument came through empty.
InvalidCurrency0x997c1de8The stablecoin currency had content, but carried a byte outside A-Z.
InvalidDecimals0xca950391The asset decimals fell outside the permitted inclusive range.
InitCallFailed0x4eae0860One of the initCalls reverted, and the factory passes the underlying reason back up.
ErrorSelectorCause
NonPayable0x6fb1b0e9ETH rode along with a call to a nonpayable registry selector.
Unauthorized0x82b42900The caller isn’t the admin the operation demands.
PolicyNotFound0x720caa4fThe policy ID referenced doesn’t exist.
IncompatiblePolicyType0xf1011ef5The operation doesn’t fit this policy’s type.
ZeroAddress0xd92e233dAn address argument that has to be set came through as the zero address.
BatchSizeTooLarge0x083e2f67A membership batch ran past the registry’s limit.
NoPendingAdmin0xb4539afafinalizeUpdateAdmin ran with nobody staged.
ChildPoliciesOutsideOfRange0x697ec868A composite’s child count fell outside the permitted range.
InvalidChildPolicy0x46508ef6A composite’s children must be existing simple policies. Composites can’t nest.
ErrorSelectorCause
Unauthorized0x8e4a23d6The caller isn’t the activation admin.
AlreadyActivated0x866b0041The feature is already on.
FeatureNotActivated0xb9b2a425The feature is off.
DelegateCallNotAllowed0x0d89438eThe precompile was reached through DELEGATECALL or CALLCODE.
StaticCallNotAllowed0xbeaba5b7A state-changing entry point was reached from inside a STATICCALL frame.
RoleSolidity constant
Default adminbytes32(0)
Mintkeccak256("MINT_ROLE")
Burnkeccak256("BURN_ROLE")
Deprecated blocked burnkeccak256("BURN_BLOCKED_ROLE")
Seizekeccak256("SEIZE_ROLE")
Pausekeccak256("PAUSE_ROLE")
Unpausekeccak256("UNPAUSE_ROLE")
Metadatakeccak256("METADATA_ROLE")
Asset operatorkeccak256("OPERATOR_ROLE")
ScopeSolidity constant
Transfer senderkeccak256("TRANSFER_SENDER_POLICY")
Transfer receiverkeccak256("TRANSFER_RECEIVER_POLICY")
Transfer executorkeccak256("TRANSFER_EXECUTOR_POLICY")
Mint receiverkeccak256("MINT_RECEIVER_POLICY")
Seize holderkeccak256("SEIZE_HOLDER_POLICY")
Seize receiverkeccak256("SEIZE_RECEIVER_POLICY")

Two counters are reserved for the built-ins; anything you create starts at 2.

NameValue
ALWAYS_ALLOW0
ALWAYS_BLOCK(uint64(uint8(IPolicyRegistry.PolicyType.ALLOWLIST)) << 56) | 1

Custom IDs pack a type byte over a counter:

[8-bit PolicyType][56-bit counter]
PolicyTypeByte
BLOCKLIST0x00
ALLOWLIST0x01
UNION0x02
INTERSECT0x03

The variant is encoded into the address, so you can read it off the prefix.

VariantByteAddress shape
ASSET0x000xB200...
STABLECOIN0x010xB201...
ConstantValue
Minimum Asset decimals6
Maximum Asset decimals18
Maximum supply cap, and the no-cap sentineltype(uint128).max
Bitmask with every feature paused15TRANSFER, MINT, BURN, SEIZE together