Skip to content

Functions

DISCLAIMER // NFA // DYOR

This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x643A45B5...0673C1 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-08-18

Reading This Document

Majeur ships as five contracts in one source file, and a DAO is all four of the first four at once: a Moloch clone holding governance state, plus Shares, Loot and Badges clones bound to it permanently at summon time. Summoner is the factory that mints the set. Functions below are grouped by role across all five, with the owning contract named in every selector row, because a member interacting with a DAO moves between them constantly — voting reads Shares, exiting burns both Shares and Loot, and posting to chat checks Badges.

Access in this framework has an unusual shape. onlyDAO on Moloch means msg.sender == address(this): the only way to reach those functions is a proposal that the DAO executes against itself, or a multicall the DAO makes to itself. onlyDAO is the whole access model; there is no role registry. On the three satellites, onlyDAO means msg.sender == DAO, the Moloch clone that created them. The practical consequence is that everything labelled Admin below requires a passing vote, a spent permit, or a spent allowance — there is no privileged address anywhere in the system.


Function Selectors

Moloch — 0x643A45B5...0673C1

SELECTOR FUNCTION SIGNATURE CATEGORY
0x31288f40 openProposal(uint256) User
0x56781388 castVote(uint256,uint8) User
0xbacbe2da cancelVote(uint256) User
0xe0a8f6f5 cancelProposal(uint256) User
0xddf0b009 queue(uint256) User
0xee5b2895 executeByVotes(uint8,address,uint256,bytes,bytes32) User
0x12708efd fundFutarchy(uint256,address,uint256) User
0xe89e80c2 resolveFutarchyNo(uint256) User
0x849609a9 cashOutFutarchy(uint256,uint256) User
0xa8841366 spendPermit(uint8,address,uint256,bytes,bytes32) User
0xd74fc428 spendAllowance(address,uint256) User
0x17f69222 buyShares(address,uint256,uint256) User
0x29f64d1a ragequit(address[],uint256,uint256) User
0xde8caa1e chat(string) User
0xac9650d8 multicall(bytes[]) User
0x095bcdb6 transfer(address,uint256,uint256) User
0xfe99049a transferFrom(address,address,uint256,uint256) User
0x558a7297 setOperator(address,bool) User
0x03dc0dc5 init(string,string,string,uint16,bool,address,address[],uint256[],(address,uint256,bytes)[]) Admin
0x12374b04 setPermit(uint8,address,uint256,bytes,bytes32,address,uint256) Admin
0xda46098c setAllowance(address,address,uint256) Admin
0x48b7fef2 setSale(address,uint256,uint256,bool,bool,bool) Admin
0x0527aab6 setQuorumBps(uint16) Admin
0xb83b4137 setMinYesVotesAbsolute(uint96) Admin
0xcf9d6ed6 setQuorumAbsolute(uint96) Admin
0xff391467 setProposalTTL(uint64) Admin
0x3821933a setTimelockDelay(uint64) Admin
0x174c480e setRagequittable(bool) Admin
0x80fe038f setTransfersLocked(bool,bool) Admin
0x80ca42a1 setProposalThreshold(uint96) Admin
0x56d3163d setRenderer(address) Admin
0x0890d80c setMetadata(string,string,string) Admin
0x92a023a9 setAutoFutarchy(uint256,uint256) Admin
0x8fb66f42 setFutarchyRewardToken(address) Admin
0x7ec924db bumpConfig() Admin
0x11c76fd9 batchCalls((address,uint256,bytes)[]) Admin
0x9e6ce8a5 onSharesChanged(address) Admin
0x997506ba proposalId(uint8,address,uint256,bytes,bytes32) View
0x3e4f49e6 state(uint256) View
0xc08cc02d getProposalCount() View
0x31933916 getMessageCount() View
0xe8a3d485 contractURI() View
0xc87b56dd tokenURI(uint256) View
0x00ad800c name(uint256) View
0x4e41a1fb symbol(uint256) View
0x00fdd58e balanceOf(address,uint256) View
0xbd85b039 totalSupply(uint256) View
0xb6363cf2 isOperator(address,address) View
0x03314efa shares() View
0x1a646240 badges() View
0x9b7b2ab0 loot() View
0x8ada6b0f renderer() View
0xd3ecebd7 executed(uint256) View
0x50a1676e createdAt(uint256) View
0x14c304fe snapshotBlock(uint256) View
0xab38c38a supplySnapshot(uint256) View
0x9ac498de queuedAt(uint256) View
0x1a32b237 tallies(uint256) View
0x9b644a23 proposalIds(uint256) View
0x083ce0cf proposerOf(uint256) View
0x43859632 hasVoted(uint256,address) View
0x2b0a999d voteWeight(uint256,address) View
0x51c60922 futarchy(uint256) View
0x15e9be3e receiptSupport(uint256) View
0x42114791 receiptProposal(uint256) View
0x33c40216 autoFutarchyParam() View
0xe005b63e autoFutarchyCap() View
0xf7c618c1 rewardToken() View
0xc6b9f06a sales(address) View
0xdd62ed3e allowance(address,address) View
0x0d80fefd messages(uint256) View
0xb58131b0 proposalThreshold() View
0x8ab8c683 minYesVotesAbsolute() View
0x6a34d91a quorumAbsolute() View
0x59a342d6 proposalTTL() View
0xeef09bad timelockDelay() View
0x79502c55 config() View
0xcd2ddd0c quorumBps() View
0x14a6d7de ragequittable() View
0x150b7a02 onERC721Received(address,address,uint256,bytes) View
0xf23a6e61 onERC1155Received(address,address,uint256,uint256,bytes) View

Shares — per-DAO clone of 0x71E9b38d...Acf600

SELECTOR FUNCTION SIGNATURE CATEGORY
0x5c19a95c delegate(address) User
0xb0fbd2c0 setSplitDelegation(address[],uint32[]) User
0x3649b58c clearSplitDelegation() User
0xa9059cbb transfer(address,uint256) User
0x23b872dd transferFrom(address,address,uint256) User
0x095ea7b3 approve(address,uint256) User
0x371aa158 init(address[],uint256[]) Admin
0x2806b0af mintFromMoloch(address,uint256) Admin
0x62a6677b burnFromMoloch(address,uint256) Admin
0x0eda3c55 setTransfersLocked(bool) Admin
0x9ab24eb0 getVotes(address) View
0x9556de73 getPastVotes(address,uint48) View
0xedb8ee5a getPastTotalSupply(uint48) View
0x587cde1e delegates(address) View
0x5acffdf1 splitDelegationOf(address) View
0x70a08231 balanceOf(address) View
0x18160ddd totalSupply() View
0xdd62ed3e allowance(address,address) View
0x06fdde03 name() View
0x95d89b41 symbol() View
0x313ce567 decimals() View
0x98fabd3a DAO() View
0x83f1211b transfersLocked() View

Loot — per-DAO clone of 0x6f1f2aF7...7bc6A5

