Back to blog
2026-05-188 min read

A DAO Treasury Stack: Multisig, Timelock, and Snapshot

The three pieces most DAO treasuries run on, why the delay matters, and a minimal timelock you can trace through end to end.

DAOGovernanceTreasuryTimelock

The Standard Stack

Most DAO treasuries run on three pieces that never touch each other directly:

  • Snapshot: off-chain voting. Weighted by token balance at a snapshot block. Costs nothing, binds nothing.
  • Timelock: the delay between a proposal being approved and its execution. Usually 24 to 72 hours.
  • Safe: the executor. A multisig that holds the funds and only moves them through the timelock.

The design separates who decides from who signs. Votes happen off-chain. The multisig signs what the timelock schedules. The timelock delays what the multisig wants.

Why the Delay Exists

The delay exists so that members can react. If a proposal is malicious or plainly wrong, the window is the time to exit or to change the guard. A treasury without a delay is a treasury where a single vote round can drain everything before anyone notices.

The delay is also a governance speed limit. It converts "we voted for this" into "we still agree with this after sleeping on it."

A Minimal Timelock

The real timelock used by many DAOs is OpenZeppelin's TimelockController. Its core loop is short:

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

contract MinimalTimelock {
  uint256 public minDelay;
  mapping(bytes32 => uint256) public scheduled;

  constructor(uint256 _minDelay) {
    minDelay = _minDelay;
  }

  function schedule(address target, bytes calldata data) external {
    bytes32 id = keccak256(abi.encode(target, data, block.timestamp));
    scheduled[id] = block.timestamp + minDelay;
  }

  function execute(address target, bytes calldata data) external {
    bytes32 id = keccak256(abi.encode(target, data, block.timestamp));
    uint256 readyAt = scheduled[id];
    require(readyAt != 0, "not scheduled");
    require(block.timestamp >= readyAt, "not ready");
    delete scheduled[id];
    (bool ok, ) = target.call(data);
    require(ok, "execution failed");
  }
}

Note what is missing: access control. In production the scheduling and execution functions are role-gated, and the timelock itself is usually a proposal target so governance can change its own parameters.

Wiring It Together

The flow for a real payout, from a script:

import { ethers } from "ethers";
import { Safe } from "@safe-global/protocol-kit";

// 1. Snapshot vote passes. The payout is now "approved".
// 2. The timelock schedules the transfer.
const timelock = new ethers.Contract(timelockAddress, timelockAbi, signer);
await timelock.schedule(
  treasuryAddress,
  timelock.interface.encodeFunctionData("executeTransfer", [recipient, amount])
);

// 3. After minDelay, the Safe signs the execution call.
const safe = await Safe.init({ provider, signer, safeAddress: treasuryAddress });
const tx = await safe.createTransaction({
  transactions: [{
    to: timelockAddress,
    data: timelock.interface.encodeFunctionData("execute", [
      timelockAddress,
      timelock.interface.encodeFunctionData("executeTransfer", [recipient, amount]),
    ]),
    value: "0",
  }],
});
const receipt = await safe.executeTransaction(tx);

Three signatures may be involved: the Safe threshold, plus whoever submits the timelock calls. That is fine. Friction is the product.

Failure Modes

  • Expired proposals: most timelocks expire after a window (for example 14 days). A proposal past its deadline must be rescheduled, which is another governance vote. Budget for this.
  • Guard changes: if the multisig can bypass the timelock, the delay is theater. The standard setup gives the timelock the execution role and nothing else.
  • Spam: anyone can schedule arbitrary calls in some configurations. Rate-limit scheduling and require a whitelisted target set for small DAOs.

What a Small DAO Should Actually Run

For a treasury under $1M: a 2-of-3 Safe, a 48-hour timelock, Snapshot voting with a simple token gate. That covers accidental and malicious transfers without hiring anyone. Fancy governance modules earn their complexity only when the treasury size justifies it.