Predicate Types
The supported operators are
<, <=, =, !=, >, and >=. Predicate values use 0x-prefixed hexadecimal strings.
Evaluation
Base checks a transaction before inclusion. If a predicate is false, the transaction remains pending. An earlier transaction can change the balance or storage value that a pending transaction watches. The pending transaction can then become eligible in the same Flashblock.Balance Example
Balance predicate
Storage Example
Storage predicate
(storage[address][slot] & mask) with value. Omit mask to compare the full storage word.
Block and Flashblock Position
Useblock_number to target a block and flashblock_index to target a position within that Flashblock. Add separate predicates when both conditions must match. Valid flashblock_index values range from 1 through 10, inclusive; the corresponding hexadecimal values are 0x1 through 0xa.
Safety
A predicate does not simulate a transaction. Keep critical checks in the application call. Predicates read raw storage. They cannot call view functions or evaluate computed values. Verify the target contract’s storage layout before using a storage predicate. State can change during block building. A condition can become true or false before inclusion. Do not use transaction placement as a source of randomness. Do not assume a zero-valued slot makes a CREATE2 address, proxy, or uninitialized contract safe to call. Validity criteria are not recorded onchain. Do not include secrets in calldata or predicate values.Read B20 and Policy Registry State
B20 tokens and the Policy Registry run as precompiles, but they keep their state in ordinary account storage. Each B20 token stores its state at the token address. The Policy Registry stores its state at0x8453000000000000000000000000000000000002. A storage predicate reads these slots the same way it reads any contract’s storage.
The balance predicate reads an account’s native ETH balance. To gate on a B20 token balance, use a storage predicate on the token’s balance slot.
Slot Derivation
B20 storage uses ERC-7201 namespaces. A top-level field lives atroot + offset. A mapping value lives at keccak256(abi.encode(key, baseSlot)), where baseSlot is root + offset. For a nested mapping, hash the outer key first, then hash the inner key against the result. Packed fields follow Solidity packing: offset 0 is the lowest-order byte of the word.
B20 Token Fields
Offsets from thebase.b20 root, read at the token address:
Policy Registry Fields
Offsets from thebase.policy_registry root, read at the Policy Registry address. Policy IDs are uint64 keys.
A
members flag records list membership, not the isAuthorized result. On an allowlist, true means the account is authorized. On a blocklist, true means the account is blocked. Composite (UNION and INTERSECT) policies have no member set of their own, and a predicate cannot evaluate them. To watch a composite policy, read the member slots of its child policies. An inverted policy ID has no record of its own; clear bit 63, read the base policy’s slots, and invert the condition you check.
Example: Wait for an Allowlist Addition
Compute themembers[policyId][account] slot with cast index, then wait for it to become 1:
Compute a member slot
Allowlist predicate
11 (0xc78b71fee795ddd74aff64ea9b2474194c938c3196430e10bb5f01ed4843400b) at the token address with mask 0x1 and value 0x0. When you mask a packed field, shift the expected value into the same byte position as the mask.