SELECTOR FUNCTION SIGNATURE CATEGORY
0xa9059cbb transfer(address,uint256) User
0x23b872dd transferFrom(address,address,uint256) User
0x095ea7b3 approve(address,uint256) User
0xe1c7392a init() Admin
0x2806b0af mintFromMoloch(address,uint256) Admin
0x62a6677b burnFromMoloch(address,uint256) Admin
0x0eda3c55 setTransfersLocked(bool) Admin
0x70a08231 balanceOf(address) View
0x18160ddd totalSupply() View
0xdd62ed3e allowance(address,address) View
0x06fdde03 name() View
0x95d89b41 symbol() View
0x313ce567 decimals() View
0x98fabd3a DAO() View
0x83f1211b transfersLocked() View

Badges — per-DAO clone of 0x47C175Ce...eD96d5

SELECTOR FUNCTION SIGNATURE CATEGORY
0x23b872dd transferFrom(address,address,uint256) User
0xe1c7392a init() Admin
0xb90e71a1 mintSeat(address,uint16) Admin
0x7903397c burnSeat(uint16) Admin
0x9e6ce8a5 onSharesChanged(address) Admin
0xa997c66a getSeats() View
0x6352211e ownerOf(uint256) View
0xb9cd92c3 seatOf(address) View
0x70a08231 balanceOf(address) View
0xc87b56dd tokenURI(uint256) View
0x01ffc9a7 supportsInterface(bytes4) View
0x06fdde03 name() View
0x95d89b41 symbol() View
0x98fabd3a DAO() View

Summoner — 0x00000000...eE9138

SELECTOR FUNCTION SIGNATURE CATEGORY
0xfec53795 summon(string,string,string,uint16,bool,address,bytes32,address[],uint256[],(address,uint256,bytes)[]) User
0xccbbd260 daos(uint256) View
0x3a479fb8 getDAOCount() View

Summary

CATEGORY COUNT
Total Functions 136
User Functions 29
Admin Functions 31
View Functions 76
CONTRACT FUNCTIONS
Moloch 81
Shares 23
Loot 15
Badges 14
Summoner 3

Selector counts are per contract, so the same signature appears more than once across the set — transferFrom, balanceOf, name and DAO are each implemented separately by several contracts with different semantics. The transferFrom on Badges always reverts.


User Functions

Function: proposalId(uint8 op, address to, uint256 value, bytes data, bytes32 nonce)

Computes the identifier for an intent. This is a pure derivation with no state change, and it is the entry point to everything else — a proposal has no existence in this system beyond the number this function returns.

ATTRIBUTE VALUE
Selector 0x997506ba
Parameters op execution mode, to target address, value wei to send, data calldata, nonce caller-chosen salt
Access Public view
FLAG OBSERVATION
△ The chain never stores the preimage. openProposal and castVote accept only the resulting uint256, so target, value and calldata first appear on-chain in the execution transaction — after voting has closed
◇ address(this) is mixed in, so the same intent yields different ids on different DAOs and a proposal cannot be replayed across instances
◇ config is mixed in, which is how bumpConfig invalidates every pending proposal at once
◇ nonce is caller-chosen and lets the same action be proposed more than once, since a re-proposal of an identical intent would otherwise collide with the executed latch
function proposalId(uint8 op, address to, uint256 value, bytes calldata data, bytes32 nonce)
    public
    view
    returns (uint256)
{
    return _intentHashId(op, to, value, data, nonce);
}

function _intentHashId(uint8 op, address to, uint256 value, bytes calldata data, bytes32 nonce)
    internal
    view
    returns (uint256)
{
    return uint256(
        keccak256(abi.encode(address(this), op, to, value, keccak256(data), nonce, config))
    );
}

Function: openProposal(uint256 id)

Registers an intent hash as a live proposal and fixes the snapshot block that determines voting weight for its entire life. Calling it is optional — castVote invokes it automatically on the first vote — but calling it explicitly is what lets a proposer control when the electorate is measured.

ATTRIBUTE VALUE
Selector 0x31288f40
Parameters id the intent hash from proposalId
Access Public; gated by proposalThreshold when non-zero
FLAG OBSERVATION
△ The snapshot is block.number - 1 at the moment of the call, so whoever opens the proposal chooses the block at which the electorate is measured
△ The threshold is checked against getVotes(msg.sender) — current votes, not snapshot votes — so it is satisfiable by acquiring shares immediately before calling
☑ Re-entry is a no-op rather than a revert: the function returns early if snapshotBlock[id] is already set, so a redundant call cannot reset a live proposal's snapshot
◇ When autoFutarchyParam is non-zero, opening also earmarks a futarchy pool. The earmark is an accounting entry, not a transfer — no funds move at this point
CONDITION REQUIREMENT
Not already opened if (snapshotBlock[id] != 0) return;
Caller meets threshold require(_shares.getVotes(msg.sender) >= threshold) when proposalThreshold != 0
Share supply exists at snapshot if (supply == 0) revert TooEarly();
VARIABLE CHANGE
snapshotBlock[id] Set to block.number - 1
createdAt[id] Set to block.timestamp when previously zero, starting the TTL clock
supplySnapshot[id] Set to getPastTotalSupply(snap), the quorum denominator
proposalIds Appended, making the proposal enumerable
proposerOf[id] Set to msg.sender, the only address that may later cancel
futarchy[id] Earmarked when auto-futarchy is configured
CONDITION REVERT MESSAGE
Caller below proposalThreshold Unauthorized()
No share supply at the snapshot block TooEarly()
function openProposal(uint256 id) public {
    if (snapshotBlock[id] != 0) return;

    Shares _shares = shares;

    uint96 threshold = proposalThreshold;
    if (threshold != 0) {
        require(_shares.getVotes(msg.sender) >= threshold, Unauthorized());
    }

    uint256 supply;
    unchecked {
        uint48 snap = toUint48(block.number - 1);
        snapshotBlock[id] = snap;
        if (createdAt[id] == 0) createdAt[id] = uint64(block.timestamp);

        supply = _shares.getPastTotalSupply(snap);
        if (supply == 0) revert TooEarly();
        supplySnapshot[id] = supply;

        proposalIds.push(id);
        proposerOf[id] = msg.sender;

        emit Opened(id, snap, supply);
    }
    // auto-futarchy earmark omitted for brevity — see artifacts.md for full source
}

Function: castVote(uint256 id, uint8 support)

Records a vote and mints a transferable ERC-6909 receipt for the weight cast. Support is 0 for AGAINST, 1 for FOR, 2 for ABSTAIN. Integrators should confirm this ordering against the contract rather than assume it matches whatever convention their tooling uses.

