Skip to content

Majeur Governance

RESEARCH CONTEXT

Operating companion to the Majeur contract analysis: constructing a proposal, verifying one somebody else published, voting, queueing, executing and exiting, with the encoding shown.

Contract behaviour described here is derived from the verified source of the framework; publishing conventions are what the sampled DAOs do, not what the contract requires. Where a live proposal is decoded, it is an already-executed one on zOrg, read from public chain data. No example on this page sends a transaction to a live instance; construction examples use $DAO and $TARGET placeholders for the reader to fill in.

⊙ generated by robots | curated by humans


Majeur is the Decentralized Autonomous Organization (DAO) framework behind zOrg, the governance body of the zFi stack. As of 2026-09-26 the factory at 0x00000000...eE9138 (etherscan) has summoned 59 instances, zOrg among them. Each is a Minimal Proxy clone of one implementation at 0x643A45B5...0673C1 (etherscan), with a separate treasury, token set and parameters. The framework has no admin role. Privileged functions are reachable only by the DAO calling itself: through a passed proposal, or through a permit that a passed proposal granted.

A Majeur proposal is an opaque uint256. The contract stores the hash of an intent, not the intent. A member who wants to know what a proposal does has to obtain its parameters from wherever the proposer published them, and then check that those parameters hash to the id on the ballot. Both directions — building the parameters and checking them — come down to Application Binary Interface (ABI) encoding and a keccak.

Where a live instance is cited, it is zOrg at 0x5E58BA0e...74053E (etherscan) or the collector DAO named FWC at 0xE7Aa6cA3...F49853 (etherscan). Their parameters are instance settings; another DAO's will differ.


Summary

  • A proposal is keccak256(abi.encode(dao, op, to, value, keccak256(data), nonce, config)). The five intent fields are never stored and never emitted before execution; they reach the chain only as the arguments of executeByVotes
  • Building one is three steps: ABI-encode the inner call with cast calldata, choose a nonce, hash the tuple. Every step is local. Changing a DAO's own settings is the same shape with to = $DAO
  • Reading one is the reverse: recompute the hash from the published parameters and compare; then decode data down to the function and arguments, recursively if it is a batchCalls or multicall; then check the op byte and the target's code
  • Passing a vote is not sufficient. The proposal time-to-live (TTL) from creation is the whole window to queue on a timelocked DAO, or to execute on one without; miss it and the proposal expires with its tally intact. See The two clocks
  • Voting weight is the delegated balance at the block before the proposal was opened. Shares acquired or delegated afterwards carry nothing for that vote
  • Ragequit burns first and pays second, and only for the token addresses the caller lists

Anatomy of a Proposal

The intent is five values. Their Solidity types matter because they are what you encode.

FIELD TYPE MEANING
op uint8 0 — the DAO performs to.call{value}(data). Any other value — the DAO performs to.delegatecall(data), running the target's code against the DAO's own storage
to address Target of the call. $DAO itself for configuration changes
value uint256 Amount of Ether (ETH), in Wei, sent with the call. Non-zero with empty data is a plain treasury payment. Ignored when op != 0, since a delegatecall forwards no ETH, but still part of the id
data bytes ABI-encoded call: four-byte selector followed by the arguments. Empty for a bare transfer
nonce bytes32 Caller-chosen salt. Carries no meaning to the contract; it exists so the same action can be proposed twice without colliding with the executed latch of the first

Two further inputs are mixed into the hash without being part of the intent: address(this), so an id is bound to one DAO, and config, a uint64 version counter that a proposal can increment through bumpConfig() to invalidate every pending proposal and permit at once. The full derivation:

uint256(keccak256(abi.encode(address(this), op, to, value, keccak256(data), nonce, config)))

openProposal, castVote, queue and state take only the resulting id. executeByVotes takes the five fields, recomputes the id, and refuses if the result is not a proposal in Succeeded or Queued state. The preimage therefore appears on-chain once, in the call to executeByVotes. Before that point the chain holds a hash, a tally and a proposer, and nothing that says what the proposal does.

A proposal with op != 0 is a Delegatecall: the target's bytecode runs with the DAO's storage and the DAO's balance, able to rewrite any slot including the satellite token addresses and the ragequittable flag. Neither zOrg nor FWC has executed with op != 0, but FWC holds a single-use permit for exactly that.


