Auth Surface
This page enumerates every privileged function in the protocol, who can call it, and which production address SHOULD hold the role. If you are setting up a new deployment or auditing an existing one, this is the canonical reference.
For the slashing-specific subset see Slashing.
Role Registry
Roles are defined in Base.sol and granted at initialization. The deploy script (script/FullDeploy.s.sol) hands every role to the deployer EOA at first, then transfers them to production multisigs and the timelock through _applyRoleHandoff.
Note: each contract defines its own role set. Tangle and MultiAssetDelegation (MAD) do NOT share role constants; the same name on both contracts is a different keccak slot.
| Role | Where | Suggested holder |
|---|---|---|
DEFAULT_ADMIN_ROLE | All AccessControl contracts | TangleTimelock |
ADMIN_ROLE | Tangle, MAD, and most peripherals | TangleTimelock |
PAUSER_ROLE | Tangle only | Operations multisig |
UPGRADER_ROLE | Tangle, MBSMRegistry, TangleToken, RewardVaults, InflationPool, TangleMetrics, ServiceFeeDistributor, StreamingPaymentManager | TangleTimelock |
SLASH_ADMIN_ROLE | Tangle only | Slashing oversight multisig |
MANAGER_ROLE | MBSMRegistry only | TangleTimelock |
ASSET_MANAGER_ROLE | MAD only | Operations multisig |
SLASHER_ROLE | MAD only | Tangle (via addSlasher) |
TANGLE_ROLE | MAD only | Tangle (via setTangle) |
MINTER_ROLE | TangleToken only | TangleTimelock |
MultiAssetDelegation does NOT define a UPGRADER_ROLE. Its _authorizeUpgrade is gated on ADMIN_ROLE. Its pause/unpause are gated on ADMIN_ROLE (no separate PAUSER_ROLE on MAD).
Tangle (src/Tangle.sol)
Tangle is the protocol entrypoint, composed of mixins under src/core/. Every state-changing path is gated by exactly one of:
- A specific OZ AccessControl role
- Service or blueprint ownership
- Permissionless gating (open to any caller)
Contract Administration
| Function | Caller | Source |
|---|---|---|
pause() | PAUSER_ROLE | Base.sol |
unpause() | PAUSER_ROLE | Base.sol |
_authorizeUpgrade(newImpl) | UPGRADER_ROLE | Base.sol |
setMBSMRegistry(addr) | ADMIN_ROLE (when not paused) | Base.sol |
setMetricsRecorder(addr) | ADMIN_ROLE | Base.sol |
setOperatorStatusRegistry(addr) | ADMIN_ROLE | Base.sol |
setServiceFeeDistributor(addr) | ADMIN_ROLE | Base.sol |
setPriceOracle(addr) | ADMIN_ROLE | Base.sol |
setTreasury(addr) | ADMIN_ROLE (NOT whenNotPaused) | Payments.sol |
setPaymentSplit(split) | ADMIN_ROLE (NOT whenNotPaused) | Payments.sol |
setTntToken(addr) | ADMIN_ROLE | Base.sol |
setRewardVaults(addr) | ADMIN_ROLE | Base.sol |
setMaxBlueprintsPerOperator(n) | ADMIN_ROLE | Base.sol |
setDefaultTntMinExposureBps(bps) | ADMIN_ROLE | Base.sol |
setTntPaymentDiscountBps(bps) | ADMIN_ROLE | Base.sol |
setMinServiceTtl(seconds) | ADMIN_ROLE | Base.sol |
setMaxServiceTtl(seconds) | ADMIN_ROLE | Base.sol |
setRequestExpiryGracePeriod(seconds) | ADMIN_ROLE | Base.sol |
setMaxQuoteAge(seconds) | ADMIN_ROLE | Base.sol |
Most admin setters in Base.sol are whenNotPaused. The two exceptions, both in Payments.sol, are setTreasury and setPaymentSplit. Paused governance can still rewire payment routing; this is intentional so a halted protocol can fix a known-bad treasury before resuming.
Slashing
| Function | Caller | Source |
|---|---|---|
proposeSlash(serviceId, op, slashBps, evidence) | Service owner, blueprint owner, or BSM-declared origin | Slashing.sol |
disputeSlash(slashId, reason) payable | Slashed operator (must post bond), or SLASH_ADMIN_ROLE (admin cannot self-dispute their own proposal) | Slashing.sol |
executeSlash(slashId) | Anyone (gated by isExecutable) | Slashing.sol |
executeSlashBatch(ids) | Anyone | Slashing.sol |
cancelSlash(slashId, reason) | SLASH_ADMIN_ROLE | Slashing.sol |
setSlashConfig(...) | ADMIN_ROLE | Slashing.sol |
See Slashing for the full lifecycle and runbooks.
Blueprints
| Function | Caller | Source |
|---|---|---|
createBlueprint(def) | Anyone (when not paused), nonReentrant | BlueprintsCreate.sol |
updateBlueprint(id, uri, hash) | Blueprint owner, nonReentrant | BlueprintsManage.sol |
transferBlueprint(id, newOwner) | Blueprint owner, nonReentrant | BlueprintsManage.sol |
deactivateBlueprint(id) | Blueprint owner, nonReentrant | BlueprintsManage.sol |
setJobEventRates(id, idxs, rates) | Blueprint owner, nonReentrant | BlueprintsManage.sol |
setBlueprintResourceRequirements(id, reqs) | Blueprint owner, nonReentrant | BlueprintsManage.sol |
Operators
| Function | Caller | Source |
|---|---|---|
registerOperator(...) | Anyone with valid stake (when not paused), nonReentrant | Operators.sol |
unregisterOperator(id) | The operator (no active services), nonReentrant | Operators.sol |
updateOperatorPreferences(id, key, rpc) | The operator | Operators.sol |
Services
| Function | Caller |
|---|---|
requestService(...) | Anyone (when not paused) |
approveService(ApprovalParams) | Operator listed in the request (request not expired) |
rejectService(requestId) | Operator listed in the request |
expireServiceRequest(requestId) | Anyone, after grace period (when not activated) |
terminateService(serviceId) | Service owner, nonReentrant |
forceRemoveOperator(serviceId, operator) | Blueprint manager only, nonReentrant |
approveService is a single entrypoint. Optional capabilities are opt-in via empty/zero fields on ApprovalParams: empty securityCommitments, zero blsPubkey, or empty teeCommitments each means “skip this capability.” See the slashing doc for the per-asset commitment contract.
Payments
| Function | Caller |
|---|---|
fundService(serviceId, amount) payable | Anyone (re-checks manager policy and TTL) |
withdrawRemainingEscrow(serviceId) | Service owner, after termination |
billSubscription(serviceId) | Anyone, gated by interval |
billSubscriptionBatch(ids) | Anyone |
claimReward(token) | The reward beneficiary |
claimRewards(tokens) | The reward beneficiary |
MultiAssetDelegation (src/staking/MultiAssetDelegation.sol)
Staking and delegation are a separate proxy with its own role registry. Functions are split across facets: StakingAdminFacet, StakingAssetsFacet, StakingOperatorsFacet, StakingSlashingFacet, StakingDelegationsFacet, StakingDepositsFacet, StakingViewsFacet.
Upgrade and Global Config (StakingAdminFacet)
| Function | Caller |
|---|---|
_authorizeUpgrade(newImpl) | ADMIN_ROLE (NOT a separate UPGRADER_ROLE; SHOULD be held by TangleTimelock) |
pause() / unpause() | ADMIN_ROLE (no separate PAUSER_ROLE on MAD) |
setTangle(addr) | ADMIN_ROLE — grants TANGLE_ROLE |
setOperatorBondToken(token) | ADMIN_ROLE — locked once any operator exists |
addSlasher(addr) / removeSlasher(addr) | ADMIN_ROLE — grants/revokes SLASHER_ROLE |
setOperatorCommission(bps) | ADMIN_ROLE (queues a 7-day timelocked commission change) |
executeCommissionChange() / cancelCommissionChange() | ADMIN_ROLE |
setDelays(...) | ADMIN_ROLE |
setRewardsManager(addr) / setServiceFeeDistributor(addr) | ADMIN_ROLE |
rescueTokens(token, to, amount) | DEFAULT_ADMIN_ROLE |
sweepDust(token, to) | ADMIN_ROLE |
resetPendingSlashCount(op, count) | ADMIN_ROLE (recovery only; default reverts unless overridden) |
Asset Configuration (StakingAssetsFacet)
| Function | Caller |
|---|---|
enableAsset(asset, config) | ASSET_MANAGER_ROLE |
enableAssetWithAdapter(asset, config, adapter) | ASSET_MANAGER_ROLE |
disableAsset(asset) | ASSET_MANAGER_ROLE |
registerAdapter(token, adapter) | ASSET_MANAGER_ROLE |
removeAdapter(token) | ASSET_MANAGER_ROLE |
setRequireAdapters(bool) | ASSET_MANAGER_ROLE |
startAdapterMigration / completeAdapterMigration / cancelAdapterMigration | ASSET_MANAGER_ROLE |
Slashing Entrypoints (StakingSlashingFacet)
| Function | Caller |
|---|---|
slashForBlueprint(...) | SLASHER_ROLE (Tangle is the canonical slasher) |
slashForService(...) | SLASHER_ROLE — per-asset commitment slashing |
slash(...) | SLASHER_ROLE — native consensus slash |
incrementPendingSlash(operator) | SLASHER_ROLE |
decrementPendingSlash(operator) | SLASHER_ROLE |
Operator Wiring From Tangle (StakingOperatorsFacet)
| Function | Caller |
|---|---|
addBlueprintForOperator(op, id) | TANGLE_ROLE |
removeBlueprintForOperator(op, id) | TANGLE_ROLE |
MBSMRegistry (src/MBSMRegistry.sol)
| Function | Caller |
|---|---|
addVersion(mbsm) | MANAGER_ROLE. Rejects EOA targets (mbsmAddress.code.length != 0); the caller can be any holder of the role. |
pinBlueprint(blueprintId, revision) | MANAGER_ROLE. Reverts if revision is currently in the deprecation grace window. |
unpinBlueprint(blueprintId) | MANAGER_ROLE |
initiateDeprecation(revision) | MANAGER_ROLE |
completeDeprecation(revision) | MANAGER_ROLE, after grace period |
queueEmergencyDeprecation(revision) | MANAGER_ROLE |
executeEmergencyDeprecation(revision) | MANAGER_ROLE, after EMERGENCY_DEPRECATION_DELAY (24h) |
cancelEmergencyDeprecation(revision) | MANAGER_ROLE |
setDeprecationGracePeriod(seconds) | MANAGER_ROLE |
_authorizeUpgrade(newImpl) | UPGRADER_ROLE |
MANAGER_ROLE SHOULD be the timelock. The 24h emergency deprecation delay is the floor on how fast a single role can disable an MBSM revision; combined with the timelock delay on top, a queued malicious deprecation has at least timelockDelay + 24h of observability before it lands.
Beacon stack
ValidatorPodManager
| Function | Caller |
|---|---|
createPod() | Anyone (one pod per address) |
recordBeaconChainEthBalanceUpdate(podOwner, sharesDelta) | Pod (the contract itself) |
L2SlashingReceiver
| Function | Caller |
|---|---|
receiveMessage(sourceChainId, sender, payload) | The configured cross-chain messenger only |
setAuthorizedSender(chainId, sender, authorized) | Owner. Revocation is immediate; authorization is timelocked (SENDER_ACTIVATION_DELAY) |
activateAuthorizedSender(chainId, sender) | Owner, after SENDER_ACTIVATION_DELAY (2 days) |
setMessenger(addr) | Owner. Bootstrap (current = address(0)) is immediate; subsequent swaps are queued |
activateMessenger() | Owner, after SENDER_ACTIVATION_DELAY |
setSlasher(addr) | Owner. Bootstrap (current = address(0)) is immediate; subsequent swaps are queued |
activateSlasher() | Owner, after SENDER_ACTIVATION_DELAY |
Two-step swap (v0.13.0). setMessenger and setSlasher no longer take effect on the
write that calls them. The owner queues the new address and the activation timestamp; after
SENDER_ACTIVATION_DELAY (2 days) elapses, the same owner calls activateMessenger() /
activateSlasher() to flip the live pointer. The bootstrap exemption (when the current
value is address(0)) lets deploy scripts wire the bridge without a 2-day deadlock; the
exemption is consumed the first time the slot is set. A typical rotation in production
looks like:
// t = 0: queue the new messenger
receiver.setMessenger(newMessenger);
// ... 2 days later (`SENDER_ACTIVATION_DELAY` has elapsed)
receiver.activateMessenger();Receiver enforcement (v0.13.0). receiveMessage now reverts if the L2 slasher returns
canSlash == false for the target operator (e.g. paused, unknown operator) or if
slashBps == 0, before consuming the bridge nonce. Previously the nonce was marked
processed first and a transient failure silently dropped the slash. With the new ordering
the bridge keeps the message available for retry until the slash actually applies. Relayers
must distinguish “already processed” (revert) from “still pending” (revert with
SlashingNotPossible) and re-deliver the latter.
L2SlashingConnector
| Function | Caller |
|---|---|
propagateBeaconSlashing(pod, newFactor) | slashingOracle OR owner (the onlySlashingOracle modifier accepts both) |
propagateBeaconSlashingToChain(pod, newFactor, destChain) | slashingOracle or owner |
batchPropagateBeaconSlashing(...) | slashingOracle or owner |
setMessenger(addr) | Owner |
setSlashingOracle(addr) | Owner |
setChainConfig(...) | Owner |
setDefaultDestinationChain(chainId) | Owner |
registerPodOperator(pod, operator) | Owner |
batchRegisterPodOperators(...) | Owner |
transferOwnership(newOwner) | Owner |
Governance
TangleTimelock
| Function | Caller |
|---|---|
schedule(...) | PROPOSER_ROLE (TangleGovernor) |
execute(...) | EXECUTOR_ROLE (TangleGovernor or open) |
cancel(...) | CANCELLER_ROLE |
updateDelay(newDelay) | The timelock itself only (onlySelf), bounded [1d, 30d] |
_authorizeUpgrade(newImpl) | The timelock itself (onlySelf) |
TangleGovernor
| Function | Caller |
|---|---|
propose(...) | Anyone with voting power above proposalThreshold |
castVote(...) | Token holders with voting power at the snapshotted timestamp |
_authorizeUpgrade(newImpl) | Through governance proposal only |
TangleToken
| Function | Caller |
|---|---|
mint(to, amount) | MINTER_ROLE (timelock); subject to MAX_SUPPLY |
_authorizeUpgrade(newImpl) | UPGRADER_ROLE |
clock() | view, returns block.timestamp (ERC-6372) |
Recommended Production Grant Matrix
| Role | Holder | Why |
|---|---|---|
DEFAULT_ADMIN_ROLE | TangleTimelock | Role grants and revokes flow through governance |
ADMIN_ROLE | TangleTimelock | Setters need a delay window for community exit |
UPGRADER_ROLE | TangleTimelock | Upgrades need a delay window |
PAUSER_ROLE | Operations multisig | Fast response to active incidents |
SLASH_ADMIN_ROLE | Slashing oversight multisig | Distinct from operations to prevent conflict of interest |
MANAGER_ROLE (MBSM) | TangleTimelock | MBSM version changes need a delay |
MINTER_ROLE (TangleToken) | TangleTimelock | Inflation flows through governance |
The deployer EOA MUST hold zero roles after deployment. Verify with the on-chain audit that _applyRoleHandoff produced. See Upgrade Discipline for the post-deploy checklist.