ATTRIBUTE VALUE
Selector 0x56781388
Parameters id proposal hash, support 0 = AGAINST, 1 = FOR, 2 = ABSTAIN
Access Public; requires non-zero weight at the snapshot block
FLAG OBSERVATION
☑ Weight is read only from getPastVotes at the snapshot block, with no fallback to current balance, so shares acquired after the snapshot carry no weight
△ Auto-opens the proposal when unopened, which means the first voter also sets the snapshot block for everyone else
△ The receipt minted here is transferable. A voter who sends it away can no longer call cancelVote, because that function burns the receipt from the caller
◇ Weight is stored per voter in voteWeight, which is what allows cancelVote to subtract precisely what was added rather than recomputing
◇ The cast to uint96 is unchecked, unlike the equivalent conversion in Badges.onSharesChanged which is guarded
CONDITION REQUIREMENT
Not executed if (executed[id]) revert AlreadyExecuted();
Valid support value if (support > 2) revert NotOk();
Within TTL if (block.timestamp >= t0 + ttl) revert Expired(); when proposalTTL != 0
Has not voted if (hasVoted[id][msg.sender] != 0) revert AlreadyVoted();
Futarchy not resolved if (F.enabled && F.resolved) revert Unauthorized();
Non-zero weight at snapshot if (weight == 0) revert Unauthorized();
VARIABLE CHANGE
tallies[id] Weight added to the FOR, AGAINST or ABSTAIN field
hasVoted[id][msg.sender] Set to support + 1
voteWeight[id][msg.sender] Set to the weight credited
receiptSupport / receiptProposal Tagged on first use of each receipt id
balanceOf / totalSupply ERC-6909 receipt minted to the voter
CONDITION REVERT MESSAGE
Proposal already executed AlreadyExecuted()
Support above 2 NotOk()
Past TTL Expired()
Already voted AlreadyVoted()
Zero weight, or futarchy already resolved Unauthorized()
function castVote(uint256 id, uint8 support) public {
    if (executed[id]) revert AlreadyExecuted();
    if (support > 2) revert NotOk();

    if (createdAt[id] == 0) openProposal(id);

    uint64 t0 = createdAt[id];
    uint64 ttl = proposalTTL;

    if (ttl != 0) {
        if (t0 == 0) revert NotOk();
        if (block.timestamp >= t0 + ttl) revert Expired();
    }

    if (hasVoted[id][msg.sender] != 0) revert AlreadyVoted();

    FutarchyConfig storage F = futarchy[id];
    if (F.enabled && F.resolved) revert Unauthorized();

    uint48 snap = snapshotBlock[id];
    uint96 weight = uint96(shares.getPastVotes(msg.sender, snap));
    if (weight == 0) revert Unauthorized();

    Tally storage t = tallies[id];
    unchecked {
        if (support == 1) t.forVotes += weight;
        else if (support == 0) t.againstVotes += weight;
        else t.abstainVotes += weight;

        hasVoted[id][msg.sender] = support + 1;
        voteWeight[id][msg.sender] = weight;
    }

    uint256 rid = _receiptId(id, support);
    if (receiptProposal[rid] == 0) {
        receiptSupport[rid] = support;
        receiptProposal[rid] = id;
    }
    _mint6909(msg.sender, rid, weight);

    emit Voted(id, msg.sender, support, weight);
}

Function: state(uint256 id)

Derives a proposal's current state from stored tallies and timestamps. Nothing writes a state value to storage — every state is computed on read, which means a proposal transitions between states purely with the passage of time and without any transaction marking the change.

ATTRIBUTE VALUE
Selector 0x3e4f49e6
Parameters id proposal hash
Access Public view
FLAG OBSERVATION
☒ The queue branch and the expiry branch are alternatives. Once queuedAt is non-zero the TTL check becomes unreachable for that proposal, so a queued proposal never expires and stays executable indefinitely, subject to the live tally until it runs
△ The TTL check precedes gate evaluation, so a proposal that was voted down reports Expired rather than Defeated if its clock ran out first
△ executed is checked first and is also set by cancelProposal as a tombstone, so a cancelled proposal reports Executed
◇ Failing quorum returns Active, not Defeated — a proposal with insufficient turnout stays open until the TTL ends it
◇ Turnout for quorum includes abstentions, so abstaining helps a proposal clear its quorum gate
CONDITION REQUIREMENT
Pass condition, dynamic quorum totalCast >= mulDiv(quorumBps, supplySnapshot, 10000) when quorumBps != 0
Pass condition, absolute quorum totalCast >= quorumAbsolute when non-zero
Pass condition, YES floor forVotes >= minYesVotesAbsolute when non-zero
Pass condition, majority forVotes > againstVotes
function state(uint256 id) public view returns (ProposalState) {
    if (executed[id]) return ProposalState.Executed;

    uint64 t0 = createdAt[id];
    if (t0 == 0) return ProposalState.Unopened;
    uint64 queued = queuedAt[id];

    if (queued != 0) {
        uint64 delay = timelockDelay;
        if (delay != 0 && block.timestamp < queued + delay) return ProposalState.Queued;
    } else {
        uint64 ttl = proposalTTL;
        if (ttl != 0 && block.timestamp >= t0 + ttl) return ProposalState.Expired;
    }

    uint256 ts = supplySnapshot[id];
    if (ts == 0) return ProposalState.Active;

    Tally storage t = tallies[id];
    uint256 forVotes = t.forVotes;
    uint256 againstVotes = t.againstVotes;
    uint256 abstainVotes = t.abstainVotes;

    unchecked {
        uint256 totalCast = forVotes + againstVotes + abstainVotes;

        uint96 absQuorum = quorumAbsolute;
        if (absQuorum != 0 && totalCast < absQuorum) return ProposalState.Active;

        uint16 bps = quorumBps;
        if (bps != 0 && totalCast < mulDiv(uint256(bps), ts, 10000)) {
            return ProposalState.Active;
        }
    }

    uint96 minYes = minYesVotesAbsolute;
    if (minYes != 0 && forVotes < minYes) return ProposalState.Defeated;
    if (forVotes <= againstVotes) return ProposalState.Defeated;

    return ProposalState.Succeeded;
}

Function: queue(uint256 id)

Starts the timelock countdown on a proposal that has passed. On a DAO with no timelock configured this is a no-op, and offers no protection from TTL expiry; the proposal must be executed inside the TTL. On one with a timelock, a proposal must be queued — by this call, or implicitly by a first executeByVotes — before the delay starts, and must be queued inside the TTL.

ATTRIBUTE VALUE
Selector 0xddf0b009
Parameters id proposal hash
Access Public — any address may queue a passing proposal
FLAG OBSERVATION
☒ Queueing permanently removes the proposal from TTL expiry. The authorisation becomes indefinite rather than merely delayed, though state() still re-evaluates the live tally against current quorum settings
△ On a timelocked DAO, failing to queue inside the TTL window destroys a proposal that met every pass condition
◇ Idempotent — a second call while already queued does not restart the clock
◇ executeByVotes will queue implicitly on its first call, so the separate queue transaction is a convenience; queueing itself is not optional on a timelocked DAO
CONDITION REVERT MESSAGE
Proposal is not in Succeeded state NotOk()
function queue(uint256 id) public {
    if (state(id) != ProposalState.Succeeded) revert NotOk();
    if (timelockDelay == 0) return;
    if (queuedAt[id] == 0) {
        queuedAt[id] = uint64(block.timestamp);
        emit Queued(id, queuedAt[id]);
    }
}

Function: executeByVotes(uint8 op, address to, uint256 value, bytes data, bytes32 nonce)