Building a Proposal

Calldata is computed locally. cast calldata takes a function signature and arguments and returns the Function Selector followed by the ABI-encoded arguments, and needs neither a node nor an account. Foundry's cast is used throughout because it is the shortest path; any ABI encoder produces identical bytes.

Choosing the shape

INTENT OP TO VALUE DATA
Pay ETH from the treasury 0 recipient amount in wei 0x
Call a function on some contract 0 that contract usually 0 encoded call
Change one of the DAO's own settings 0 $DAO 0 encoded call to the setter
Several calls in one proposal 0 $DAO 0 — inner values are paid from the treasury encoded batchCalls or multicall

The DAO's own setters are ordinary functions guarded by msg.sender == address(this), so a configuration change is a proposal whose target is the DAO. The only other route is a permit granted for that exact call; see Permits and Allowances.

# Set quorum to 10% of snapshot supply
cast calldata "setQuorumBps(uint16)" 1000
# 0x0527aab600000000000000000000000000000000000000000000000000000000000003e8

# Give unqueued proposals a 24-hour life
cast calldata "setProposalTTL(uint64)" 86400
# 0xff3914670000000000000000000000000000000000000000000000000000000000015180

# Replace the DAO's metadata renderer
cast calldata "setRenderer(address)" $NEW_RENDERER

cast sig "setQuorumBps(uint16)" returns the selector on its own. The complete setter list with selectors is in the functions reference.

Multiple actions in one proposal

Majeur has two aggregators and they are not interchangeable.

FUNCTION SELECTOR MECHANISM USE
batchCalls((address,uint256,bytes)[]) 0x11c76fd9 call to each target in turn, forwarding each value; onlyDAO Several external calls — a token approval followed by the call that consumes it, for instance
multicall(bytes[]) 0xac9650d8 delegatecall to address(this) for each payload; msg.sender preserved; not payable Several of the DAO's own setters in one proposal

batchCalls is the general one. Each element is a (target, value, data) tuple, and the inner data is itself built with cast calldata:

APPROVE=$(cast calldata "approve(address,uint256)" $SPENDER 1000000000000000000)
CONSUME=$(cast calldata "claimTribute(address,address)" $PROPOSER 0x0000000000000000000000000000000000000000)

cast calldata "batchCalls((address,uint256,bytes)[])" \
  "[($TOKEN,0,$APPROVE),($SPENDER,0,$CONSUME)]"

The result is the data field of a proposal whose to is $DAO and whose value is 0. batchCalls pays each inner value from the DAO's own balance at execution time, so the treasury has to hold the sum; multicall is not payable and reverts if the proposal carries any value. One failing inner call reverts the whole batch, and a reverted execution does not consume the proposal.

Nonce and id

Pick any 32 bytes. cast keccak "$(date +%s)" or a random hex string is fine; the only constraint is that an identical tuple under the same nonce resolves to the same id, so a re-proposal of an action that already executed needs a different one.

With the five fields in hand, the id can be computed without touching a node. abi.encode is standard head encoding, which cast abi-encode produces when given a signature with the matching argument types:

DATA_HASH=$(cast keccak $DATA)
CONFIG=$(cast call $DAO "config()(uint64)" --rpc-url $RPC_URL)   # 0 on a DAO that has never bumped

cast keccak $(cast abi-encode \
  "f(address,uint8,address,uint256,bytes32,bytes32,uint256)" \
  $DAO 0 $TARGET 0 $DATA_HASH $NONCE $CONFIG)

The DAO's proposalId(uint8,address,uint256,bytes,bytes32) view returns the same number when a node is at hand. The offline form is the one to use for verification: it needs no RPC and produces the id from the published fields alone.

The transactions

Every step after construction is a call on $DAO. The proposer typically sends the first two; the others can be sent by anyone.

# 1. Open — fixes the snapshot at block.number - 1. Optional: the first castVote opens implicitly,
#    but opening explicitly lets the proposer choose when the electorate is measured.
cast send $DAO "openProposal(uint256)" $ID

