Ownership structure and access control
A chain owner of an Arbitrum chain is an entity that can carry out critical upgrades to the chain's core protocol; this includes upgrading protocol contracts, setting core system parameters, and adding and removing other chain owners.
An Arbitrum chain's initial chain owner is set by the chain's creator when the chain is deployed.
The chain-ownership architecture is designed to give Arbitrum chain creators flexibility in deciding how upgrades to their chain occur.
Architecture
Chain ownership affordance is handled via Upgrade Executor contracts.
Each Arbitrum chain is deployed with two Upgrade Executors — one on the Arbitrum chain itself, and one on its parent chain. At deployment, the chain's critical affordances are given to the Upgrade Executor contracts.
Some examples:
- The parent chain's core protocol contracts are upgradeable proxies that are controlled by a proxy admin; the proxy admin is owned by the Upgrade Executor on the parent chain.
- The core Rollup contract's admin role is given to the Upgrade Executor on the parent chain.
- The affordance to call setters on the
ArbOwnerprecompile—which allows for setting system gas parameters and scheduling ArbOS upgrades (among other things)—is given to the Upgrade Executor on the Arbitrum chain.
Calls to an Upgrade Executor can only be made by chain owners; e.g., entities granted the EXECUTOR_ROLE affordance on the Upgrade Executor. Upgrade Executors also have the ADMIN_ROLE affordance granted to themselves, which lets chain owners add or remove chain owners.
With this architecture, the Upgrade Executor represents a single source of truth for affordances over critical upgradability of the chain.
The ArbOwner precompile reference can be found on the Precompiles reference page.
Upgrades
Upgrades occur via a chain owner initiating a call to an Upgrade Executor, which in turn calls some chain-owned contract.
Chain owners can either call UpgradeExecutor.executeCall, which will in turn call the target contract directly, or UpgradeExecutor.execute, which will delegate-call to an "action contract" and use its code to call the target contract.
Per-function permissions
The tables below list the privileged functions on the SequencerInbox, Rollup/RollupAdminLogic, and Bridge contracts, who is allowed to call each one, and the Arbitrum Chain SDK (formerly the Orbit SDK) helper that performs the call (where one exists). This answers the recurring question of which calls the Rollup owner makes and which must be routed through the Upgrade Executor.
How to read the Required caller column. The source of truth is the access-control modifier on each function in the nitro-contracts source:
- A function gated to the Rollup owner or admin — an
onlyRollupOwner-style modifier, or anyRollupAdminLogicadmin function — is, in a standard deployment, triggered by the owner through the Upgrade Executor, because the Upgrade Executor contract is the owner/admin of these contracts. See Upgrades above for the conceptual explanation of that flow. - A function callable directly by a dedicated role (for example the batch poster manager) does not need to go through the Upgrade Executor; that role's address calls it directly.
The Chain SDK's sequencerInboxPrepareTransactionRequest and rollupAdminLogicPrepareTransactionRequest helpers accept an upgradeExecutor argument and wrap the call in UpgradeExecutor.executeCall automatically, so the owner path is handled for you.
SequencerInbox
Access is enforced by the onlyRollupOwner and onlyRollupOwnerOrBatchPosterManager modifiers.
| Function | Required caller | Chain SDK helper |
|---|---|---|
setValidKeyset(bytes) | Rollup owner — via Upgrade Executor | setValidKeyset / buildSetValidKeyset |
invalidateKeysetHash(bytes32) | Rollup owner — via Upgrade Executor | buildInvalidateKeysetHash |
setIsBatchPoster(address, bool) | Rollup owner (via Upgrade Executor) or batch poster manager (directly) | buildSetIsBatchPoster (also buildEnableBatchPoster / buildDisableBatchPoster) |
setIsSequencer(address, bool) | Rollup owner (via Upgrade Executor) or batch poster manager (directly) | sequencerInboxPrepareTransactionRequest |
setMaxTimeVariation(MaxTimeVariation) | Rollup owner — via Upgrade Executor | buildSetMaxTimeVariation |
setBatchPosterManager(address) | Rollup owner — via Upgrade Executor | sequencerInboxPrepareTransactionRequest |
setFeeTokenPricer(IFeeTokenPricer) | Rollup owner — via Upgrade Executor | sequencerInboxPrepareTransactionRequest |
setBufferConfig(BufferConfig) | Rollup owner — via Upgrade Executor | sequencerInboxPrepareTransactionRequest |
updateRollupAddress() | Rollup owner (inline check) — via Upgrade Executor | sequencerInboxPrepareTransactionRequest |
setAllowListEnabled is not a SequencerInbox function, despite being commonly asked about in that context. It lives on the Inbox (the delayed inbox/AbsInbox)—see the Inbox row below.
Rollup/RollupAdminLogic
Every function on RollupAdminLogic is reachable only through the Rollup proxy's admin, which in a standard deployment is the Upgrade Executor. There is no per-function modifier—the proxy routes admin calls to this logic contract only when the caller is the admin—so all of these require the Upgrade Executor and none have a dedicated Chain SDK helper. They are all issued through the generic rollupAdminLogicPrepareTransactionRequest({ functionName, args, upgradeExecutor, rollup, account }) dispatcher.
| Function | Required caller | Chain SDK helper |
|---|---|---|
setValidator(address[], bool[]) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setOwner(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setConfirmPeriodBlocks(uint64) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setMinimumAssertionPeriod(uint256) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setValidatorAfkBlocks(uint64) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setValidatorWhitelistDisabled(bool) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
increaseBaseStake(uint256) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
decreaseBaseStake(uint256, uint64) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setWasmModuleRoot(bytes32) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setLoserStakeEscrow(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setInbox(IInboxBase) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setSequencerInbox(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setDelayedInbox(address, bool) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setOutbox(IOutbox) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
removeOldOutbox(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setAnyTrustFastConfirmer(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
setChallengeManager(address) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
pause() | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
resume() | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
forceRefundStaker(address[]) (only whenPaused) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
forceCreateAssertion(...) (only whenPaused) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
forceConfirmAssertion(...) (only whenPaused) | Rollup admin — via Upgrade Executor | rollupAdminLogicPrepareTransactionRequest |
The function set above reflects the BoLD-era RollupAdminLogic. On pre-BoLD chains some names differ (for example, setBaseStake in place of increaseBaseStake/decreaseBaseStake, and forceCreateNode/forceConfirmNode in place of the *Assertion variants). Always confirm against the nitro-contracts version your chain runs.
Bridge
Access is enforced by the onlyRollupOrOwner modifier—the caller must be the Rollup contract itself or the Rollup owner. The owner path runs through the Upgrade Executor. The Chain SDK has no dedicated Bridge helpers; these are configured during deployment or via the Rollup admin forwarders above.
| Function | Required caller | Chain SDK helper |
|---|---|---|
setSequencerInbox(address) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | — |
setDelayedInbox(address, bool) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | — |
setOutbox(address, bool) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | — |
setSequencerReportedSubMessageCount(uint256) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | — |
updateRollupAddress(IOwnable) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | — |
Inbox (delayed inbox)
setAllowListEnabled and the allowlist setter live on the Inbox (AbsInbox), not on SequencerInbox. Access is enforced by onlyRollupOrOwner.
| Function | Required caller | Chain SDK helper |
|---|---|---|
setAllowListEnabled(bool) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | buildSetAllowListEnabled |
setAllowList(address[], bool[]) | Rollup contract, or Rollup owner — owner path via Upgrade Executor | buildSetAllowList |
Ownership flexibility
A chain owner is simply an address; it is set by the Arbitrum chain's deployer and can represent any sort of governance scheme (i.e., it could be an EOA (as is set via the Chain SDK Rollup creation example), a Multisig, a governance token system, etc.)
The Arbitrum DAO governed chains, while not Arbitrum chains themselves, use a similar architecture and upgrade pattern as Arbitrum chains, with both a governance token and a Multisig (aka, the "Security Council") as chain owners. For more info and best practices on action contracts, see "DAO Governance Action Contracts".
The DAO-governed chains' Upgrade Executor contracts don't have the .executeCall method; only the .execute method.