Reveals the preimage of a passed proposal and performs the call. This is the only function in the framework that takes the full intent rather than its hash, and it recomputes the id from what it was given — so presenting a payload that does not hash to an approved proposal simply fails to match anything.

ATTRIBUTE VALUE
Selector 0xee5b2895
Parameters The full intent: op, to, value, data, nonce
Access Public payable, nonReentrant — no caller restriction whatsoever
FLAG OBSERVATION
☒ op != 0 performs a delegatecall, running the target's code against the DAO's own storage. A passing proposal can therefore rewrite any slot directly rather than being limited to the contract's own functions
△ Anyone may execute. Combined with queued proposals never expiring, a stale authorisation can be fired by any party at a moment of their choosing
△ The first call on a timelocked DAO queues and returns (true, "") without executing. The returned boolean does not distinguish queueing from execution
☑ The executed latch is set before the external call, and the whole function is nonReentrant via transient storage, so a re-entrant execution of the same proposal is blocked twice over
◇ A failed inner call reverts the whole transaction through _execute, so a proposal cannot be consumed by a failing execution
CONDITION REQUIREMENT
Not already executed if (executed[id]) revert AlreadyExecuted();
State is Succeeded or Queued if (st != Succeeded && st != Queued) revert NotOk();
Timelock elapsed if (block.timestamp < untilWhen) revert Timelocked(untilWhen);
STEP ACTION
1 Recompute the intent hash from the supplied parameters
2 Reject if already executed or not in an executable state
3 On a timelocked DAO, queue and return early if not yet queued; otherwise require the delay to have elapsed
4 Set the executed latch
5 Perform the call or delegatecall, reverting the transaction if it fails
6 Resolve any futarchy pool in favour of the YES side
CONDITION REVERT MESSAGE
Already executed, or preimage does not match an approved id AlreadyExecuted() or NotOk()
Not Succeeded or Queued NotOk()
Timelock has not elapsed Timelocked(untilWhen)
Inner call reverts NotOk()
function executeByVotes(
    uint8 op, // 0 = call, 1 = delegatecall
    address to,
    uint256 value,
    bytes calldata data,
    bytes32 nonce
) public payable nonReentrant returns (bool ok, bytes memory retData) {
    uint256 id = _intentHashId(op, to, value, data, nonce);

    if (executed[id]) revert AlreadyExecuted();

    ProposalState st = state(id);

    if (st != ProposalState.Succeeded && st != ProposalState.Queued) revert NotOk();

    if (timelockDelay != 0) {
        if (queuedAt[id] == 0) {
            queuedAt[id] = uint64(block.timestamp);
            emit Queued(id, queuedAt[id]);
            return (true, "");
        }
        uint64 untilWhen = queuedAt[id] + timelockDelay;
        if (block.timestamp < untilWhen) revert Timelocked(untilWhen);
    }

    executed[id] = true;

    (ok, retData) = _execute(op, to, value, data);
    _resolveFutarchyYes(id);
    emit Executed(id, msg.sender, op, to, value);
}

function _execute(uint8 op, address to, uint256 value, bytes calldata data)
    internal
    returns (bool ok, bytes memory retData)
{
    if (op == 0) {
        (ok, retData) = to.call{value: value}(data);
    } else {
        (ok, retData) = to.delegatecall(data);
    }
    if (!ok) revert NotOk();
}
flowchart TD
    A["executeByVotes(op, to, value, data, nonce)"] --> B["id = keccak(this, op, to, value, keccak(data), nonce, config)"]
    B --> C{"executed[id]?"}
    C -->|yes| R1["revert AlreadyExecuted"]
    C -->|no| D{"state(id)"}
    D -->|"Defeated / Expired / Active / Unopened"| R2["revert NotOk"]
    D -->|"Succeeded or Queued"| E{"timelockDelay != 0?"}
    E -->|no| H["set executed latch"]
    E -->|yes| F{"queuedAt == 0?"}
    F -->|yes| G["queue it, return (true, '') — NOT executed"]
    F -->|no| I{"delay elapsed?"}
    I -->|no| R3["revert Timelocked"]
    I -->|yes| H
    H --> J{"op == 0?"}
    J -->|yes| K["to.call{value}(data)"]
    J -->|no| L["to.delegatecall(data) — DAO storage context"]
    K --> M["resolve futarchy YES, emit Executed"]
    L --> M

    style L fill:#ffe1e1
    style G fill:#fff4e1

Function: cancelVote(uint256 id) / cancelProposal(uint256 id)

Two distinct retractions. cancelVote withdraws the caller's own vote while a proposal is still active, burning their receipt and subtracting their weight. cancelProposal withdraws the whole proposal, and is available only to the proposer and only before anyone has voted.

ATTRIBUTE VALUE
Selector 0xbacbe2da / 0xe0a8f6f5
Parameters id proposal hash
Access cancelVote — any prior voter. cancelProposal — proposerOf[id] only
FLAG OBSERVATION
☑ cancelVote subtracts the stored voteWeight rather than re-reading the snapshot, so the tally cannot drift even if checkpoint history changes shape
△ cancelVote burns the receipt from the caller, so a voter who transferred their receipt away cannot retract their vote
☑ cancelProposal requires a completely untouched proposal — zero votes across all three tallies and no futarchy pool — so it cannot be used to kill a contested proposal
◇ cancelProposal tombstones by setting executed[id] = true, which permanently burns that intent hash under the current config and makes state report Executed
CONDITION REVERT MESSAGE
Proposal not Active NotOk()
Caller has not voted (cancelVote) NotOk()
Caller is not the proposer (cancelProposal) Unauthorized()
Any vote already cast, or a futarchy pool exists (cancelProposal) NotOk()
function cancelProposal(uint256 id) public {
    require(msg.sender == proposerOf[id], Unauthorized());
    if (state(id) != ProposalState.Active) revert NotOk();
    if (queuedAt[id] != 0) revert NotOk();

    Tally memory t = tallies[id];
    if ((t.forVotes | t.againstVotes | t.abstainVotes) != 0) revert NotOk();

    FutarchyConfig memory F = futarchy[id];
    if (F.enabled && F.pool != 0) revert NotOk();
    executed[id] = true; // tombstone intent id
    emit ProposalCancelled(id, msg.sender);
}

Function: ragequit(address[] tokens, uint256 sharesToBurn, uint256 lootToBurn)

Burns the caller's shares and loot and pays out a proportional slice of each token in the supplied list. This is the exit right the framework is built around — a member who loses a vote can leave with their share of the treasury rather than be bound by the outcome.

