Social
Moderation

Moderation

Content on Superhero cannot be removed. The Tipping contract has no delete and no update entrypoint, so every post and comment ever published stays in the contract's state. Moderation happens at the community application layer, through token-holder governance, and what it controls is access to a community's chat room and the threshold for opening a DAO vote.

CommunityManagement.aes

Each community gets its own CommunityManagement contract, deployed by the factory during community creation.

State

FieldTypeMeaning
owneraddressThe contract owner; set to Call.caller at init, transferable with set_owner
minimum_token_thresholdintRaw base-unit token balance required for write access in this community
is_privateboolWhether the community is private
moderator_accountsset of addressThe current moderator set
muted_user_idsset of stringMuted identifiers: ak_… addresses held as strings
meta_infomap of string to stringCommunity metadata

Entrypoints and who may call them

Two guards cover every write:

function require_owner() =
  require(Call.caller == state.owner, "ONLY_OWNER_CAN_CHANGE")

function require_moderator() =
  require(Set.member(Call.caller, state.moderator_accounts), "ONLY_MODERATOR_CAN_CHANGE")
EntrypointSignatureGuard
get_state() => CommunityManagement.stateread
moderator_accounts() => Set.set(address)read
muted_user_ids() => Set.set(string)read
minimum_token_threshold() => intread
owner() => addressread
meta_info() => meta_inforead
version() => intread; returns 1
mute_user_id(user_id: string) => unitrequire_moderator
unmute_user_id(user_id: string) => unitrequire_moderator
add_moderator(moderator: address) => unitrequire_owner
delete_moderator(moderator: address) => unitrequire_owner
change_minimum_token_threshold(minimum_token_threshold: int) => unitrequire_owner
change_meta_info(meta_info: meta_info) => unitrequire_owner
set_owner(owner: address) => unitrequire_owner

Every write emits a typed event first, then applies: MuteUserId(string), UnmuteUserId(string), AddModerator(address), DeleteModerator(address), ChangeMinimumTokenThreshold(int), SetOwner(address) and ChangedMetaInfo.

The contract checks the owner, not the DAO; the DAO is whoever the owner is.

Who owns it, and who the first moderator is

CommunityFactory.create_community settles both in one transaction:

let community_management = Chain.create(Call.caller, 10 ^ decimals(), is_private, meta_info) : CommunityManagement
let dao = Chain.create(None, Contract.address) : DAO
...
community_management.set_owner(dao.address)
QuestionAnswer
Initial ownerThe factory, because init sets owner = Call.caller, then reassigned to the DAO in the same transaction by set_owner
Final ownerThe community's DAO contract
Initial moderatorThe account that called create_community, the community's creator
Initial minimum_token_threshold10 ^ decimals() with decimals() = 18, so one whole token in raw base units
Initial muted_user_idsEmpty (Set.new())
is_privateExactly the boolean passed to create_community

The creator gets the first moderator seat, and the DAO gets everything else.

Changing the owner

set_owner requires the current owner, which is the DAO. The DAO exposes one route to it: the ChangeDAO vote subject, which moves the token sale's beneficiary and owner to a replacement DAO and then calls community_management.set_owner(new_dao_address). It first requires that the replacement references the same CommunityManagement instance (NEW_DAO_HAS_TO_REFERENCE_SAME_COMMUNITY_MANAGEMENT), so a ChangeDAO vote cannot point a community at somebody else's moderation contract.

Who can do what

ActionWhoHow
Mute a user idA moderatorA direct call to mute_user_id
UnmuteA moderatorunmute_user_id
Appoint a moderatorToken holdersAn AddModerator DAO vote, applied to the contract
Remove a moderatorToken holdersA DeleteModerator DAO vote
Change the write thresholdToken holdersA ChangeMinimumTokenThreshold DAO vote
Change community metadataToken holdersA ChangeMetaInfo DAO vote
Replace the ownerToken holdersA ChangeDAO vote, which reassigns set_owner

Moderators act unilaterally within their mandate; the mandate itself is granted and revoked only by a vote.