# 2. Vote — 0 AGAINST, 1 FOR, 2 ABSTAIN. Weight is the caller's delegated balance at the snapshot.
cast send $DAO "castVote(uint256,uint8)" $ID 1

# 3. Queue — once state() reads Succeeded. A no-op on a DAO without a timelock.
cast send $DAO "queue(uint256)" $ID

# 4. Execute — reveals the preimage.
cast send $DAO "executeByVotes(uint8,address,uint256,bytes,bytes32)" \
  0 $TARGET 0 $DATA $NONCE

Opening checks proposalThreshold against the opener's current delegated votes (getVotes, not the snapshot). On an implicit open the check falls on the first voter, who becomes the proposer. The threshold is zero on any DAO that never set it.

state(uint256) returns an enum — 0 Unopened, 1 Active, 2 Queued, 3 Succeeded, 4 Defeated, 5 Expired, 6 Executed — and is the read to make between steps. One trap in step 4: on a timelocked DAO, executeByVotes against an unqueued Succeeded proposal queues it and returns (true, "") without executing, so a true is not evidence that the call ran.

Whatever the proposer publishes about the proposal — the five fields, in a form a voter can copy into the commands above — is the only thing a voter can verify against. The contract does not carry it. FWC posts a <<<PROPOSAL_DATA … PROPOSAL_DATA>>> JSON block containing the five fields into the DAO's own on-chain chat (chat(string), readable back through messages(uint256)), where a voter can re-hash it. FWC's keeper contract uses this convention; the framework does not require it.


Reading a Proposal

Verification runs the other way: an id on a ballot, a blob of parameters published beside it, and the question of whether the blob is the proposal and what the proposal does.

Where the parameters come from

PROPOSAL STATE SOURCE OF THE PREIMAGE
Pending (Active, Succeeded, Queued) Wherever the proposer published it. The chain has only the id, tally, proposer, createdAt and queuedAt
Executed The arguments of the executeByVotes call — the transaction input when an externally owned account (EOA) executes, an internal call in the trace when a contract does. The Executed(uint256 indexed id, address indexed by, uint8 op, address to, uint256 value) event carries mode, target and value but not data

For a pending proposal there is no substitute for the publisher. If the proposer published nothing, the id cannot be checked against any intent, and a FOR vote approves contents the voter has not seen.

Step one: recompute the id

Recompute the id from the published five fields exactly as in Nonce and id and compare. A mismatch means the description is not the proposal — a typo, a stale nonce, or a different config than the one the id was computed under. Do this before decoding anything, because decoding parameters that do not hash to the ballot id tells you about some other proposal.

Step two: decode the data

# Identify the selector
cast 4byte ${DATA:0:10}

# Decode against the signature it resolves to
cast decode-calldata "setRenderer(address)" $DATA

△ cast 4byte queries a public, user-submitted signature registry. Four-byte selectors collide, and unregistered functions return nothing at all. Treat a hit as a lead. The confirmation is cast sig "<signature>" against a function that exists in the target's verified source; if the target is unverified, the selector cannot be confirmed against source.

The output is one value per parameter, in signature order; --json returns the same structure as nested arrays, which is easier to work with once there is more than one element.

If the selector is 0x11c76fd9 (batchCalls) or 0xac9650d8 (multicall), the first decode yields an array whose elements each contain a bytes field, and that field is itself calldata — selector plus padded arguments — to be decoded rather than read. For batchCalls the decode looks like this, one (target, value, data) tuple per inner call:

[(0x62eC8675…, 0, 0x2806b0af0000…), (0x62eC8675…, 0, 0x095ea7b30000…), (0x00000000…, 0, 0xac6695d10000…)]

Take each inner data, slice the first ten characters for its selector, identify it and decode it against that signature exactly as above. Each inner target is a separate contract to identify. Repeat until nothing that comes out decodes further; the leaves are the actions the proposal performs.

The loop is mechanical: find the selector, resolve it, decode, recurse into every bytes that looks like calldata. The DNZN calldata decoder runs the loop in the browser and prints the nested calls as a tree; paste data, or a full executeByVotes input, and it unwraps nested batchCalls and multicall layers. It reports what the bytes say. Confirming that a registry-resolved selector really exists on the target, and that the target is what the proposal claims, remains the reader's check.