ATTRIBUTE VALUE
Selector 0x29f64d1a
Parameters tokens ascending list of assets to claim, sharesToBurn, lootToBurn
Access Public, nonReentrant; requires ragequittable == true
FLAG OBSERVATION
☒ The token list is caller-supplied and unvalidated against the treasury. Omitting an asset forfeits the claim on it permanently, because the burn has already happened by the time the loop runs
△ Only ETH and ERC-20 assets can be claimed. Both live DAOs implement onERC721Received and onERC1155Received, so a treasury can hold NFTs that this function cannot distribute
☑ The denominator is read before the burns, so the payout ratio is the caller's share of the pre-burn supply and cannot be inflated by ordering
☑ Strictly ascending order is enforced, which prevents claiming the same token twice in one call
☑ Shares, loot, the DAO itself and the 1007 loot sentinel are all rejected as claim targets, so the exit cannot recursively drain the governance tokens
◇ Shares and loot are equivalent for this purpose — both burn against the same combined denominator, which is why loot dilutes treasury claims without diluting votes
CONDITION REQUIREMENT
Ragequit enabled if (!ragequittable) revert NotOk();
Non-empty token list require(tokens.length != 0, LengthMismatch());
Something to burn if (sharesToBurn == 0 && lootToBurn == 0) revert NotOk();
Tokens strictly ascending if (i != 0 && tk <= prev) revert NotOk();
function ragequit(address[] calldata tokens, uint256 sharesToBurn, uint256 lootToBurn)
    public
    nonReentrant
{
    uint256 amt = sharesToBurn + lootToBurn;
    unchecked {
        if (!ragequittable) revert NotOk();
        require(tokens.length != 0, LengthMismatch());
        if (sharesToBurn == 0 && lootToBurn == 0) revert NotOk();

        Shares _shares = shares;
        Loot _loot = loot;

        uint256 total = _shares.totalSupply() + _loot.totalSupply();
        if (sharesToBurn != 0) _shares.burnFromMoloch(msg.sender, sharesToBurn);
        if (lootToBurn != 0) _loot.burnFromMoloch(msg.sender, lootToBurn);

        address prev;
        address tk;
        uint256 pool;
        uint256 due;
        for (uint256 i; i != tokens.length; ++i) {
            tk = tokens[i];
            require(tk != address(shares), Unauthorized());
            require(tk != address(loot), Unauthorized());
            require(tk != address(this), Unauthorized());
            require(tk != address(1007), Unauthorized());

            if (i != 0 && tk <= prev) revert NotOk();
            prev = tk;

            pool = tk == address(0) ? address(this).balance : balanceOfThis(tk);
            due = mulDiv(pool, amt, total);
            if (due == 0) continue;

            _payout(tk, msg.sender, due);
        }
    }
}

Function: fundFutarchy(uint256 id, address token, uint256 amount) / resolveFutarchyNo(uint256 id) / cashOutFutarchy(uint256 id, uint256 amount)

A prediction market attached to a proposal. Anyone may fund a reward pool; voters hold receipts for whichever side they voted; when the proposal resolves, receipts on the winning side are burned for a pro-rata slice of the pool and receipts on the losing side are worth nothing. The effect is that voters on the winning side share the pool and voters on the losing side receive nothing.

ATTRIBUTE VALUE
Selector 0x12708efd / 0xe89e80c2 / 0x849609a9
Parameters id proposal hash; token reward asset; amount funding or receipts to burn
Access Public; cashOutFutarchy is nonReentrant
FLAG OBSERVATION
◇ Resolution is asymmetric. The YES side wins implicitly inside executeByVotes; the NO side must be resolved by an explicit resolveFutarchyNo call that anyone can make once the proposal is Defeated or Expired
△ Because a queued proposal never expires and cannot be defeated after its TTL, a funded pool on such a proposal can be left unresolvable in practice until someone executes it
△ The reward token may be address(this) or address(1007), sentinels meaning freshly minted shares or loot. A pool denominated that way pays winners by dilution rather than from the treasury
☑ The reward token is pinned on first funding and every later contribution must match, so a pool cannot be split across assets
☑ _finalizeFutarchy computes payoutPerUnit once at resolution against the winning side's total supply, so later receipt transfers cannot change anyone else's payout
◇ Payout is floor-rounded via mulDiv, so dust remains in the contract rather than over-paying the last claimant
CONDITION REQUIREMENT
Reward token is permitted Must be ETH, address(this), address(1007), shares or loot
Not already resolved if (F.resolved) revert NotOk();
ETH funding matches value if (msg.value != amount) revert NotOk();
Minted-token funding is self-called if (msg.sender != address(this)) revert Unauthorized();
Resolution state (resolveFutarchyNo) if (st != Defeated && st != Expired) revert NotOk();
function cashOutFutarchy(uint256 id, uint256 amount)
    public
    nonReentrant
    returns (uint256 payout)
{
    FutarchyConfig storage F = futarchy[id];
    if (!F.enabled || !F.resolved) revert NotOk();

    uint8 winner = F.winner; // 1 or 0
    uint256 rid = _receiptId(id, winner);

    _burn6909(msg.sender, rid, amount);

    payout = mulDiv(amount, F.payoutPerUnit, 1e18);
    if (payout == 0) {
        emit FutarchyClaimed(id, msg.sender, amount, 0);
        return 0;
    }

    _payout(F.rewardToken, msg.sender, payout);
    emit FutarchyClaimed(id, msg.sender, amount, payout);
}

Function: spendPermit(uint8 op, address to, uint256 value, bytes data, bytes32 nonce)

Executes a pre-authorised intent by burning one permit token, bypassing the vote entirely. Where executeByVotes asks "did this proposal pass", spendPermit asks only "does the caller hold a permit for exactly this action".

ATTRIBUTE VALUE
Selector 0xa8841366
Parameters The full intent, identical in shape to executeByVotes
Access Public payable, nonReentrant; requires a permit balance for the derived id
FLAG OBSERVATION
☒ No quorum, no timelock and no expiry apply. A permit granted once is a standing right until its count is exhausted or a later proposal reduces it
☒ The same op semantics apply, so a permit can authorise repeated delegatecall into DAO storage
△ An unspent permit is opaque by default — the intent preimage first appears only when it is spent, so unless the DAO republishes the parameters elsewhere, a granted-but-unused permit cannot be characterised by an outside observer
☑ Permit receipts are soulbound. transfer and transferFrom revert with SBT() for any id flagged in isPermitReceipt, so a permit cannot move through those transfer paths
◇ The permit must have been installed by setPermit, which is onlyDAO and therefore required a passing proposal or a summon-time init call
CONDITION REVERT MESSAGE
Id is not a registered permit Unauthorized()
Caller holds no permit token Arithmetic underflow in _burn6909
Inner call reverts NotOk()
function spendPermit(uint8 op, address to, uint256 value, bytes calldata data, bytes32 nonce)
    public
    payable
    nonReentrant
    returns (bool ok, bytes memory retData)
{
    uint256 tokenId = _intentHashId(op, to, value, data, nonce);
    require(isPermitReceipt[tokenId], Unauthorized());

    executed[tokenId] = true;

    _burn6909(msg.sender, tokenId, 1);

    (ok, retData) = _execute(op, to, value, data);

    if (futarchy[tokenId].enabled) _resolveFutarchyYes(tokenId);
    emit PermitSpent(tokenId, msg.sender, op, to, value);
}

Function: spendAllowance(address token, uint256 amount) / buyShares(address payToken, uint256 shareAmount, uint256 maxPay)

Two ways value leaves or enters the DAO without a per-transaction vote. spendAllowance lets a party the DAO has funded pull against a standing balance. buyShares lets anyone purchase newly minted or treasury-held shares or loot at a governance-set price.