The vote that appoints a moderator

Moderation changes ride the same DAO machinery as treasury payouts:

RuleValue
Who may open a voteA holder with at least the community threshold of tokens
Cost to open555 AE, paid to the DAO
Voting periodTwo weeks, measured in key-blocks
Approval requiredAt least 55% of the vote stake in favour
QuorumAt least 10% of the total token stake participating
Execution windowRoughly two days after the vote closes
Who executesAnyone. Any account or script can trigger the application

Tokens used to vote are locked by the vote contract via the AEX-9 allowance mechanism and can be reclaimed with revoke_vote before the close or withdraw after it. The 555 AE goes to the DAO whether or not the vote passes.

Appointing a moderator therefore takes two weeks and 555 AE.

The app offers five of the seven vote subjects in its create-vote UI: payout, change DAO, change the minimum token threshold, add moderator, delete moderator. The two it omits need richer input than the form supports: a payout of a specific amount takes two fields, and changing the metadata takes a map.

What a mute does

A user_id is the holder's ak_… account address held as a string. The token-gated-rooms pipeline enforces it:

  1. The API reads CommunityManagement.get_state() and mirrors muted_user_ids into the room's muted list, alongside moderators, min_token_threshold, is_private and the owner address. The relay's admin and role lists are derived from the same read.
  2. Eligibility for the room is balance >= min_token_threshold AND NOT muted, where the muted check compares the member's æternity address against that list.
  3. A muted member is removed from the relay group with a kind-9001 event, and the sync refuses to publish a kind-9000 add for any member currently muted, so a mute is not undone by a later re-sync.

Mute a holder and they become ineligible for that community's chat room regardless of balance, until the mute is lifted.

A mute affects chat-room eligibility only. Posts, comments, topic feeds and the popular ranking are not filtered by muted_user_ids: a muted account's existing posts stay visible, and it can keep posting.

The per-room mute in the chat app is a different thing: it is a notification preference that silences your own alerts and changes nothing for anyone else. See Notifications.

The write threshold

minimum_token_threshold is a raw base-unit token balance, and it gates two things:

GateWhereRule
Opening a DAO voteDAO.add_votetoken.balance(Call.caller) >= minimum_token_threshold, else ADD_VOTE_TOKEN_HOLDER_MISSMATCH
Entering the community chat roomThe rooms eligibility checkraw balance >= raw threshold, and not muted

It starts at one whole token (10 ^ 18) and moves only by a ChangeMinimumTokenThreshold vote. The same value surfaces in the chat rooms API as min_token_threshold, alongside the room's is_private flag. A room a holder is not eligible for does not appear in their room list, and the composer shows a "hold the token" gate. See Chat.

The privacy flag

create_community(collection_name, name, initial_buy_count, is_private, meta_info) passes is_private straight into CommunityManagement's init, and the rooms service reads it back out of get_state() unchanged. The community's on-chain privacy flag and the chat room's are the same value, chosen once at creation.

What moderation cannot do

  • It cannot delete a post. The bytes stay in contract state.
  • It cannot delete a comment. Comments are posts.
  • It cannot prevent republication. Anyone can post the same content again.
  • It does not hide anything the muted account posted. A mute is a chat-room exclusion, not a content filter.
  • It does not reach other clients. A different frontend reading the same contract is free to ignore every mute; a different relay would enforce nothing.
  • It is per-community. There is no global mute list; each CommunityManagement instance holds its own.

Hiding your own post

The only post-visibility flag is is_hidden, and the indexer sets it from the post's own media list. Any entry whose text contains hidden marks the post hidden at index time, which removes it from the feeds and from popular ranking. It is a convention an author applies to their own post, not a moderator action, and it does not remove the post from the chain.

Related

  • Posting: why there is nothing to delete in the first place.
  • Chat: the relay-side membership control, the other half of community moderation.
  • Moderators: the governance view of appointing and removing them.
  • Voting: the mechanics, quorum and thresholds behind every moderation change.
  • Community management contract: the contract reference.