Step three: the fields that are not calldata

  • op — anything other than 0 is a delegatecall. No setter requires one; every setter is reachable by ordinary self-call
  • to — resolve it on an explorer. Verified source, deployer, and whether it is what the proposal description claims. For a batchCalls, every inner target
  • value — with empty data this is a payment; with non-empty data it is ETH forwarded into the call. Ignored when op != 0

Worked example: zOrg proposal 7

Proposal index 7 in zOrg's registry executed in transaction 0x36cde4af...f656ba (tx) at block 23875426 (etherscan). It nests three calls into two contracts, one of which the public selector registry does not know.

The execution input decodes at the outer layer as an executeByVotes:

cast tx 0x36cde4af05f5d8ce7553d43c9ded7b31d53a378e6fc21a04e5165715a4f656ba input --rpc-url $RPC_URL \
  | xargs cast decode-calldata "executeByVotes(uint8,address,uint256,bytes,bytes32)"

# 0                                                   <- op: plain call
# 0x5E58BA0e06ED0F5558f83bE732a4b899a674053E          <- to: the DAO itself
# 0                                                   <- value
# 0x11c76fd9…  (836 bytes)                            <- data: selector is batchCalls
# 0xfaca2964ea31bc208b43e65524756e61ab82ebfe7d4981e56fd985cbde1b261f   <- nonce

Decoding the data as batchCalls((address,uint256,bytes)[]) yields three (target, value, data) elements, and each inner data decodes in turn (decode the full input):

# TARGET SELECTOR DECODED CALL
1 0x62eC8675...359016 (etherscan) — zOrg's Loot clone 0x2806b0af mintFromMoloch(address,uint256) mint 1e18 loot to the DAO itself
2 0x62eC8675...359016 (etherscan) — same Loot contract 0x095ea7b3 approve(address,uint256) approve 0x00000000...ea73D0 (etherscan) to pull 1e18 loot
3 0x00000000...ea73D0 (etherscan) — verified as Tribute 0xac6695d1 claimTribute(0x1C0Aa8cC...855A20, address(0)) — proposer 0x1C0Aa8cC...855A20 (etherscan)

cast 4byte 0xac6695d1 returns nothing. The target's verified source has claimTribute(address proposer, address tribTkn), and cast sig "claimTribute(address,address)" returns 0xac6695d1, which settles it. That two-argument form is the deployed Tribute; the upstream repository has since changed the signature, so the verified source at the target is the reference, not the repository. The source also says what the call does: Tribute is an over-the-counter (OTC) escrow where a proposer locks an asset and names what they want from the DAO in return. claimTribute, called by the DAO, pays the named consideration to the proposer and releases the locked asset to the DAO. A token consideration is pulled from the DAO, which is why call 2 approves it; an ETH consideration has to be attached as the inner call's value. tribTkn = address(0) means the locked asset is ETH.

Read together, the three calls are a classical Moloch Tribute: the DAO mints one Loot to itself, approves the escrow to take it, and claims an ETH offer posted by the zFi deployer. The receipt's TributeClaimed event fills in the number the calldata does not contain — the offer was 0.001 ETH for that one loot. A voter reading only the calldata would know the DAO was issuing a unit of non-voting economic claim to the deployer in exchange for whatever ETH the deployer had locked; the amount was stored in the escrow contract, and the proposal never contained it.

Finally, the id. With config = 0:

DATA_HASH=$(cast keccak $DATA)       # 0xb767def1…084d8e
cast keccak $(cast abi-encode "f(address,uint8,address,uint256,bytes32,bytes32,uint256)" \
  0x5E58BA0e06ED0F5558f83bE732a4b899a674053E 0 0x5E58BA0e06ED0F5558f83bE732a4b899a674053E 0 \
  $DATA_HASH 0xfaca2964ea31bc208b43e65524756e61ab82ebfe7d4981e56fd985cbde1b261f 0)
# 0x28f8700c3e0140a067dad58abddc335a1bf2bcd0623ec9f47dae362916b03d72

That value is proposalIds(7) on zOrg and the first indexed topic of the Executed log in the transaction above. The round trip uses the transaction input, the DAO's proposalIds storage and a local keccak; no front end is involved.