ATTRIBUTE VALUE
Selector 0xd74fc428 / 0x17f69222
Parameters token/amount; or payToken, shareAmount, maxPay
Access Public, both nonReentrant; buyShares is payable
FLAG OBSERVATION
△ An allowance has no deadline and no rate limit. It is a standing claim the holder draws on whenever they choose
△ An allowance denominated in address(this) or address(1007) mints fresh shares or loot through _payout rather than transferring an existing balance, making it an open-ended dilution right
△ buyShares treats maxPay == 0 as "no maximum", which is also the natural default for an unset field. A buyer passing zero accepts whatever price is in storage when the transaction lands
☑ buyShares follows checks-effects-interactions — the sale cap is decremented before funds are pulled or tokens issued
☑ Excess ETH above cost is refunded on the ETH path; the ERC-20 path rejects any attached ETH outright
◇ cost = shareAmount * price performs no decimal scaling, so pricePerShare is denominated per whole share unit and callers must account for the 18 decimals themselves
function spendAllowance(address token, uint256 amount) public nonReentrant {
    allowance[token][msg.sender] -= amount;
    _payout(token, msg.sender, amount);
}

function _payout(address token, address to, uint256 amount) internal {
    if (amount == 0) return;
    if (token == address(0)) {
        safeTransferETH(to, amount);
    } else if (token == address(this)) {
        shares.mintFromMoloch(to, amount);
    } else if (token == address(1007)) {
        loot.mintFromMoloch(to, amount);
    } else {
        safeTransfer(token, to, amount);
    }
}

Function: chat(string message) / multicall(bytes[] data)

chat appends a message to an on-chain log, gated on holding a badge. multicall batches several calls to the DAO itself in one transaction, and is the standard way a single proposal performs several configuration changes.

ATTRIBUTE VALUE
Selector 0xde8caa1e / 0xac9650d8
Parameters message text; or data array of encoded calls
Access chat — badge holders only. multicall — public
FLAG OBSERVATION
◇ Chat eligibility is badge possession, and badges track the seat board rather than share balance directly, so who may speak is decided by the seat allocation mechanics rather than by stake
◇ multicall uses delegatecall to address(this), which preserves msg.sender. It therefore grants no privilege by itself — onlyDAO still requires the caller to be the DAO
☑ The combination is what makes a single proposal able to change many settings: the DAO proposes a call to itself invoking multicall, and each inner call then sees msg.sender == address(this)
◇ Message text is stored in contract storage rather than emitted only as an event, so the log is readable by messages(uint256) without an archive node
function chat(string calldata message) public payable {
    unchecked {
        require(badges.balanceOf(msg.sender) != 0, Unauthorized());
        messages.push(message);
        emit Message(msg.sender, messages.length - 1, message);
    }
}

function multicall(bytes[] calldata data) public returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i; i != data.length; ++i) {
        (bool success, bytes memory result) = address(this).delegatecall(data[i]);
        if (!success) {
            assembly ("memory-safe") {
                revert(add(result, 0x20), mload(result))
            }
        }
        results[i] = result;
    }
}

Function: delegate(address delegatee) / setSplitDelegation(address[] delegates, uint32[] bps) / clearSplitDelegation()

Voting-power routing on the Shares contract. A holder may point their whole balance at one delegate, or divide it across as many as four in basis points. Delegation is non-custodial throughout — shares never move, only the checkpointed vote totals attributed to each delegate.

ATTRIBUTE VALUE
Selector 0x5c19a95c / 0xb0fbd2c0 / 0x3649b58c
Parameters delegatee; or parallel arrays of delegates and basis points
Access Public — each holder controls only their own routing
FLAG OBSERVATION
☑ Splits are validated to sum to exactly 10,000 basis points, contain no zero addresses, and contain no duplicates, with a hard maximum of four entries
☑ Reallocation is path-independent: _repointVotesForHolder computes old and new target allocations from the current balance and moves only the difference, so repeated delegation changes cannot accumulate rounding drift
☑ _targetAlloc assigns the remainder to the final delegate rather than flooring every entry, so the allocated total always equals the holder's balance exactly
◇ Delegation defaults to self. _delegates storing zero is interpreted as self-delegation, and _autoSelfDelegate materialises that on first balance change, so a holder need take no action to have voting power
◇ Setting a single delegate collapses any existing split, and the split is deleted rather than merged
△ Changing delegation after a proposal's snapshot block has no effect on that proposal, since castVote reads only getPastVotes at the snapshot
CONDITION REVERT MESSAGE
Array length mismatch, zero, or above four SplitLen()
Basis points do not sum to 10,000 SplitSum()
A delegate address is zero SplitZero()
A delegate appears twice SplitDupe()
function setSplitDelegation(address[] calldata delegates_, uint32[] calldata bps_) public {
    address account = msg.sender;
    uint256 n = delegates_.length;
    require(n == bps_.length && n > 0 && n <= MAX_SPLITS, SplitLen());

    (address[] memory oldD, uint32[] memory oldB) = _currentDistribution(account);

    uint256 sum;
    for (uint256 i; i != n; ++i) {
        address d = delegates_[i];
        require(d != address(0), SplitZero());
        uint32 b = bps_[i];
        sum += b;

        for (uint256 j = i + 1; j != n; ++j) {
            require(d != delegates_[j], SplitDupe());
        }
    }
    require(sum == BPS_DENOM, SplitSum());

    _autoSelfDelegate(account);

    delete _splits[account];
    for (uint256 i; i != n; ++i) {
        _splits[account].push(Split({delegate: delegates_[i], bps: bps_[i]}));
    }

    _repointVotesForHolder(account, oldD, oldB);

    emit WeightedDelegationSet(account, delegates_, bps_);
}

Function: summon(...)

Deploys a complete DAO — a Moloch clone plus its Shares, Loot and Badges clones — and runs an arbitrary list of setup calls. This is the only path to an initialised Majeur DAO — init requires msg.sender == SUMMONER — and the factory had produced 56 of them at analysis, 59 by 2026-09-26.

ATTRIBUTE VALUE
Selector 0xfec53795
Parameters Name, symbol, URI, quorum bps, ragequittable flag, renderer, salt, initial holders and shares, and an initCalls array
Access Public payable — permissionless
FLAG OBSERVATION
△ initCalls execute with the new DAO as msg.sender, which satisfies onlyDAO. The summoner can therefore set every governance parameter, install permits and allowances, and mint tokens before any electorate exists to object
☑ Summoning is permissionless and the resulting DAO has no owner, so the factory operator retains no authority over DAOs it did not summon
◇ The clone address is deterministic — create2 over keccak256(abi.encode(initHolders, initShares, salt)) — so a DAO's address can be computed before deployment
◇ quorumBps, ragequittable and renderer are applied only when non-zero, so passing zero leaves the framework default rather than setting zero
function summon(
    string calldata orgName,
    string calldata orgSymbol,
    string calldata orgURI,
    uint16 quorumBps,
    bool ragequittable,
    address renderer,
    bytes32 salt,
    address[] calldata initHolders,
    uint256[] calldata initShares,
    Call[] calldata initCalls
) public payable returns (Moloch dao) {
    bytes32 _salt = keccak256(abi.encode(initHolders, initShares, salt));
    Moloch _implementation = implementation;
    assembly ("memory-safe") {
        mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3)
        mstore(0x14, _implementation)
        mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73)
        dao := create2(callvalue(), 0x0e, 0x36, _salt)
        if iszero(dao) {
            mstore(0x00, 0x30116425)
            revert(0x1c, 0x04)
        }
        mstore(0x24, 0)
    }
    dao.init(
        orgName, orgSymbol, orgURI, quorumBps, ragequittable, renderer,
        initHolders, initShares, initCalls
    );
    daos.push(dao);
    emit NewDAO(msg.sender, dao);
}

