The DAO contract
A DAO in Aragon OSx is a single smart contract, one DAO contract that is the organization, rather than a collection of contracts or an off-chain entity with an on-chain treasury. Everything a DAO can do reduces to functionality this contract exposes or delegates.
That one contract wears several hats at once:
- The treasury. It holds the organization's assets (native coin and tokens) and receives deposits.
- The executor. It performs arbitrary on-chain actions on behalf of the organization, see Actions and execution. This is how a passed proposal actually does something.
- Its own permission database.
DAOinheritsPermissionManager, so the rules for who may do what live in the DAO contract's own storage. A DAO is self-authorizing. - A signer. It can validate signatures on the organization's behalf via EIP-1271.
- A discoverable, upgradeable identity. It implements EIP-4824 (
daoURI) for off-chain metadata, and is deployed behind a UUPS proxy so it can be upgraded.
That first cluster, hold assets, execute anything, own the rules for who may do what, is what a DAO is for: a durable on-chain organization. Upgradeability (the proxy) matters too, but it's how the organization endures, not what it's for; if proxies are new to you, read Proxy deployment first, then come back.
Everything else, governance, membership, asset management, is added by plugins. The DAO itself stays deliberately lean: it holds funds, executes actions, and answers "is this allowed?".
Why one contract holds everything
The DAO is also its own PermissionManager, which is what makes OSx cohere. Because of that, one authorization model governs the DAO's own admin functions, its plugins' functions, and even framework contracts. A plugin doesn't keep its own access-control list; it asks the DAO "does this caller have permission?" (see Authorizing against a DAO). There is exactly one source of truth for authority per organization, and it is the DAO.
Deployment and bootstrapping ROOT
A DAO has to start with someone holding ROOT: a brand-new DAO has an empty permission database, and someone must make the first grants. Yet the safe end state is the DAO holding ROOT over itself, so it self-governs and no outside key can override it. Bootstrapping is getting cleanly from the first to the second, and OSx does it for you in one atomic step (below).
DAO is deployed behind a UUPS proxy. The implementation's constructor calls _disableInitializers() (so only proxies are ever initialized, the standard OpenZeppelin upgradeable-safety pattern), and each proxy is set up through initialize:
function initialize(
bytes calldata _metadata,
address _initialOwner,
address _trustedForwarder,
string calldata daoURI_
) external;initialize grants ROOT_PERMISSION_ID to _initialOwner. ROOT is god-mode (see Permissions): whoever holds it can grant or revoke any permission. A freshly initialized DAO therefore has its initial owner in total control, which is fine as a bootstrap step and dangerous as an end state.
The intended lifecycle looks like: the initial owner sets the DAO up, transfers ROOT to the DAO itself (so the organization self-governs), and revokes its own ROOT. In practice you never do this by hand, the DAOFactory performs the whole dance atomically in one transaction. A DAO where an EOA still holds ROOT is a DAO that EOA fully controls.
Granting ROOT to the DAO over itself is what lets a DAO manage its own permissions through governance: a proposal can execute
grant/revokeactions on the DAO.
Where ROOT ends up
Once bootstrapped, ROOT on a DAO can rest in one of three end states, and the choice sets how mutable the organization is:
- The DAO itself (the usual case). The organization self-governs: every permission change, plugin install, update, or uninstall happens only through a passed proposal that executes
grant/revokeas the DAO. - A parent DAO. A DAO can grant ROOT to another DAO and revoke its own, making itself subordinate: the parent now controls its permission setup. This is the building block for sub-DAOs and hierarchical organizations.
- Nobody, on purpose. ROOT is an ordinary permission and
revokehas no floor, so a DAO can revoke ROOT from every holder, itself included. With no ROOT anywhere,grant,revoke,grantWithCondition, and theapply*batch functions become permanently uncallable (all areauth(ROOT_PERMISSION_ID), andisGrantedgives ROOT no bypass). The permission table is frozen forever: no new plugins, no updates or uninstalls, no rewiring, ever.
That last option is deliberate: revoking ROOT entirely is how you make a permission setup immutable. It freezes the structure, not activity, the DAO can still execute actions and its installed plugins keep working (those are gated by EXECUTE and their own permissions, not ROOT); it just can never change who may do what again. It is also irreversible: with no ROOT, nothing can ever grant ROOT back.
Permissions the DAO defines
Each of the DAO's own privileged functions is gated by a permission (via the auth modifier from PermissionManager):
| Permission | Gates |
|---|---|
EXECUTE_PERMISSION_ID | execute(), see Actions and execution. The most consequential permission in the protocol. |
UPGRADE_DAO_PERMISSION_ID | Upgrading the DAO's implementation (UUPS). |
SET_METADATA_PERMISSION_ID | setMetadata() and setDaoURI(). |
SET_TRUSTED_FORWARDER_PERMISSION_ID | Setting the ERC-2771 meta-transaction forwarder. |
REGISTER_STANDARD_CALLBACK_PERMISSION_ID | Registering new callback handlers (below). |
VALIDATE_SIGNATURE_PERMISSION_ID | Used inside signature validation, not a function gate. |
The first five can never be granted to the ANY_ADDR wildcard (the DAO overrides isPermissionRestrictedForAnyAddr to forbid it): letting "anyone" execute arbitrary calls or upgrade the contract would be catastrophic. VALIDATE_SIGNATURE_PERMISSION_ID is deliberately excluded from that restriction so it can back a generic signature validator. ROOT_PERMISSION_ID is always wildcard-restricted, with no override. See Permissions for the ANY_ADDR model.
Holding assets
deposit(token, amount, reference)is the tracked deposit path. Native coin (token == address(0)) requiresmsg.value == amount; an ERC-20 pulls viasafeTransferFromand requiresmsg.value == 0. EmitsDeposited, and is permissionless, anyone can fund a DAO.receive()handles plain native-coin transfers and just emitsNativeTokenDeposited. (It does no bookkeeping. Beware the 2300-gas stipend that.transfer/.sendsenders impose: such a transfer to the DAO can run out of gas inreceive; a sender using a normalcallis unaffected.)
Assets leave the DAO only through execute, which is permission-gated, so funding is open while spending is governed.
Adaptive token callbacks
To receive ERC-721 / ERC-1155 tokens a contract must answer callbacks like onERC721Received with a specific magic value. Rather than hard-coding these, the DAO mixes in a CallbackHandler: a registry mapping a callback selector to the magic value it must return. The ERC-721/1155 receiver callbacks are registered at initialize, and registerStandardCallback (gated by REGISTER_STANDARD_CALLBACK_PERMISSION_ID) lets a DAO support future token/callback standards without a contract upgrade. Unregistered callbacks revert UnknownCallback.
Metadata and discovery
setMetadata takes an opaque bytes blob (conventionally an IPFS CID) and only emits it as the MetadataSet event, it is not stored on-chain, so reading a DAO's current metadata means indexing that event. daoURI() implements EIP-4824 for standardized off-chain DAO discovery. See DAO metadata for the JSON shape and the daoURI-vs-blob distinction.
Upgrades across versions
_authorizeUpgrade is gated by UPGRADE_DAO_PERMISSION_ID. After an implementation upgrade, initializeFrom(previousVersion, initData) migrates storage; it refuses to upgrade across a major version (ProtocolVersionUpgradeNotSupported if the previous major isn't 1). This is also where the contract keeps ERC-165 interface IDs and storage layout stable across versions (removed fields become reserved slots like __removed0; new state goes in the storage __gap). See Protocol version.
Keep in mind
- ROOT left with an EOA is total control of the DAO. After any setup, confirm ROOT sits with the DAO itself, not the deployer, that's the line between a self-governing DAO and one somebody privately owns.
- Metadata is event-only. Read a DAO's current metadata by indexing the
MetadataSetevent; it isn't stored on-chain.
See also
- Permissions, the authorization model the DAO inherits.
- Actions and execution, how the DAO acts on the world.
- Plugins, how functionality is added.
- DAOFactory, how a DAO is actually deployed and bootstrapped.