Skip to content

Deploy your first DAO ​

Deploy a DAO with a Multisig plugin so that, from block one, only a passed multisig proposal can make it act and no external address retains control. In A hands-on tour of OSx, you made a bare DAO and held EXECUTE_PERMISSION_ID as an EOA. That suits a demo; this deployment gives execution authority to the organization's governance.

It's still one transaction. DAOFactory.createDao creates the DAO, installs the plugin through the PSP's temporary-ROOT window, and hands EXECUTE to the plugin, atomically.

What you need ​

The one-time Setup, with the multisig plugin's remapping enabled. This guide reads the DAOFactory and Multisig PluginRepo (MULTISIG_PLUGIN_REPO_ADDRESS) addresses, and installs a specific plugin version (a release/build, see release vs build).

Step 1, the skeleton ​

Fork the network and wire the two addresses. Everything else happens in one test:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Test} from "forge-std/Test.sol";
import {DAO} from "@aragon/osx/core/dao/DAO.sol";
import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol";
import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol";
import {PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol";
import {IPlugin} from "@aragon/osx/common/plugin/IPlugin.sol";
import {Multisig} from "@aragon/multisig-plugin/Multisig.sol"; // path follows your installed dependency

contract DeployWithMultisig is Test {
    DAOFactory factory = DAOFactory(vm.envAddress("DAO_FACTORY_ADDRESS"));
    PluginRepo multisigRepo = PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS"));

    function setUp() public {
        vm.createSelectFork(vm.envString("RPC_URL"));
    }
}

Step 2, describe the DAO and the plugin install ​

A DAO created with plugins takes a PluginSettings[]: each entry names a plugin version (its repo + a (release, build) tag) and the install data the plugin's setup expects. Multisig's MultisigSetup decodes its data as (address[] members, MultisigSettings, TargetConfig, bytes metadata):

solidity
function test_deployWithMultisig() public {
    // Who is on the multisig, and its rule (2-of-3, members only).
    address[] memory members = new address[](3);
    members[0] = address(0xA11CE);
    members[1] = address(0xB0B);
    members[2] = address(0xCa201);
    Multisig.MultisigSettings memory settings =
        Multisig.MultisigSettings({onlyListed: true, minApprovals: 2});

    // Where the plugin executes. target == address(0) is the sentinel for "my own DAO":
    // at runtime the plugin resolves it to TargetConfig(dao(), Call). This is what lets you
    // install in the same tx that creates the DAO, the DAO address doesn't exist yet here.
    IPlugin.TargetConfig memory targetConfig =
        IPlugin.TargetConfig({target: address(0), operation: IPlugin.Operation.Call});

    bytes memory installData = abi.encode(members, settings, targetConfig, bytes(""));

    // Point at the plugin version to install.
    PluginRepo.Tag memory versionTag = PluginRepo.Tag({release: 1, build: 3});

    DAOFactory.PluginSettings[] memory pluginSettings = new DAOFactory.PluginSettings[](1);
    pluginSettings[0] = DAOFactory.PluginSettings({
        pluginSetupRef: PluginSetupRef({versionTag: versionTag, pluginSetupRepo: multisigRepo}),
        data: installData
    });

    // ... createDao in Step 3 (same function)
}

The installData layout is dictated by the plugin's setup, not by OSx; every plugin publishes the ABI for its data in its build metadata. Get it wrong and prepareInstallation reverts on abi.decode.

Step 3, create the DAO with the plugin ​

Continue the same function. createDao returns the DAO and the installed plugin(s):

solidity
    DAOFactory.DAOSettings memory daoSettings = DAOFactory.DAOSettings({
        trustedForwarder: address(0),
        daoURI: "",
        subdomain: "",
        metadata: ""
    });

    (DAO dao, DAOFactory.InstalledPlugin[] memory installed) =
        factory.createDao(daoSettings, pluginSettings);

    address multisig = installed[0].plugin;

The factory ran the temporary-ROOT sequence: it prepared and applied the multisig setup, whose permission list granted the plugin EXECUTE_PERMISSION_ID on the DAO. Because you passed a plugin, the factory did not grant you EXECUTE.

Step 4, verify governance is in control ​

Confirm the plugin can make the DAO act and you cannot:

solidity
    bytes32 EXECUTE = dao.EXECUTE_PERMISSION_ID();
    assertTrue(dao.hasPermission(address(dao), multisig, EXECUTE, ""));       // the plugin can
    assertFalse(dao.hasPermission(address(dao), address(this), EXECUTE, "")); // you cannot
}

The DAO now acts only when a multisig proposal passes and calls dao.execute. No EOA holds execution authority, and the DAO holds ROOT over itself, so changing those permissions is also a governance decision.

What you just saw ​

  • Specify the plugin's version + install data, and the factory prepares and applies its installation, including the temporary ROOT grants.
  • The setup configures the permissions: here, EXECUTE goes to the plugin. See multisig's setup.
  • TargetConfig.target = address(0) means "my own DAO", which makes same-transaction installation possible.

Next ​