Admin Functions

Everything in this section is reachable only through onlyDAO. On Moloch that means the caller must be the DAO itself — a passing proposal executing against its own address, a spent permit, or a summon-time init call. On Shares, Loot and Badges it means the caller must be the owning Moloch clone.

Function: setQuorumBps / setQuorumAbsolute / setMinYesVotesAbsolute / setProposalThreshold / setProposalTTL / setTimelockDelay / setRagequittable

The governance parameter surface. Each is a single-value setter with no timelock of its own beyond the one the proposal that calls it already passed through, and each takes effect immediately on the next state evaluation.

ATTRIBUTE VALUE
Selector 0x0527aab6, 0xcf9d6ed6, 0xb83b4137, 0x80ca42a1, 0xff391467, 0x3821933a, 0x174c480e
Parameters A single scalar each
Access onlyDAO
FLAG OBSERVATION
☒ Changes apply retroactively to proposals already in flight, because state() reads current configuration rather than values captured at snapshot time. Lowering quorum can pass a proposal that was failing; raising it can strand one that was passing
△ setRagequittable(false) removes the exit right the framework is designed around. It is a single passing proposal away on any DAO
☑ setQuorumBps is bounded to 10,000, so quorum cannot be set above total supply
◇ Only supplySnapshot and snapshotBlock are captured per proposal. Every other gate is evaluated live
function setQuorumBps(uint16 bps) public payable onlyDAO {
    if (bps > 10_000) revert NotOk();
    quorumBps = bps;
}

function setProposalTTL(uint64 s) public payable onlyDAO { proposalTTL = s; }
function setTimelockDelay(uint64 s) public payable onlyDAO { timelockDelay = s; }
function setRagequittable(bool on) public payable onlyDAO { ragequittable = on; }

Function: setPermit(...) / setAllowance(address spender, address token, uint256 amount) / setSale(...)

The three ways governance delegates future authority rather than exercising it directly. setPermit grants repeatable execution of one fixed intent, setAllowance grants a standing draw on a token, and setSale opens a share or loot sale at a fixed price.

ATTRIBUTE VALUE
Selector 0x12374b04 / 0xda46098c / 0x48b7fef2
Parameters Intent plus spender and count; spender, token and amount; or sale terms
Access onlyDAO
FLAG OBSERVATION
☒ None of the three carries an expiry. All persist until a later proposal changes them
△ setPermit sets the count absolutely rather than incrementally, minting or burning the difference against the spender's current balance, so revocation is setPermit(..., 0)
△ A sale with minting: true issues new shares, diluting existing holders at whatever price governance chose
◇ setSale requires a non-zero price, which prevents an accidental free mint through this path
◇ A permit's intent is revealed on-chain only when it is spent. An unspent permit is a stored hash whose scope can be established only by recomputing it from parameters published elsewhere
function setPermit(
    uint8 op, address to, uint256 value, bytes calldata data, bytes32 nonce,
    address spender, uint256 count
) public payable onlyDAO {
    uint256 tokenId = _intentHashId(op, to, value, data, nonce);
    isPermitReceipt[tokenId] = true;
    uint256 bal = balanceOf[spender][tokenId];
    uint256 diff;

    unchecked {
        if (count > bal) {
            diff = count - bal;
            _mint6909(spender, tokenId, diff);
        } else if (count < bal) {
            diff = bal - count;
            _burn6909(spender, tokenId, diff);
        }
    }

    emit PermitSet(spender, tokenId, count);
}

Function: bumpConfig() / batchCalls(Call[] calls) / setMetadata / setRenderer / setTransfersLocked / setAutoFutarchy / setFutarchyRewardToken

The remaining governance surface: invalidating pending proposals, batching external calls, and configuring presentation, transferability and futarchy defaults.

ATTRIBUTE VALUE
Selector 0x7ec924db, 0x11c76fd9, 0x0890d80c, 0x56d3163d, 0x80fe038f, 0x92a023a9, 0x8fb66f42
Parameters Varies
Access onlyDAO
FLAG OBSERVATION
☒ bumpConfig invalidates every pending proposal at once by changing the salt every id is derived from. It is the only revocation mechanism for approved-but-unexecuted proposals, and it is indiscriminate
△ setTransfersLocked can freeze shares and loot for everyone except transfers to and from the DAO, which converts a liquid position into a locked one by vote
△ setRenderer accepts any address without validation, including one with no code, which would make tokenURI and contractURI revert
◇ batchCalls performs external calls and requires each to succeed, so one failure reverts the batch
◇ Both live DAOs read config == 0, so neither has ever bumped
function bumpConfig() public payable onlyDAO {
    unchecked {
        ++config;
    }
}

function batchCalls(Call[] calldata calls) public payable onlyDAO {
    for (uint256 i; i != calls.length; ++i) {
        (bool ok,) = calls[i].target.call{value: calls[i].value}(calls[i].data);
        require(ok, NotOk());
    }
}

Function: mintFromMoloch / burnFromMoloch (Shares and Loot) / mintSeat / burnSeat / onSharesChanged (Badges)

The satellite mutation surface. Shares and Loot expose mint and burn to their owning DAO only. Badges exposes seat management to the DAO, and onSharesChanged is the hook that keeps the seat board synchronised with share balances.

ATTRIBUTE VALUE
Selector 0x2806b0af / 0x62a6677b / 0xb90e71a1 / 0x7903397c / 0x9e6ce8a5
Parameters Recipient and amount, or seat index
Access onlyDAO on each satellite
FLAG OBSERVATION
☑ The DAO binding is written once in init behind a zero-address check and has no setter, so a satellite cannot be re-pointed at a different governor
☑ Shares.mintFromMoloch calls _autoSelfDelegate before applying the voting delta, so newly minted shares carry voting power without the recipient taking any action
△ Badges.onSharesChanged grants any free seat without comparing balances, and only applies the cutline once all 256 are taken. The board is therefore not the top 256 holders
◇ Badge eviction is sticky — a seated holder keeps their seat through balance decreases until a newcomer strictly exceeds the current minimum, or until their share balance reaches zero, which releases the seat
◇ Badges.onSharesChanged guards its uint96 conversion with an explicit Overflow() check, which the equivalent conversion in Moloch.castVote does not
function mintFromMoloch(address to, uint256 amount) public payable onlyDAO {
    _mint(to, amount);
    _autoSelfDelegate(to);
    _afterVotingBalanceChange(to, int256(amount));
}