A simpler specimen, for comparison: zOrg's proposal 20 executed in 0xf90b79da...f8dd80 (tx) with to = ZorgConviction at 0x0000006D...32a9a8 (etherscan) and data = 0x56d3163d…, which is setRenderer(address) — a one-argument call against a contract that names the DAO as its immutable admin through dao() (decode). The same procedure applies with one fewer layer.


The Lifecycle

flowchart TD
    A["proposalId computed off-chain"] --> B["openProposal(id)<br/>or implicit on first castVote"]
    B --> C["snapshot = block.number - 1<br/>electorate fixed"]
    C --> D["castVote(id, support)<br/>0 AGAINST · 1 FOR · 2 ABSTAIN"]
    D --> E{"quorum met and<br/>FOR > AGAINST?"}
    E -->|no, TTL elapsed| X["Expired"]
    E -->|no, time remains| D
    E -->|yes| F["Succeeded"]
    F --> T{"timelockDelay set?"}
    T -->|yes| G{"queue(id) before<br/>TTL elapses?"}
    G -->|no| X2["Expired — tally intact, dead"]
    G -->|yes| H["Queued — TTL no longer applies"]
    H --> I["timelockDelay"]
    I --> J["executeByVotes(op, to, value, data, nonce)<br/>any caller, no deadline"]
    T -->|no| K{"executeByVotes before<br/>TTL elapses?"}
    K -->|no| X2
    K -->|yes| J2["Executed"]

    style X2 fill:#ffe1e1
    style H fill:#fff4e1
    style J fill:#e1f0ff

Figure 1: Proposal lifecycle. Opening, voting, queueing and executing are separate transactions that different parties may have to send.

ACTION WHO CONSTRAINT
openProposal Any address whose current delegated votes meet proposalThreshold The opener picks the block, and therefore the electorate. On an implicit open, the first voter is the opener
castVote Any address with delegated shares at the snapshot One vote per address; cancelVote then recast while Active. Closed once proposalTTL from creation elapses, if set
cancelProposal The proposer — the opener, or the first voter on an implicit open Only while Active, unqueued, with no votes cast and no futarchy pool. Cancelling burns the id; re-proposing needs a new nonce
queue Anyone Only while Succeeded and inside proposalTTL from creation. Does nothing without a timelock
executeByVotes Anyone After timelockDelay from queueing, with no deadline. Without a timelock, before proposalTTL elapses

The two clocks

proposalTTL bounds the life of an unqueued proposal from its creation; timelockDelay is the wait between queueing and execution. Both are per-DAO and both are zero unless set, which disables them. On a timelocked DAO the window to rescue a passing proposal is the TTL alone, because queue must happen while the proposal is still unexpired and the timelock only begins afterwards; lengthening the timelock does not lengthen the window. On a DAO with a TTL and no timelock, queue is a no-op and the proposal must be executed inside the TTL.

Missing the window kills that id. It does not kill the action — re-propose the identical tuple under a fresh nonce and the vote restarts from zero. zOrg has two proposals that drew zero AGAINST votes at two to three times the required turnout and read Expired because nobody queued them inside its 24-hour TTL; FWC's chat shows the same "claim fees" call proposed under three different nonces. The analysis covers the sample in detail.

Once queued, the expiry branch is never evaluated again for that id, so a queued proposal cannot expire; after its timelock any address can execute it at any later time. The outcome is still open until then. state() re-counts the live tally against current quorum settings, votes can still be cast until the TTL from creation elapses, and AGAINST votes cast during the timelock, or a raised quorum or YES floor, can return the proposal to Active or Defeated. The only outright revocation is bumpConfig(), which invalidates every pending proposal and every permit on the DAO at once, including any mid-vote.

state() uses whatever parameters are current when it is read, regardless of what was in force at the snapshot. A proposal that changes quorum or TTL changes the status of every proposal already in flight the moment it executes; the analysis linked above includes a zOrg proposal that reads Expired for this reason alone.


Voting Power and Delegation

