Community DAOs
Moderators & Write Thresholds

Moderators & Write Thresholds

A community's governance layer and its moderation layer are the same thing. Moderators are appointed and removed by DAO vote; the token balance you need to post is changed by DAO vote. Both live in one contract, CommunityManagement.aes, deployed alongside the DAO when the community is created.

The contract

CommunityManagement.state =
  { owner                   : address
  , minimum_token_threshold : int
  , is_private              : bool
  , moderator_accounts      : Set.set(address)
  , muted_user_ids          : Set.set(string)
  , meta_info               : map(string, string) }

CommunityFactory.create_community deploys it in the same transaction that creates the token, the sale and the DAO:

Chain.create(Call.caller, 10 ^ decimals(), is_private, meta_info) : CommunityManagement
ArgumentValue at creation
initial_moderatorCall.caller, whoever created the community
minimum_token_threshold10 ^ 18, exactly one whole token (decimals() is 18)
is_privatePassed through from create_community; the web app sends false
meta_infoPassed through from create_community

owner is set to the factory in init and immediately reassigned to the DAO with community_management.set_owner(dao.address). The creator ends up as the sole moderator but not the owner, and cannot change the threshold or appoint anyone else without a vote.

Reach the contract from the DAO with get_community_management(), which returns option(CommunityManagement).

Who can change what

ChangeEntrypointAuthorised by
Appoint a moderatoradd_moderator(moderator)DAO vote (AddModerator)
Remove a moderatordelete_moderator(moderator)DAO vote (DeleteModerator)
Change the write thresholdchange_minimum_token_threshold(n)DAO vote (ChangeMinimumTokenThreshold)
Replace the metadata mapchange_meta_info(meta_info)DAO vote (ChangeMetaInfo)
Mute an accountmute_user_id(user_id)A moderator
Unmute an accountunmute_user_id(user_id)A moderator
Transfer ownershipset_owner(owner)The owner; in practice only a ChangeDAO vote

Moderators mute and unmute. Everything structural (who the moderators are, what the threshold is, what the community says about itself) is a token-holder decision that goes through a proposal and a two-week vote. A moderator cannot appoint another moderator, cannot change the threshold, and cannot remove themselves from the set. Muting is quick and needs no vote, while changing the moderator roster needs a full proposal.

The owner of CommunityManagement is the DAO, which is how a passed vote reaches these entrypoints. set_owner is not one of the seven vote subjects; the only path that calls it after creation is a ChangeDAO migration, which hands it to the replacement DAO in the same transaction. See Replaceable DAOs.

The minimum token threshold

minimum_token_threshold is the amount of the community's own token an account needs to write in the community. It has a second job: it is also the bar for opening a DAO proposal. Raising the threshold makes a community both harder to post in and harder to propose in; lowering it does the reverse.

Read it directly:

CommunityManagement.minimum_token_threshold() : int

The value is in the token's smallest unit. Divide by 10^decimals (read decimals from the token's meta_info()) before comparing it to a balance shown in a wallet. Every community starts at 10^18, one whole token, until a ChangeMinimumTokenThreshold vote moves it.

The contract stores the threshold and hands it out. The app and the backend enforce it for posting (see Moderation), and DAO.add_vote enforces it for proposals, requiring the proposer's balance to clear it.

Choosing one

  • The token price is not fixed. The threshold is a token count, and the bonding curve price rises with supply, so a threshold set when the token was cheap becomes expensive later without anyone voting to change it.
  • It gates proposals as well as posts. Set it high enough to keep out drive-by spam and low enough that more than a handful of accounts can still open a vote.
  • Changing it is slow and costs 555 AE. Treat it as a decision you revisit a few times a year, not a dial you tune.

Moderator powers

A moderator's on-chain authority is exactly two entrypoints:

stateful entrypoint mute_user_id(user_id: string) : unit
stateful entrypoint unmute_user_id(user_id: string) : unit

Read the set with muted_user_ids() and the roster with moderator_accounts().

What a mute does

A mute removes the account from the community's token-gated chat room and prevents it being re-added, regardless of token balance. A muted holder who clears the threshold is still excluded from the room.

user_id is a string; pass the account's ak_ address. Entries are matched against member addresses, so any other string mutes nobody.

A mute does not hide or filter content. A muted account's existing posts stay visible everywhere they were visible before. It is also separate from the per-user room mute in the app, which is a notification preference an account sets for itself.

There is no post deletion, no edit, no ban, no timed mute and no appeal mechanism in the contract. Moderation here is membership control, not content control. See Moderation.

Muting is not governance. A moderator does not need a vote to mute, and a mute is not reversed by vote. Only a moderator's unmute_user_id changes it.

The is_private flag

is_private is set once, at community creation, as the third argument to CommunityManagement.init:

init(initial_moderator : address,
     minimum_token_threshold : int,
     is_private : bool,
     meta_info : CommunityManagement.meta_info)

Whoever creates the community chooses it through the factory:

CommunityFactory.create_community(collection_name, name, initial_buy_count,
                                  is_private, meta_info)

It is fixed at creation. CommunityManagement has no set_is_private entrypoint, it is not in the vote-subject set, and change_meta_info only replaces the metadata map. A community cannot become private, or stop being private, after it is created. The only way to change it is a new community.

is_private only affects chat-room visibility: a private community's chat requires an authenticated relay session (a NIP-42 AUTH handshake) before history can be read, while the room still appears in listings with a private badge. The token, the feed, the trades, the holders and all on-chain state stay public, and the write gate is minimum_token_threshold for every community, private or not.

The metadata map

CommunityManagement.meta_info = map(string, string)

A free-form string map holding the community's descriptive metadata. change_meta_info replaces it wholesale, through a ChangeMetaInfo vote, so a proposal that means to change one key must carry every other key unchanged. There is no per-key update entrypoint.

Events

CommunityManagement emits an event for every state change, so moderation history is auditable without replaying contract state:

EventFields
ChangeMinimumTokenThresholdthe new threshold
AddModeratorthe appointed address
DeleteModeratorthe removed address
MuteUserIdthe muted user id string
UnmuteUserIdthe unmuted user id string
SetOwnerthe new owner address
ChangedMetaInfono fields

Every mute in a community's history is a permanent public record, including ones that were later undone. See Contract events.

Related