diff --git a/README.md b/README.md
index 531ce01..74bfc0e 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ forge install base/base-std
src
├── StdPrecompiles.sol: Collection of precompiles and their interfaces
└── interfaces
+ ├── IActivationRegistry.sol: Runtime feature activation registry (Beryl+)
├── IB20.sol: Core Token Standard
├── IB20Stablecoin.sol: Stablecoin variant of B20
├── IB20Security.sol: Security variant of B20
diff --git a/src/interfaces/IActivationRegistry.sol b/src/interfaces/IActivationRegistry.sol
index e138ad4..191ed81 100644
--- a/src/interfaces/IActivationRegistry.sol
+++ b/src/interfaces/IActivationRegistry.sol
@@ -2,88 +2,94 @@
pragma solidity >=0.8.20 <0.9.0;
/// @title IActivationRegistry
-/// @notice Singleton precompile that gates Base-native features behind
-/// an activation admin. Each feature is identified by an opaque
-/// `bytes32` feature id and is either activated or not;
-/// consumers (other precompiles, system configuration, or
-/// downstream contracts) consult `isActivated` to gate behavior.
+/// @notice Singleton precompile that gates Base-native features behind an
+/// activation admin. Each feature is identified by an opaque `bytes32`
+/// id and is either enabled or disabled; all features default to
+/// disabled. The admin can call `enable` or `disable`; any other
+/// caller reverts with `Unauthorized`. No-op transitions (enabling an
+/// already-enabled feature or disabling an already-disabled feature)
+/// also revert.
///
-/// The activation admin is the only address authorized to call
-/// `activate`; all other callers revert with `Unauthorized`.
-/// Activation is one-way: once a feature is activated it cannot
-/// be deactivated.
+/// @dev The precompile enforces two call-context invariants surfaced as ABI
+/// reverts:
+/// - `DelegateCallNotAllowed`: entry points require a direct `CALL`.
+/// `DELEGATECALL` / `CALLCODE` are rejected so the caller identity is
+/// bound to `msg.sender`, not the calling contract's storage context.
+/// - `StaticCallNotAllowed`: `enable` and `disable` mutate state and
+/// cannot be invoked from a `STATICCALL` frame.
///
-/// @dev The precompile enforces two call-context invariants that are
-/// surfaced as reverts but cannot originate from normal Solidity
-/// consumers:
-/// - `DelegateCallNotAllowed`: the precompile MUST be invoked
-/// via `CALL` (not `DELEGATECALL` or `CALLCODE`), so the admin
-/// identity is bound to `msg.sender` rather than the calling
-/// contract's storage context.
-/// - `StaticCallNotAllowed`: `activate` mutates state and cannot
-/// be invoked from a `STATICCALL` frame.
+/// Feature ids are opaque to the registry: any `bytes32` is a valid id.
+/// By convention the producing component picks a stable id derived from
+/// a human-readable feature name (the chain-node source uses 32-byte
+/// digests for this purpose).
///
-/// Feature ids are opaque to the registry: it does not interpret
-/// them, and any `bytes32` is a valid id. By convention the
-/// producing component picks a stable id derived from a
-/// human-readable feature name (the chain-node source uses
-/// 32-byte digests for this purpose).
+/// `FeatureNotEnabled` is raised by consumers that call an
+/// `assertEnabled`-style gate on a disabled feature, not by `isEnabled`
+/// itself (which returns `false` instead).
interface IActivationRegistry {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
- /// @notice `caller` is not the activation admin and is therefore
- /// not authorized to call `activate`.
+ /// @notice `caller` is not the activation admin and is therefore not
+ /// authorized to call `enable` or `disable`.
error Unauthorized(address caller);
- /// @notice `activate` was called on a feature that is already
- /// activated.
- error AlreadyActivated(bytes32 feature);
+ /// @notice `enable` was called on a feature that is already enabled.
+ error AlreadyEnabled(bytes32 feature);
- /// @notice `feature` is not activated. Returned by precompiles that
- /// consult the registry as a hard gate (the chain node
- /// uses an `assertActivated`-style flow for this); not
- /// raised by `isActivated`, which returns `false` instead.
- error FeatureNotActivated(bytes32 feature);
+ /// @notice `disable` was called on a feature that is already disabled.
+ error AlreadyDisabled(bytes32 feature);
- /// @notice The precompile was invoked via `DELEGATECALL` or
- /// `CALLCODE`. All entry points require a direct `CALL`.
+ /// @notice `feature` is not enabled. Raised by consumers that use the
+ /// registry as a hard gate (e.g. an `assertEnabled` pattern);
+ /// not raised by `isEnabled`, which returns `false` instead.
+ error FeatureNotEnabled(bytes32 feature);
+
+ /// @notice The precompile was invoked via `DELEGATECALL` or `CALLCODE`.
+ /// All entry points require a direct `CALL`.
error DelegateCallNotAllowed();
- /// @notice A state-mutating entry point (`activate`) was invoked
- /// from a `STATICCALL` frame.
+ /// @notice A state-mutating entry point (`enable` or `disable`) was
+ /// invoked from a `STATICCALL` frame.
error StaticCallNotAllowed();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
- /// @notice Emitted when `feature` transitions from inactive to
- /// activated. `caller` is the activation admin.
- event FeatureActivated(bytes32 indexed feature, address indexed caller);
+ /// @notice Emitted when `feature` transitions from disabled to enabled.
+ /// `caller` is the activation admin.
+ event FeatureEnabled(bytes32 indexed feature, address indexed caller);
+
+ /// @notice Emitted when `feature` transitions from enabled to disabled.
+ /// `caller` is the activation admin.
+ event FeatureDisabled(bytes32 indexed feature, address indexed caller);
/*//////////////////////////////////////////////////////////////
ACTIVATION QUERIES
//////////////////////////////////////////////////////////////*/
- /// @notice Whether `feature` is currently activated. Returns
- /// `false` for any feature id that has never been
- /// activated (the default state).
- function isActivated(bytes32 feature) external view returns (bool);
+ /// @notice Whether `feature` is currently enabled. Returns `false` for
+ /// any feature id that has never been enabled (the default state).
+ function isEnabled(bytes32 feature) external view returns (bool);
- /// @notice The address authorized to call `activate`.
- function admin() external view returns (address);
+ /// @notice The address authorized to call `enable` and `disable`.
+ function activationAdmin() external view returns (address);
/*//////////////////////////////////////////////////////////////
ACTIVATION CONTROL
//////////////////////////////////////////////////////////////*/
- /// @notice Activates `feature`. Caller MUST equal `admin()` (else
- /// `Unauthorized`). Reverts with `AlreadyActivated` if the
- /// feature is already activated; reverts with
- /// `StaticCallNotAllowed` if invoked under `STATICCALL`.
- /// Emits `FeatureActivated` on success. Activation is
- /// one-way: there is no `deactivate` counterpart.
- function activate(bytes32 feature) external;
+ /// @notice Enables `feature`. Caller MUST equal `activationAdmin()` (else
+ /// `Unauthorized`). Reverts with `AlreadyEnabled` if the feature
+ /// is already enabled; reverts with `StaticCallNotAllowed` if
+ /// invoked under `STATICCALL`. Emits `FeatureEnabled` on success.
+ function enable(bytes32 feature) external;
+
+ /// @notice Disables `feature`. Caller MUST equal `activationAdmin()` (else
+ /// `Unauthorized`). Reverts with `AlreadyDisabled` if the feature
+ /// is already disabled; reverts with `StaticCallNotAllowed` if
+ /// invoked under `STATICCALL`. Emits `FeatureDisabled` on success.
+ function disable(bytes32 feature) external;
}