Weight for a proposal is getPastVotes(delegate, snapshotBlock) on the Shares contract, and nothing else. There is no fallback to current balance.

  • Shares self-delegate by default; no action is required before voting
  • delegate(address) on the Shares contract moves the caller's whole balance to one delegate. setSplitDelegation(address[],uint32[]) divides it across up to four in basis points summing to 10,000; clearSplitDelegation() returns to a single delegate. See Split Delegation
  • Delegation is non-custodial; shares do not move
  • Any change made after a proposal opens has no effect on that proposal's weights

The snapshot is block.number - 1 at the moment of opening, so the opener cannot fold a same-block transfer into it, but can wait for a favourable distribution and open then. proposalThreshold is the only gate on who may open, and it is zero unless set. Abstentions count toward Quorum.


Tokens and Entitlements

A Majeur DAO issues four token types; only Shares carry votes.

TOKEN STANDARD VOTES TREASURY CLAIM TRANSFERABLE NOTES
Shares ERC20 with checkpoints ☑ ☑ ☑ unless setTransfersLocked Delegatable; the snapshot denominator
Loot ERC20 ☒ ☑ ☑ unless setTransfersLocked Economic claim without governance weight. Issuing it dilutes treasury claims without affecting voting weight
Badges Soulbound Token (ERC721) ☒ ☒ ☒ 256 seats; required to call chat. Seats fill first-come while any are free, then by strictly exceeding the smallest seated balance. A seat survives balance decreases until displaced, and is released when the holder's share balance reaches zero
Vote receipts ERC-6909 on the DAO ☒ ☒ ☑ Minted by castVote in proportion to weight; the claim on a Futarchy pool. Transferring one forfeits cancelVote, which burns it from the caller

Permits are also ERC-6909 balances on the DAO, minted against an intent hash; see Permits and Allowances.


Ragequit

ragequit(address[] tokens, uint256 sharesToBurn, uint256 lootToBurn) burns the caller's shares and loot and pays a proportional slice of each listed token, ETH included as address(0). It is permissionless while ragequittable is set — the flag is off at birth unless the summoner set it, and a passing proposal can clear it.

payout of token X = balance_X(DAO) × (sharesToBurn + lootToBurn) / (totalShares + totalLoot)

The denominator is read before the burn. Constraints on the arguments:

  • tokens must be sorted strictly ascending by address, with no duplicates
  • The DAO's own Shares and Loot contracts, the DAO's address and address(1007) cannot be listed; any of them reverts the whole call. Shares and loot held by the treasury are therefore not claimable, although they count in the denominator
  • Only listed tokens are paid. The list is not checked against holdings, the burn happens first, and there is no dry-run — an omitted asset is a forfeited claim
  • Non-Fungible Tokens (NFTs) held by the treasury have no payout path at all; the DAO can receive ERC721 and single ERC1155 assets that no holder can claim
cast calldata "ragequit(address[],uint256,uint256)" \
  "[0x0000000000000000000000000000000000000000,$TOKEN_A,$TOKEN_B]" $SHARES $LOOT

Enumerate the treasury independently before building that list. The analysis covers the arithmetic and the rounding.


Permits and Allowances

Governance can grant standing rights that do not go back to a vote each time they are used. Neither kind expires, and a later proposal can revoke either by setting it lower. bumpConfig() also voids every permit, because permit ids include config; allowances survive it.

MECHANISM GRANTS RECORDED
Permit — setPermit(op, to, value, data, nonce, spender, count) count executions of one exact intent via spendPermit, no quorum, no timelock PermitSet event with the intent hash only. The intent itself is in the calldata of the transaction that called setPermit
Allowance — setAllowance(spender, token, amount) Right to pull up to amount of token via spendAllowance at the holder's discretion ☒ No event. Storage only, read with allowance(token, spender) — the reverse of the setter's argument order

A permit's intent is not in its PermitSet event, but it is in the calldata of the proposal or summon transaction that called setPermit; decode that to learn what an unspent permit allows. An allowance denominated in address(this) mints new shares on draw rather than transferring held ones, and one denominated in address(1007) mints new loot, which makes either a standing dilution right. Because setAllowance emits nothing, such a grant is found only by reading storage or decoding executed proposal calldata. FWC carries both kinds: routine keeper permits, a Multisig-held bumpConfig permit and a single-use delegatecall permit, plus two share-minting allowances to sale contracts that together came to roughly 4.5 times its share supply on 2026-09-26. The artifacts enumerate the permits and the allowances.


