Community DAOs
Proposals & Vote Subjects

Proposals & Vote Subjects

A proposal's action is a value of the Sophia variant type VoteSubjects.subject, which has a fixed list of seven constructors compiled into the contract. The set is closed. If an action is not one of the constructors below, no vote can express it. Changing the set means deploying a different DAO implementation and migrating to it with a ChangeDAO vote.

The subjects

In ACI order, which is also the variant tag order used on the wire:

TagSubjectParametersEffect
0VotePayoutaddressDisburse the entire DAO balance to that account
1VotePayoutAmountaddress, intDisburse a specified amount to that account
2ChangeDAOaddressTransfer the treasury, the token sale's ownership and beneficiary rights, and ownership of community management to another DAO
3ChangeMetaInfomap(string, string)Replace the community's metadata map in community management
4ChangeMinimumTokenThresholdintChange the minimum token threshold for write access and for opening proposals
5AddModeratoraddressAppoint a moderator
6DeleteModeratoraddressRemove a moderator

The tag is the index: a decoded subject arrives as an object keyed by the tag number, so tag 5 with one address payload is an AddModerator proposal. The Superhero wallet's transaction summary decodes the subject in this order before asking you to sign.

Parameter shapes

SubjectWhat the address must be
VotePayoutAn account (ak_) that receives the whole treasury
VotePayoutAmountAn account (ak_) that receives amount
ChangeDAOA contract (ct_) implementing the ReplaceableDAO interface
AddModerator / DeleteModeratorAn account (ak_) to appoint or remove as moderator

VotePayoutAmount's int is in aettos, with 10^18 aettos to one AE. ChangeMinimumTokenThreshold's int is in the community token's smallest unit, so divide by 10^decimals to compare it against a displayed balance.

Payout amounts and the treasury balance

add_vote validates only that Call.value is 555 AE and that the caller holds the minimum token threshold; it does not compare a payout amount to the DAO's balance. apply_vote_subject executes the subject with Chain.spend(receiver, amount), which aborts if the balance is insufficient and leaves the vote unapplied. If no application succeeds inside the execution window, the proposal expires.

  • Check the treasury before you propose an amount, and leave headroom. The balance can fall between proposal and execution: another payout vote may apply first, and treasury income only arrives on buys.
  • VotePayout spends Contract.balance: whatever is there at execution, including zero, so it cannot fail this way.

Any number of proposals can be open at once, including conflicting ones. If two payout votes both pass, both can be applied, in whatever order people call apply_vote_subject, until the balance runs out.

ChangeMetaInfo replaces the whole metadata map. CommunityManagement.change_meta_info takes a complete map(string, string); there is no per-key update, so a proposal that changes one field must carry every other field unchanged.

The ChangeDAO safeguard

ChangeDAO hands over everything. The branch in apply_vote_subject:

VoteSubjects.ChangeDAO(new_dao_address) =>
    let new_dao = Address.to_contract(new_dao_address) : ReplaceableDAO
    require(new_dao.is_dao(), "NEW_DAO_INVALID")
    Chain.spend(new_dao_address, Contract.balance)
    get_token_sale().set_beneficiary(new_dao_address)
    get_token_sale().set_owner(new_dao_address)
    require(get_community_management() == new_dao.get_community_management(),
            "NEW_DAO_HAS_TO_REFERENCE_SAME_COMMUNITY_MANAGEMENT")
    call_community_management((community_management) => community_management.set_owner(new_dao_address))

Five effects in one transaction: the treasury moves, the sale's beneficiary changes, the sale's owner changes, the target is checked to point at the same CommunityManagement, and that contract's ownership is transferred. Moderator appointments and threshold changes keep working after a migration, because the new DAO becomes the owner that CommunityManagement checks.

Two guards protect the migration:

  • is_dao() must return true. An account address, a token address or an unrelated contract does not answer that call, so the migration cannot be applied to one.
  • The target must already reference the same CommunityManagement. Deploy the replacement DAO pointing at the existing community management contract before the vote is applied; otherwise the whole transaction reverts and nothing moves.

set_beneficiary on the sale also runs its own verify_dao probe against the new address, so a target whose record_sale_transaction is not payable fails there.

is_dao() is a shape check, not a trust check. Any contract can return true from it. Read Replaceable DAOs before voting on a ChangeDAO proposal.

The metadata record

DAOVote.metadata = { subject     : VoteSubjects.subject
                   , description : string
                   , link        : string }
FieldPurpose
subjectThe machine-readable action. This is what executes.
descriptionFree text explaining the proposal to voters. At most 300 characters.
linkA reference URL, such as a forum thread, a spec or a budget.

Only subject and the description length are enforced by the contract: DAOVote.init requires String.length(metadata.description) =< 300. There is no title field. The link is free text supplied by whoever opened the vote; the app runs it through a URL-safety check before rendering it as a hyperlink, and any client that surfaces it should do the same.

Who may open a proposal

The same rule covers all seven subjects: a holder who owns at least the community's minimum_token_threshold at the moment of the call can open a vote by calling DAO.add_vote(metadata) with exactly 555 AE attached. The balance is read live from the AEX-9 contract for Call.caller; there is no snapshot and no holding period. There is no moderator-only subject and no proposal whitelist. Moderators are appointed by vote but hold no proposal rights of their own.

Open a proposal

stateful payable entrypoint add_vote(metadata: DAOVote.metadata) : (int, DAOVote)

The call deploys a new DAOVote contract, registers it in the DAO's votes map under the next sequential id, and returns both the id and the vote contract address. Keep both: the vote address is where ballots are read and cast, and the id is what apply_vote_subject(vote_seq_id) takes.

A worked VotePayoutAmount proposal

Paying 100 AE to a contributor, with the discussion thread as the reference link:

subject     = VotePayoutAmount(ak_contributor..., 100000000000000000000)
description = "Pay 100 AE to the contributor who built the community bot, \
               per the scope agreed in the linked thread."
link        = "https://forum.aeternity.com/t/..."

100000000000000000000 is 100 AE in aettos (100 * 10^18). A vote is not editable after it is opened, so check the digit count before you sign; the Superhero wallet's transaction summary shows the decoded subject and its payload.

From the app

Open the community's DAO at /trends/dao/:saleAddress, fill in the create-vote form, and confirm the 555 AE payment in your wallet. VotePayoutAmount and ChangeMetaInfo proposals are opened through the SDK or a direct contract call. The vote's detail page decodes and displays the subject that was signed.

Related