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| Argument | Value at creation |
|---|---|
initial_moderator | Call.caller, whoever created the community |
minimum_token_threshold | 10 ^ 18, exactly one whole token (decimals() is 18) |
is_private | Passed through from create_community; the web app sends false |
meta_info | Passed 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
| Change | Entrypoint | Authorised by |
|---|---|---|
| Appoint a moderator | add_moderator(moderator) | DAO vote (AddModerator) |
| Remove a moderator | delete_moderator(moderator) | DAO vote (DeleteModerator) |
| Change the write threshold | change_minimum_token_threshold(n) | DAO vote (ChangeMinimumTokenThreshold) |
| Replace the metadata map | change_meta_info(meta_info) | DAO vote (ChangeMetaInfo) |
| Mute an account | mute_user_id(user_id) | A moderator |
| Unmute an account | unmute_user_id(user_id) | A moderator |
| Transfer ownership | set_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() : intThe 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) : unitRead 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:
| Event | Fields |
|---|---|
ChangeMinimumTokenThreshold | the new threshold |
AddModerator | the appointed address |
DeleteModerator | the removed address |
MuteUserId | the muted user id string |
UnmuteUserId | the unmuted user id string |
SetOwner | the new owner address |
ChangedMetaInfo | no fields |
Every mute in a community's history is a permanent public record, including ones that were later undone. See Contract events.
Related
- Proposals and Vote Subjects: the four subjects that reach this contract, with their parameter shapes.
- How DAOs Work: how a moderator appointment travels from proposal to execution.
- Quorum and Thresholds: the vote a moderator change has to clear.
- Moderation: how muting and the threshold are applied in the app.
- Community Management contract reference: the full entrypoint table.
- Replaceable DAOs: a
ChangeDAOmigration transfers this contract's ownership too.