function onSharesChanged(address a) public payable onlyDAO {
    unchecked {
        Shares _shares = Moloch(DAO).shares();

        uint256 bal256 = _shares.balanceOf(a);
        require(bal256 <= type(uint96).max, Overflow());
        uint96 bal = uint96(bal256);
        // seat insertion, update, or eviction — full body in the verified source on Etherscan
    }
}

Function: init(...) (Moloch, Shares, Loot, Badges)

One-time initialisation on each clone. Moloch.init is callable only by the SUMMONER immutable; the satellites are callable only while their DAO field is still zero.

ATTRIBUTE VALUE
Selector 0x03dc0dc5 (Moloch), 0x371aa158 (Shares), 0xe1c7392a (Loot and Badges)
Parameters Varies by contract
Access SUMMONER for Moloch; first caller for the satellites, which in practice is always the Moloch clone within the same transaction
FLAG OBSERVATION
☑ Each satellite guards with require(DAO == address(0)), so the require(DAO == address(0)) guard blocks re-initialisation after the summon transaction
☑ Moloch.init requires msg.sender == SUMMONER, and that immutable is baked into the implementation bytecode, so no address other than the factory can initialise any clone
△ The initCalls loop runs arbitrary calls with the DAO as sender before any electorate exists
◇ Satellite clone addresses are derived with bytes32(bytes20(address(this))) as salt, making them deterministic from the DAO address
function init(
    string calldata orgName, string calldata orgSymbol, string calldata orgURI,
    uint16 _quorumBps, bool _ragequittable, address _renderer,
    address[] calldata initHolders, uint256[] calldata initShares, Call[] calldata initCalls
) public payable {
    require(msg.sender == SUMMONER, Unauthorized());
    require(initHolders.length == initShares.length, LengthMismatch());
    // metadata and flags applied when non-zero
    bytes32 _salt = bytes32(bytes20(address(this)));

    badges = Badges(_badges = _init(badgesImpl, _salt));
    Badges(_badges).init();
    shares = Shares(_shares = _init(sharesImpl, _salt));
    Shares(_shares).init(initHolders, initShares);
    loot = Loot(_loot = _init(lootImpl, _salt));
    Loot(_loot).init();

    for (uint256 i; i != initCalls.length; ++i) {
        (bool ok,) = initCalls[i].target.call{value: initCalls[i].value}(initCalls[i].data);
        require(ok, NotOk());
    }
}

View Functions

Function: getVotes(address) / getPastVotes(address, uint48) / getPastTotalSupply(uint48)

The checkpoint reads that voting depends on. getVotes returns current delegated weight; the two past-lookups binary-search the checkpoint arrays for a historical block.

ATTRIBUTE VALUE
Selector 0x9ab24eb0 / 0x9556de73 / 0xedb8ee5a
Parameters Account and block number
Access Public view on Shares
FLAG OBSERVATION
☑ Both past-lookups reject blockNumber >= block.number with BadBlock(), which prevents querying a block whose checkpoints are not yet final
☑ _writeCheckpoint overwrites rather than appends when a checkpoint already exists for the current block, so within-block changes collapse to a single final value and cannot be read at an intermediate state
◇ getVotes reads the last checkpoint directly rather than searching, so the proposal threshold check in openProposal is a current-state read
◇ Loot has no equivalent — it carries no checkpoints and contributes nothing to voting weight or to getPastTotalSupply
function getPastVotes(address account, uint48 blockNumber) public view returns (uint256) {
    if (blockNumber >= block.number) revert BadBlock();
    return _checkpointsLookup(_checkpoints[account], blockNumber);
}

function getPastTotalSupply(uint48 blockNumber) public view returns (uint256) {
    if (blockNumber >= block.number) revert BadBlock();
    return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
}

Function: getSeats() / ownerOf(uint256) / seatOf(address) / supportsInterface(bytes4)

Badge board reads. getSeats walks the occupied bitmap and returns every filled seat with its cached holder and balance.

ATTRIBUTE VALUE
Selector 0xa997c66a / 0x6352211e / 0xb9cd92c3 / 0x01ffc9a7
Parameters Seat id or holder address
Access Public view on Badges
FLAG OBSERVATION
☑ getSeats sizes its output by popcount before filling it, so it returns exactly the occupied seats with no empty padding
△ supportsInterface advertises ERC-721 and ERC-721 Metadata but the collection is soulbound — transferFrom always reverts with SBT(). It does not advertise ERC-5192, so an indexer classifying it from interface ids alone would render transfer controls that cannot work
◇ The seat balance stored in seats is a cache written on onSharesChanged, not a live read, so it can lag the true share balance if the hook is not triggered
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
    return interfaceId == 0x01ffc9a7 // ERC165
        || interfaceId == 0x80ac58cd // ERC721
        || interfaceId == 0x5b5e139f; // ERC721Metadata
}

function transferFrom(address, address, uint256) public pure {
    revert SBT();
}

Remaining View Functions

The balance of the view surface is auto-generated accessors over public state, plus metadata passthroughs. None performs computation beyond a storage read or a delegation to the renderer.

FUNCTION CONTRACT RETURNS
getProposalCount() / proposalIds(uint256) Moloch Length of the per-DAO proposal registry, and the id at each index
getMessageCount() / messages(uint256) Moloch Length of the per-DAO chat log, and the message at each index
tallies(uint256) Moloch Packed FOR, AGAINST and ABSTAIN totals
executed / createdAt / snapshotBlock / supplySnapshot / queuedAt / proposerOf Moloch Per-proposal state used by state()
hasVoted(uint256,address) / voteWeight(uint256,address) Moloch Vote record stored as support + 1, and weight credited
futarchy(uint256) / receiptSupport / receiptProposal Moloch Prediction market configuration and receipt decoding
balanceOf(address,uint256) / totalSupply(uint256) / isOperator Moloch ERC-6909 receipt and permit accounting
shares() / loot() / badges() / renderer() Moloch Satellite and renderer addresses
quorumBps / quorumAbsolute / minYesVotesAbsolute / proposalThreshold / proposalTTL / timelockDelay / config / ragequittable Moloch Governance configuration
autoFutarchyParam() / autoFutarchyCap() / rewardToken() Moloch Futarchy defaults
sales(address) / allowance(address,address) Moloch Sale terms and standing token allowances
contractURI() / tokenURI(uint256) / name(uint256) / symbol(uint256) Moloch Metadata, delegated to the renderer when no override is stored
name() / symbol() / decimals() Shares, Loot, Badges Derived from the DAO's org name, with a suffix per contract
delegates(address) / splitDelegationOf(address) Shares Effective delegation, defaulting to 100% self
DAO() / transfersLocked() Shares, Loot, Badges Owning governor and transfer lock state
daos(uint256) / getDAOCount() Summoner Every DAO summoned — 56 to date
onERC721Received / onERC1155Received Moloch Acceptance hooks, allowing the treasury to receive NFTs it cannot distribute via ragequit