Futarchy

A proposal can carry a Futarchy reward pool, funded by anyone via fundFutarchy in ETH, shares or loot, or earmarked automatically when a DAO enables setAutoFutarchy. Vote receipts on the side that prevailed are burned through cashOutFutarchy for a pro-rata share of the pool; receipts on the losing side are worth nothing.

  • ◇ FOR pays only if the proposal is executed. A passing proposal that expires unexecuted resolves to NO and pays AGAINST; ABSTAIN receipts never pay
  • ◇ YES resolves inside executeByVotes; NO requires an explicit resolveFutarchyNo once the proposal reads Defeated or Expired
  • △ With autoFutarchy on and no rewardToken set, the pool is newly minted loot — winners are paid by diluting every treasury claim

Checklists

  • Obtain the five fields from wherever the DAO publishes them, and recompute the id offline. A mismatch means the published fields are not the proposal on the ballot
  • Decode data to selector and arguments; for batchCalls and multicall, every element. Confirm each selector against the target's verified source, not the 4byte registry alone
  • Expect op = 0; any other value is a delegatecall and runs the target's code against the DAO's storage
  • Resolve every target on an explorer; an unverified target's selectors cannot be confirmed against source
  • Confirm delegated weight at the snapshot block with getPastVotes
  • On a timelocked DAO, queue(id) promptly: proposalTTL from creation is the whole window, and timelockDelay does not extend it. Without a timelock, execute inside the TTL instead
  • Until execution, late AGAINST votes or a settings change can still turn the result
  • After the timelock, executeByVotes with the exact original tuple. On a timelocked DAO, a true return from the first call means queued, not executed
  • Enumerate every ERC20 the treasury holds plus ETH, from the explorer, not from an interface
  • Leave out the DAO's own Shares and Loot contracts; listing either reverts the call
  • Sort ascending, no duplicates
  • NFTs are not claimable
  • Read proposalTTL, timelockDelay, quorumBps, proposalThreshold, ragequittable, config
  • Scan PermitSet and SaleUpdated events, then read allowances out of storage
  • List proposals in Queued or Succeeded state. A Queued proposal becomes executable by anyone when its delay ends; a queued proposal past its delay reads Succeeded and is executable now
  • Decode the summon transaction's initCalls, which ran before any electorate existed


References

RESOURCE NOTES
z0r0z/majeur Framework repository. The deployed Moloch.sol is byte-identical to commit 7d7a36b; peripherals such as Tribute have changed since their deployment
Etherscan — Moloch implementation Verified source behind every behavioural claim on this page
Etherscan — zOrg DAO Source of the decoded proposals
Etherscan — Tribute Verified source of the escrow called by zOrg proposal 7
Etherscan — FWC DAO Source of the permit and allowance observations
zFi DAO interface Front end through which both instances are operated
Majeur Artifacts Raw reads behind the figures cited here
DNZN calldata decoder Browser tool that recursively decodes nested proposal calldata
Foundry Cast reference cast calldata, cast sig, cast 4byte, cast decode-calldata, cast abi-encode, cast keccak
Solidity ABI specification Encoding rules behind every byte shown above
Moloch DAO The 2019 framework Majeur descends from; origin of ragequit and tribute proposals

Changelog

DATE AUTHOR NOTES
2026-08-19 Artificial. Generated by robots. Gas: 700 tok
2026-08-19 Artificial. Restructured around proposal construction and decoding; zOrg proposal 7 added as the worked decode; simulation section removed. Gas: 90 tok
2026-09-26 Artificial. Decoding links to the DNZN calldata decoder. Corrected after a standards audit and fact-check: queueing without a timelock, the proposal threshold, batch value, late votes on queued proposals, permit visibility, badge seats, ragequit exclusions, futarchy payout, and the proposal 7 data hash. Gas: 830 tok
2026-09-26 Artificial. Concision pass: removed repeated TTL/timelock explanations, cast tutorial material, and an unbacked claim about third-party decoders. Gas: 25 tok
2026-09-26 Denizen. Reviewed, edited, and curated by humans.