Skip to content

Contract methods and events

Cabdell’s rules are enforced by one smart contract, deployed once per network as an Algorand application. Every action (publishing, reviewing, voting, commenting, flagging) is a call to one of its methods, signed by the person who acts. This page lists them all, with the events they emit and the data they store, for developers and auditors. Terms such as application, box, ASA and minimum balance are explained in the Glossary.

The contract is written in Algorand Python (compiled with puyapy 5.10.1), exposes an ARC-4 ABI, is described by an ARC-56 application specification, and emits ARC-28 events. Everything here was checked against the source and the compiled specification, which win over any other description:

  • projects/contracts/smart_contracts/ariadne/contract.py (methods), events.py (events), types.py (box values), keys.py (box keys), constants.py (constants);
  • projects/contracts/smart_contracts/artifacts/ariadne/Ariadne.arc56.json (the ARC-56 specification, with the deployed bytecode);
  • projects/contracts/V4_REPORT.md (deposits, fees, opcode costs and the flows of the bonds, measured on LocalNet).

This page describes contract v4 (D-163), the MainNet candidate. The normative text is the protocol specification, version 3.0, sections 5 to 13 and 17, amended by the decision log. The application id of each network is in Networks and addresses.

For readers who know contract v3 (D-162 lists the audit findings, D-163 the decision, as amended the same day):

  • Flags weigh at most 10, need a reputation box 30 days old, and carry a bond of 1 ALGO, doubled per clearance by governance for 365 days (at most 16 ALGO). A round that does not become a dispute within 30 days lapses. The threshold is 30, always.
  • Disputes end in four ways: cleared by governance at once, retracted by governance after 14 days (with the CID of its reasons), retracted by the submitter, or expired by anyone after 60 days (not a clearance). settle_flag settles each bond: back to the flagger, half to the submitter and half retained, or all retained.
  • A dispute freezes only new versions: votes, claims, confirmations and comment resolutions continue.
  • Damping: repeated grants from one address to another weigh w // n, with one counter per (giver, receiver). vote_article takes the list of the article’s authors; resolve_comment takes a payment and grants only from a resolver with reputation 10.
  • Governance changes in two steps (propose_governance, accept_governance); set_governance is gone.
  • Amendments only by an accepted author of the parent; DOIs only by the submitter.
  • Events carry schema_version 2 and a gap-free event_seq; Voted, Flagged, DisputeResolved, FieldAdded gain fields; FlagSettled and GovernanceProposed are new.
Topic Rule
Article number article_id is a sequence assigned by the contract, starting at 1. Each article also has an Algorand asset (ASA) whose id is stored in the article box and in Published.
Addresses ARC-4 address: the raw 32-byte public key.
Boxes Named storage slots of the application, one per record (article, vote, review…). Each box raises the application’s minimum balance, which the caller covers with the pay argument.
CIDs byte[36], the raw CIDv1 bytes: 0x01, the codec, 0x12 0x20 (sha2-256, 32 bytes), then the 32-byte digest. Articles must use codec 0x70 (dag-pb, a folder); reviews, comments and the reasons of a dispute resolution codec 0x55 (raw, one file). Any other prefix, length or an all-zero digest is rejected.
pay argument A payment transaction placed in the group just before the call, from the caller to the application address, without close-out or rekey. Its amount must cover the increase of the application’s minimum balance caused by the call (the storage deposit), plus the bond for flag. Overpayment is accepted and kept; there are no refunds.
Fees The caller pays every network fee of the call, including those of the inner transactions, through fee pooling. Fees are read from the network when sending, never hard-coded.
Time Every time the contract reads is Global.latest_timestamp, the time of the previous block; a “day” is 86 400 seconds of it, and day numbers are floor(time / 86 400).
Statuses 1 preprint, 2 under_review, 3 final (shown in the app as “closed version”), 4 disputed, 5 retracted. “Live” below means statuses 1 to 4 (anything but retracted).
Accepted author The submitter, or a declared co-author who confirmed with accept_coauthorship. Accepted authors may not vote on, review or flag their own article.
Failure Any violated precondition rejects the whole transaction group. The assertion messages are in contract.py.
Events Every state-changing call emits exactly one canonical event, followed by zero to two ReputationChanged; the submitter’s retraction of a disputed article emits StatusChanged followed by DisputeResolved.

0 is invalid for every enumeration, with the two exceptions noted for the dispute outcome.

Enumeration Values
type 1 article, 2 notes, 3 amendment, 4 dataset, 5 replication
status 1 preprint, 2 under_review, 3 final, 4 disputed, 5 retracted
recommendation 1 accept, 2 minor_revision, 3 major_revision, 4 reject
flag reason 1 plagiarism, 2 spam, 3 fabricated_data, 4 other
vote target (target_kind) 1 article, 2 review, 3 comment
dispute outcome 1 cleared, 2 retracted, 3 expired; 0 is the value of an outcome box while its dispute is open (pending) and the outcome FlagSettled reports for a round that never became a dispute (lapsed). resolve_dispute accepts only 1 and 2
reputation reason 1 article_upvote, 2 review_useful, 3 comment_upvote, 4 comment_resolved
identity scheme 1 ORCID iD

The storage deposit is in microAlgo (1 ALGO = 1 000 000 microAlgo) and goes to the application account through the pay argument. It is a permanent deposit, not a fee; the only box ever deleted is a flag’s, by its settlement.

Method Who may call Allowed statuses Deposit (pay) Creates
add_field governance not applicable 2 500 + 400 × (3 + name bytes) field box
propose_governance governance not applicable none nothing (global state)
accept_governance the proposed address not applicable none nothing (global state)
resolve_dispute governance disputed none nothing (writes the outcome box)
expire_dispute anyone disputed, 60 days after it started none nothing (writes the outcome box)
settle_flag anyone any, once the flag’s round is over none nothing: deletes the flag box, pays out or retains
publish anyone; for an amendment, an accepted author of the parent not applicable (the article starts as preprint) 222 200 + 29 300 per declared co-author ASA, article box, co-author boxes
extend anyone not applicable none nothing
claim_article the submitter live none (the opt-in costs the author 100 000 on their own account) nothing
new_version the submitter 1, 2 none nothing
set_status the submitter see the transition table none nothing
accept_coauthorship a declared co-author live none nothing
submit_review anyone but an accepted author live 65 800, or 92 700 with the reviewer’s first reputation box in the field review box, reviewer index box, reputation box if missing
vote_review anyone but the reviewer live 32 100, + 18 900 for a first damping counter vote box, damping counter if missing
vote_article anyone but an accepted author live 28 900, + 18 900 per author without a damping counter vote box, damping counters if missing
claim_reputation an accepted author live 0, 26 900 or 53 800 the claimer’s missing reputation boxes
comment anyone every status 56 500, or 83 400 with the commenter’s first reputation box in the field comment box, reputation box if missing
vote_comment anyone but the comment’s author live 32 100, + 18 900 for a first damping counter vote box, damping counter if missing
resolve_comment the submitter live 0, or 18 900 for a first damping counter damping counter if missing
flag anyone but an accepted author, with a reputation box in the primary field holding at least 10 and 30 days old live 29 700 + the bond (1 000 000, doubled per recent clearance); + 7 300 for the flag that opens a dispute flag box; the round’s outcome box when it opens a dispute
declare_identity anyone not applicable none nothing
declare_doi the submitter any; a non-empty DOI not when retracted none nothing
follow anyone not applicable none nothing

The create(governance) method runs once, when the application is deployed.

Governance is one address stored in global state. On TestNet it is a native Falcon-1024 (post-quantum) account, and it pays its own fees and deposits like anyone else. It can register fields, resolve disputes and hand governance to another address in two steps; it cannot change articles, reputation, deposits, bonds or the program.

create(governance: address) -> void

Creation only. governance must not be the zero address. Sets article_seq_next to 1, pending_governance to the zero address and event_seq to 0, and emits GovernanceChanged with the zero address as previous_governance (its event_seq is 1). The deployer keeps no power.

add_field(pay: pay, field_id: uint16, name: string) -> void
  • Sender: the governance address.
  • field_id is not 0 and not registered yet; name is 1 to 96 bytes. Fields are never renamed or removed.
  • Creates the field box t + field_id, whose value is the name in UTF-8.
  • Emits FieldAdded.
propose_governance(new_governance: address) -> void
  • Sender: the current governance address.
  • new_governance is neither the zero address, nor the current governance, nor the application’s own address (which can never sign, so it would end governance for ever).
  • Stores it as pending_governance, replacing any earlier proposal. Nothing else changes yet.
  • Emits GovernanceProposed.
accept_governance() -> void
  • Sender: the address in pending_governance, signing itself. Fails when no change is proposed.
  • Makes it the governance address and clears pending_governance.
  • Emits GovernanceChanged. Because the new address must sign, a mistyped or unsignable address can never take over. This is how governance will move to a post-quantum multisig account once Algorand offers one.
resolve_dispute(article_id: uint64, outcome: uint8, reasons: byte[36]) -> void
  • Sender: the governance address. The article must be disputed.
  • outcome 1 (cleared), at any time: restores the status the article had before the dispute. outcome 2 (retracted), only once latest_timestamp ≥ disputed_at + 14 days: retracts the article and revokes its ASA (one inner asset-configuration transaction).
  • reasons: the raw CID (codec 0x55) of the published text giving governance’s reasons. It is required for both outcomes and checked like any CID.
  • Closes the round (see Ending a dispute) and emits DisputeResolved with resolved_by = the governance address and the reasons. No StatusChanged is emitted.

resolve_dispute, expire_dispute and the submitter’s set_status(retracted) on a disputed article all close the disputed round the same way: they write the round’s outcome box (1 cleared, 2 retracted, 3 expired), increment dispute_round, reset round_flag_weight and status_before_dispute, stamp updated_at, and emit DisputeResolved for the round that was resolved. Only a clearance by governance (outcome 1) counts as a clearance: clearances grows by one (at most 4) and cleared_at takes the current time, which raises the bond of later flags (see flag). An expiry is not a clearance. Flags of earlier rounds stay on record in the events. The last round, 65 535, takes no flags, so no article can be stuck in disputed.

expire_dispute(article_id: uint64) -> void
  • Sender: anyone. The article must be disputed, and latest_timestamp ≥ disputed_at + 60 days.
  • Restores the status the article had before the dispute, with outcome 3 (expired). It is not a clearance, and the round’s bonds will be retained at settlement.
  • Emits DisputeResolved with resolved_by = the caller and reasons all zero bytes.
settle_flag(article_id: uint64, dispute_round: uint16, flagger: address) -> void
  • Sender: anyone. The destination of the money is fixed by the contract, whoever calls.
  • The flag box (article_id, dispute_round, flagger) must exist, and its round must be over: an earlier round than the article’s current one, or the current round when the article is not disputed and latest_timestamp ≥ round_started_at + 30 days (the round lapsed). Nothing can be settled while a dispute is open.
  • The outcome is the round’s outcome box, or 0 (lapsed) when the round never had one (it never became a dispute).
  • Deletes the flag box; its freed deposit (29 700) goes back to the flagger. The bond recorded in the flag box is settled by outcome:
    • 2 (retracted) or 0 (lapsed): the whole bond to the flagger, in the same payment as the deposit;
    • 1 (cleared): half the bond (bond // 2) to the article’s submitter, in a payment of its own; the other half is retained;
    • 3 (expired): the whole bond is retained, and nobody receives any of it.
  • Retained amounts stay in the application account for ever: no method pays them out.
  • When the flagger’s account holds nothing (it was closed) and the outcome is 1 or 3, the deposit cannot be paid to it (it would stay below the minimum balance): it goes with the submitter’s half (cleared) or is retained too (expired).
  • The payments are inner transactions with fee 0: the caller pools their fees (two inner transactions for a clearance, one otherwise, fewer for a closed flagger).
  • Emits FlagSettled. A flag is settled exactly once, since its box is gone.
publish(pay: pay, cid: byte[36], primary_field: uint16, secondary_field: uint16,
article_type: uint8, parent_article: uint64, coauthors: address[]) -> uint64
  • Sender: anyone. The sender becomes the submitter, for ever.
  • cid: a dag-pb CID (codec 0x70).
  • primary_field is a registered field; secondary_field is 0 or a registered field different from the primary.
  • article_type is 1 to 5. parent_article is the number of an existing article when the type is amendment (3), and 0 for every other type. An amendment’s sender must be an accepted author of the parent (the submitter or a co-author who confirmed); an amendment of an amendment needs an author of that amendment.
  • coauthors: 0 to 25 addresses in strictly ascending order of their raw 32 bytes (which proves they are distinct), never the sender. The list can never be changed afterwards.
  • Creates the article’s ASA (one inner transaction), the article box, the submitter’s co-author box (accepted) and one co-author box per declared address (not accepted yet). Creates no reputation box.
  • Returns the new article_id and emits Published. The article starts as preprint, version 1.
  • Deposit: 222 200 microAlgo (ASA 100 000 + article box 92 900 + the submitter’s co-author box 29 300), plus 29 300 per declared co-author (954 700 with 25).
extend() -> void

Changes nothing and emits nothing. A call to extend placed in the same group lends its opcode budget and its box references to the other calls (D-073). Clients add ceil(boxes / 8) - 1 calls to extend, where:

  • for publish, boxes = 3 + 1 with a secondary field + 2 for an amendment (the parent and the sender’s co-author box of it) + the number of co-authors. Up to 4 co-authors (5 without a secondary field) need none;
  • for vote_article, boxes = 4 + 2 × the number of authors (one co-author box and one damping counter per author; one fewer when the voter is a declared, unconfirmed co-author). One or two authors need none; 26 authors need 6.
claim_article(article_id: uint64) -> void
  • Sender: the submitter, who has opted in to the article’s ASA (paying their own opt-in deposit). The article is not claimed yet and not retracted.
  • Four inner transactions move the token to the author and freeze it there for ever: unfreeze, transfer, refreeze, and an asset configuration that sets the freeze address to zero.
  • Emits ArticleClaimed. Optional: authorship lives in the article box, not in the token.
new_version(article_id: uint64, cid: byte[36]) -> void
  • Sender: the submitter. Status preprint or under_review (not while disputed, so the jury judges a fixed text).
  • cid: a dag-pb CID different from the current one. The version number grows by exactly 1 (at most 65 535).
  • Moves the ASA’s ARC-19 reserve to the new CID’s digest (one inner transaction), sets prev_cid to the old CID.
  • Emits Versioned. The whole version history is in these events.
set_status(article_id: uint64, new_status: uint8) -> void

Sender: the submitter. Exactly these transitions are allowed; every other one fails:

From To
preprint under_review
preprint retracted
under_review final
under_review retracted
final retracted
disputed retracted

preprint cannot go straight to final, and retracted is final for ever. Only the contract (through flags) moves an article into disputed; governance (resolve_dispute), anyone after 60 days (expire_dispute) and the submitter (by retracting) move it out. A retraction revokes the ASA (one inner transaction: manager, freeze and clawback set to zero). Emits StatusChanged; retracting a disputed article then closes the round with outcome 2 and emits DisputeResolved (resolved_by = the submitter, reasons all zero bytes), so its flaggers’ bonds go back to them.

accept_coauthorship(article_id: uint64) -> void
  • Sender: an address declared in publish that has not confirmed yet. Status live (disputed included).
  • Fails if the sender already voted on the article or reviewed it.
  • Increments author_count. The new author’s share counts from publication (see claim_reputation).
  • Emits CoauthorAccepted. One-time and irreversible; there is no way to add, remove or replace a co-author.

Every grant from one address to another is counted, in one damping counter per ordered pair (giver, receiver): a box keyed d + SHA-256(giver’s 32 bytes + receiver’s 32 bytes), holding the number of earlier grants. The same counter serves every kind of vote and comment resolutions. A grant uses n = the counter + 1, and the counter then becomes n. The counter is created by the giver’s first grant to that receiver (the giver pays its 18 900 microAlgo) and is never deleted.

A vote then weighs w' = w // n, where w = 1 + isqrt(rep) is the voter’s raw weight in the article’s primary field: w' is what the vote box records, what the totals add and what any grant is computed from. A weight-1 voter’s second vote for the same person weighs 0, by design. For example, a voter of weight 11 who marks four reviews of the same reviewer useful grants 33, 15, 9 and 6 (11, 5, 3 and 2, times 3), before the daily cap.

submit_review(pay: pay, article_id: uint64, cid: byte[36], recommendation: uint8) -> uint64
  • Sender: anyone who is not an accepted author of the article. Status live (reviews stay allowed while disputed).
  • One review per reviewer and article. cid is a raw CID (codec 0x55); recommendation is 1 to 4.
  • Creates the review box and the reviewer index box, and the reviewer’s own reputation box in the article’s primary field when it does not exist yet (later “useful” votes are paid into it).
  • Returns review_seq (1, 2, … per article) and emits Reviewed. Reviews are immutable.
vote_review(pay: pay, article_id: uint64, review_seq: uint64) -> void
  • Sender: anyone but the reviewer, once per review. Status live.
  • The vote weighs w // n, with n from the damping counter (voter, reviewer), which then grows by one.
  • Grants 3 × (w // n) to the reviewer, under the daily cap, except when the voter is an accepted author of the article: that vote is recorded, grants 0, and still moves the counter.
  • Creates a vote box, and the damping counter on a first grant. Emits Voted (target 2), then ReputationChanged when something was granted.
vote_article(pay: pay, article_id: uint64, authors: address[]) -> void
  • Sender: anyone but an accepted author, once per article. Status live.
  • authors: every author of the article, the submitter and each declared co-author (confirmed or not), in strictly ascending order of their raw 32 bytes: exactly invited_count + 1 addresses, each with a co-author box of the article.
  • n = 1 + the largest damping counter (voter, author) over the authors; the vote weighs w // n, and every author’s counter grows by one.
  • Adds the damped weight to the article’s vote_total and 1 to vote_count. It touches no reputation box: the authors claim their shares later.
  • Creates a vote box and the missing damping counters. Emits Voted (target 1, target_seq 0) and never ReputationChanged. Many authors need extend calls (above).
claim_reputation(pay: pay, article_id: uint64) -> void
  • Sender: an accepted author. Status live (claims continue while disputed).
  • With N = 1 + the number of declared co-authors and T = vote_total, a confirmed co-author is entitled to floor(T / N) and the submitter to ceil(T / N). The claimable amount is the entitlement minus what the sender already claimed; it must be greater than 0.
  • Grants up to that amount in the primary field, under the daily cap (the grant must be greater than 0), and half of what was granted in the secondary field, if any, under that field’s cap. What the cap holds back stays claimable on a later day; the secondary field’s shortfall is not carried over.
  • Creates the sender’s reputation boxes in the article’s fields when they are missing.
  • Emits ReputationClaimed, then one ReputationChanged per field that received something.

See Reputation and vote weight for the reasoning and worked examples.

comment(pay: pay, article_id: uint64, cid: byte[36], reply_to: uint64, mentions: address[]) -> uint64
  • Sender: anyone, accepted authors included. Allowed in every status, retracted included.
  • cid is a raw CID (codec 0x55). reply_to is 0 for a new thread or the number of an existing comment of the same article; a reply’s depth is its parent’s depth + 1, at most 6.
  • mentions: 0 to 5 addresses in strictly ascending order of their raw 32 bytes, never the sender. They are stored only in the event, for notifications, and carry no reputation.
  • Creates the comment box, increments the parent’s reply count, and creates the commenter’s reputation box in the primary field when it is missing.
  • Returns comment_seq and emits Commented.
vote_comment(pay: pay, article_id: uint64, comment_seq: uint64) -> void
  • Sender: anyone but the comment’s author, once per comment. Status live.
  • The vote weighs w // n, with n from the damping counter (voter, commenter), which then grows by one.
  • Grants floor((w // n) / 5) to the comment’s author under the daily cap (nothing below a damped weight of 5).
  • Creates a vote box, and the damping counter on a first grant. Emits Voted (target 3), then ReputationChanged when something was granted.
resolve_comment(pay: pay, article_id: uint64, comment_seq: uint64) -> void
  • Sender: the submitter. Status live.
  • The comment exists, was not written by an accepted author, and is not resolved yet. Any depth may be resolved, once.
  • When the submitter holds at least 10 reputation in the article’s primary field, grants 5 // n to the comment’s author under the daily cap, with n from the damping counter (submitter, commenter), which then grows by one: 5, 2, 1, 1, 1, then 0, so at most 10 per pair, ever. Below 10 reputation the comment is resolved, nothing is granted and no counter moves.
  • The payment covers the damping counter of a first grant (otherwise it may be 0).
  • Emits CommentResolved, then ReputationChanged when something was granted.
flag(pay: pay, article_id: uint64, reason: uint8) -> void
  • Sender: anyone who is not an accepted author and holds a reputation box in the article’s primary field with at least 10 reputation, created at least 30 days before (floor(latest_timestamp / 86 400) ≥ since + 30). Status live.
  • reason is 1 to 4. One flag per address in the round the flag counts in.
  • Lapse. When the current round has flags, the article is not disputed and latest_timestamp ≥ round_started_at + 30 days, the round has lapsed: the call first closes it (dispute_round + 1, weight 0), and the flag counts in the new round. A flag in a round without flags sets round_started_at.
  • The flag weighs min(1 + isqrt(rep), 10) and is added to the round’s total.
  • Threshold. 30, always. When the total reaches it and the article is not disputed, the article becomes disputed: status_before_dispute and disputed_at are recorded and the round’s outcome box is created with 0 (pending). While disputed, flags are still recorded and the total keeps growing, without a status change.
  • Bond. FLAG_BOND << clearances while latest_timestamp < cleared_at + 365 days, else FLAG_BOND: 1 000 000 microAlgo, or 2, 4, 8 or 16 ALGO after recent clearances by governance. The payment covers the flag box (and the outcome box) plus the bond, which the flag box records and the application holds until settle_flag.
  • Creates a flag box. Emits Flagged, whose new_status gives the status after the call (no StatusChanged).

See Flags, disputes and governance.

These three methods (contract v3) only emit an event: no box, no deposit, network fee only. The indexer projects them and checks identities and DOIs against outside records.

declare_identity(scheme: uint8, value: string) -> void

scheme at least 1 (1 = ORCID iD); value at most 64 bytes; an empty value withdraws the declaration. The format of the iD (its check character) is checked by the web app and the indexer, not by the contract. Emits IdentityDeclared.

declare_doi(article_id: uint64, version: uint16, doi: string) -> void
  • Sender: the submitter of an existing article (since v4, co-authors cannot declare or withdraw a DOI).
  • version is at most the current version; 0 means every version (Zenodo’s concept DOI).
  • A non-empty DOI is 4 to 200 bytes, starts with 10., and needs an article that is not retracted. An empty DOI withdraws the declaration, in any status.
  • Emits DoiDeclared.
follow(target: address, on: bool) -> void

target is neither the zero address nor the sender. on true follows, false unfollows; the event carries the canonical value (0x80 or 0x00) of the bit every ARC-4 decoder reads, whatever byte was sent. Emits Followed.

An ABI call starts with the first 4 bytes of the SHA-512/256 hash of the method signature. These values appear in the compiled program:

Selector Signature
cc694eaa create(address)void
5b672353 add_field(pay,uint16,string)void
78d25f9d propose_governance(address)void
38b454a1 accept_governance()void
b9418e21 resolve_dispute(uint64,uint8,byte[36])void
3fa466d0 expire_dispute(uint64)void
6d29c9f3 settle_flag(uint64,uint16,address)void
fafe4bc1 publish(pay,byte[36],uint16,uint16,uint8,uint64,address[])uint64
21ebf21b extend()void
29d89a27 claim_article(uint64)void
d1536be5 new_version(uint64,byte[36])void
4f5856df set_status(uint64,uint8)void
a1b57ed3 accept_coauthorship(uint64)void
58d1498f vote_article(pay,uint64,address[])void
51410368 claim_reputation(pay,uint64)void
9c1e9523 submit_review(pay,uint64,byte[36],uint8)uint64
81ab418c vote_review(pay,uint64,uint64)void
9151ca27 comment(pay,uint64,byte[36],uint64,address[])uint64
e7c2f56f vote_comment(pay,uint64,uint64)void
424f9961 resolve_comment(pay,uint64,uint64)void
566a854f flag(pay,uint64,uint8)void
cf6986fa declare_identity(uint8,string)void
8baaa94e declare_doi(uint64,uint16,string)void
f7bc7f0c follow(address,bool)void

Events are application logs. Each log is the event’s 4-byte selector (the first 4 bytes of the SHA-512/256 hash of the signature, for example Published(uint8,uint64,uint64,uint64,address,uint16,uint16,uint8,uint64,byte[36],address[],uint64)) followed by the ARC-4 encoding of its fields. Every event starts with schema_version (uint8, 2 in contract v4) and event_seq (uint64), and ends with ts, the block time in Unix seconds (uint64). Logs that start with 151f7c75 are ARC-4 return values, not events.

Event Fields between event_seq and ts Emitted by Selector
FieldAdded field_id uint16, name string add_field ca2a92a7
GovernanceChanged previous_governance address, new_governance address create, accept_governance d6dd5190
GovernanceProposed proposed address propose_governance 0da66bba
Published article_id uint64, asa_id uint64, author address, primary_field uint16, secondary_field uint16, article_type uint8, parent_article uint64, cid byte[36], coauthors address[] publish 67911442
ArticleClaimed article_id uint64, author address claim_article 69359182
Versioned article_id uint64, version uint16, cid byte[36], prev_cid byte[36] new_version 753a5ea8
StatusChanged article_id uint64, from_status uint8, to_status uint8, actor address set_status 4408b701
CoauthorAccepted article_id uint64, coauthor address, author_count uint16, claimed_total uint64 (always 0) accept_coauthorship 17e64480
ReputationClaimed article_id uint64, claimant address, consumed uint64 claim_reputation 713c0488
Reviewed article_id uint64, review_seq uint64, reviewer address, recommendation uint8, cid byte[36] submit_review 1f19b4be
Voted target_kind uint8, article_id uint64, target_seq uint64, voter address, weight uint64, raw_weight uint64, n uint64 vote_article, vote_review, vote_comment cce2908a
Commented article_id uint64, comment_seq uint64, author address, reply_to uint64, root_seq uint64, depth uint8, cid byte[36], mentions address[] comment ec8f4eb9
CommentResolved article_id uint64, comment_seq uint64, resolved_by address resolve_comment ba0859e7
Flagged article_id uint64, dispute_round uint16, flagger address, reason uint8, weight uint64, round_total uint64, new_status uint8, bond uint64, threshold uint64 flag 09804822
DisputeResolved article_id uint64, dispute_round uint16 (the round resolved), outcome uint8, new_status uint8, resolved_by address, reasons byte[36] resolve_dispute, expire_dispute, set_status (retracting a disputed article) 6d4c3a82
FlagSettled article_id uint64, dispute_round uint16, flagger address, outcome uint8, bond_to address, bond uint64, retained uint64 settle_flag ea1e19b9
ReputationChanged address address, field_id uint16, delta uint64, new_total uint64, reason uint8 claim_reputation, vote_review, vote_comment, resolve_comment 452cafe2
IdentityDeclared address address, scheme uint8, value string (empty = withdrawn) declare_identity 3811bc59
DoiDeclared article_id uint64, version uint16 (0 = every version), doi string (empty = withdrawn), declared_by address (always the submitter) declare_doi 381a9269
Followed follower address, target address, on bool follow 14475466

Details that matter when you decode them:

  • event_seq is 1 for the creation event and grows by exactly one with every later event of the application, with no gap. It gives a total order, and an indexer that sees a gap knows it missed events (Cabdell’s indexer then stops rather than go on; see Run an indexer).
  • Order within a call: the canonical event first, then the ReputationChanged events (primary field before secondary field for a claim). vote_article never emits ReputationChanged; a grant of 0 emits none. The submitter’s retraction of a disputed article emits StatusChanged, then DisputeResolved.
  • target_seq is 0 for article votes, the review or comment number otherwise.
  • Voted.weight is the damped weight, the one the vote box records and the totals add; raw_weight is 1 + isqrt(rep) and n the damping divisor (for an article vote, 1 + the largest counter over its authors). With them an indexer rebuilds every damping counter.
  • Flagged.dispute_round is the round the flag counts in (after closing a lapsed round); bond is the bond deposited (doubled after recent clearances); threshold is always 30. Entering disputed emits no StatusChanged: read Flagged.new_status.
  • DisputeResolved.outcome is 1 cleared, 2 retracted, 3 expired; resolved_by is governance, the submitter (own retraction) or whoever expired the dispute; reasons is the raw CID of governance’s published reasons, all zero bytes for an expiry and an own retraction. Leaving disputed through governance or an expiry emits no StatusChanged.
  • FlagSettled.outcome is 0 for a lapsed round (never a dispute), else the round’s outcome. bond_to is the flagger (lapsed, retracted), the article’s submitter (cleared) or the zero address (expired: nothing paid); bond is what went to bond_to (the whole bond, half of it, or 0), the flagger’s deposit refund apart; retained is what the application keeps for ever (0, the other half, or the whole bond). A closed flagger’s deposit is added to the submitter’s bond (cleared) or to retained (expired).
  • Published.coauthors and Commented.mentions are in ascending address order, not in the order of any text.
  • Followed.on is canonical: 0x80 or 0x00.
  • string values are counted in raw bytes by the contract, which accepts any bytes. Decode them as bytes and convert to text without rejecting invalid UTF-8, as the indexer does (D-117).
  • A decoder keys on the selector and schema_version. The v4 signatures differ from those of v3, so a v3 decoder never misreads a v4 log.
  • Size limits per application call: 32 logs and 1 024 bytes, ARC-4 return values included. Whole logs, selector included: Published 122 bytes plus 32 per co-author, Commented 126 plus 32 per mention, Flagged 97, DisputeResolved 101, FlagSettled 112, Voted 94.

The events carry everything needed to rebuild every box of the application, the dispute outcomes, the damping counters, the bonds held and the amounts retained. The contract’s test suite proves it by replaying the logs and rebuilding every box byte for byte; the indexer is built the same way (see Verify without trusting cabdell.press).

Key Type Meaning
governance address the governance address
pending_governance address the address proposed by propose_governance, the zero address when none
article_seq_next uint64 the next article_id, starting at 1
event_seq uint64 the number of events emitted so far (the event_seq of the last one)

Box keys are binary; integers are big-endian; addresses are the raw 32 bytes. The deposit of a box is 2 500 + 400 × (key bytes + value bytes) microAlgo, paid by the caller whose action creates it.

Box Key Key bytes Value Value bytes Deposit Created by
field t + uint16 field_id 3 the name, UTF-8 1 to 96 2 500 + 400 × (3 + name bytes) add_field
article a + uint64 article_id 9 Article 217 92 900 publish
co-author au + uint64 article_id + address 42 CoAuthor 25 29 300 publish (the submitter and each declared co-author)
review r + uint64 article_id + uint64 review_seq 17 Review 85 43 300 submit_review
reviewer index ri + uint64 article_id + address 42 uint64 review_seq 8 22 500 submit_review
comment c + uint64 article_id + uint64 comment_seq 17 Comment 118 56 500 comment
article vote va + uint64 article_id + address 42 Vote 24 28 900 vote_article
review vote vr + uint64 article_id + uint64 review_seq + address 50 Vote 24 32 100 vote_review
comment vote vc + uint64 article_id + uint64 comment_seq + address 50 Vote 24 32 100 vote_comment
flag f + uint64 article_id + uint16 dispute_round + address 43 Flag 25 29 700 flag; deleted by settle_flag
reputation p + address + uint16 field_id 35 Reputation 26 26 900 claim_reputation, submit_review, comment
dispute outcome o + uint64 article_id + uint16 dispute_round 11 uint8 outcome 1 7 300 the flag that opens a dispute
damping counter d + SHA-256(giver + receiver) 33 uint64 earlier grants 8 18 900 the giver’s first grant: vote_article, vote_review, vote_comment, resolve_comment

A reputation box is only ever created by an action of its own address, which pays for it: a vote never creates a reputation box for anyone else, and the damping counters a vote creates are paid by the voter. The flag box is the only box the contract ever deletes; the outcome and damping boxes are never deleted.

The values are ARC-4 static tuples, so their sizes are fixed (each bool takes one byte).

Article (217 bytes):

Offset Field Type Notes
0 author address the submitter, immutable
32 primary_field uint16 immutable
34 secondary_field uint16 immutable, 0 = none
36 article_type uint8 immutable
37 parent_article uint64 immutable, not 0 only for amendments
45 status uint8
46 status_before_dispute uint8 0 unless disputed
47 version uint16 starts at 1
49 current_cid byte[36]
85 prev_cid byte[36] all zeros for version 1
121 created_at uint64
129 updated_at uint64 stamped by calls that change the article box
137 vote_total uint64 sum of the damped vote weights, never decreases
145 vote_count uint64
153 review_seq_next uint64 starts at 1
161 comment_seq_next uint64 starts at 1
169 dispute_round uint16 starts at 1
171 round_flag_weight uint64 flag weight of the current round
179 author_count uint16 1 + confirmed co-authors
181 invited_count uint16 declared co-authors, immutable
183 claimed bool the ASA was claimed
184 asa_id uint64 the article’s ASA, immutable
192 round_started_at uint64 time of the first flag of the current round (the round lapses 30 days later)
200 disputed_at uint64 time the last dispute started (the 14 and 60 days count from it)
208 cleared_at uint64 time of the last clearance by governance (an expiry is not one)
216 clearances uint8 clearances by governance so far, at most 4: the bond doubles for each
Value Fields, in order
CoAuthor (25 bytes) invited_at uint64, accepted bool, accepted_at uint64, claimed_total uint64
Review (85 bytes) reviewer address, cid byte[36], recommendation uint8, created_at uint64, useful_total uint64
Comment (118 bytes) author address, cid byte[36], reply_to uint64, root_seq uint64, depth uint8, created_at uint64, vote_total uint64, reply_count uint64, resolved bool, resolved_at uint64
Vote (24 bytes) weight uint64 (the damped weight), granted uint64 (always 0 for article votes), created_at uint64
Flag (25 bytes) reason uint8, weight uint64, created_at uint64, bond uint64 (the bond held for this flag)
Reputation (26 bytes) rep uint64, updated_at uint64, cap_day uint32, cap_today uint16, since uint32 (the day the box was created)

Version history, mentions and declarations are not stored in boxes: they live only in the events.

publish creates one Algorand Standard Asset per article, an ARC-71 non-transferable asset whose issuer is the application:

Parameter Value
total, decimals 1, 0
default frozen true
unit name, asset name ARIA, Ariadne Article
URL template-ipfs://{ipfscid:1:dag-pb:reserve:sha2-256} (ARC-19)
reserve the digest of the current CID, as an address; new_version moves it
manager the application; the zero address once retracted
freeze the application; the zero address once claimed
clawback always the zero address

After a claim nobody can move the token: clawback and freeze are zero and the holding is frozen. The article box, not the token, is the authority on authorship.

From constants.py. They are compiled into the program and can only change with a new application.

Constant Value Meaning
REP_CAP_DAY 50 most reputation an address can receive per field and day, from all sources
EPOCH_DAY_SECONDS 86 400 a day: floor(block time / 86 400)
GRANT_REVIEW_NUM / DEN 3 / 1 a “useful” vote grants its damped weight × 3 to the reviewer
GRANT_COMMENT_NUM / DEN 1 / 5 a comment upvote grants floor(damped weight / 5) to its author
GRANT_RESOLVED 5 a resolved comment grants 5 // n to its author, from a resolver with reputation 10
MIN_FLAG_REP 10 reputation in the primary field needed to flag, and for a resolution to grant anything
FLAG_THRESHOLD 30 flag weight in one round that makes an article disputed; it never grows
FLAG_WEIGHT_CAP 10 the largest weight of a flag: a dispute needs three flaggers at least
FLAG_BOND 1 000 000 microAlgo held per flag until settle_flag, doubled per clearance by governance (below)
FLAG_REP_AGE_DAYS 30 days the flagger’s reputation box in the primary field must exist
FLAG_WINDOW_DAYS 30 a round that never became a dispute lapses this long after its first flag
DISPUTE_TIMEOUT_DAYS 60 anyone may expire a dispute this long after it started
RETRACT_DELAY_DAYS 14 governance may retract a disputed article only this long after the dispute started
CLEARANCE_MEMORY_DAYS 365 the bond stays doubled this long after the last clearance by governance
MAX_CLEARANCES 4 the bond doubles at most four times (× 16 = 16 ALGO)
MAX_COMMENT_DEPTH 6 deepest reply (a new thread is depth 0)
MAX_MENTIONS 5 addresses mentioned per comment
MAX_COAUTHORS 25 co-authors declared per article
MAX_FIELD_NAME 96 bytes in a field name
MAX_IDENTITY_LEN 64 bytes in a declared identity
MAX_DOI_LEN 200 bytes in a declared DOI (which starts with 10.)
MAX_VERSION 65 535 last version number
MAX_UINT16 65 535 last dispute round (takes no flags)
SCHEMA_VERSION 2 first field of every event
CID_LEN 36 bytes in a CID

A vote’s raw weight is 1 + isqrt(rep) (rep 0 gives 1, 100 gives 11, 10 000 gives 101), using the AVM’s integer square root; a missing reputation box reads as 0. A flag weighs the same, capped at 10. All arithmetic is integer arithmetic.

The size limit of an article folder (20 MB) is not a contract constant: the web app and the indexer enforce it.

Measured on LocalNet (algod 5.0.2, consensus V42) and reported in V4_REPORT.md. The approval program is 7 303 bytes (plus 4 for the clear program, 7 307 of the 8 192 allowed, on 3 extra pages); there are 24 ABI methods and 20 events. The creator’s minimum balance for the application is 557 000 microAlgo. One application call may spend 700 opcodes; the test gate keeps every call under 600, with at most 8 box references and 1 024 log bytes.

“Minimum fees” is the number of network minimum fees the whole group costs when an Ed25519 account signs it, counting the pay transaction and the inner transactions (multiply by the network’s current minimum fee, 1 000 microAlgo when measured). A Falcon-1024 (post-quantum) signature adds 2 minimum fees per transaction it signs.

Method Highest opcodes per application call Application calls Minimum fees (Ed25519)
add_field 137 1 2
propose_governance, accept_governance 66, 57 1 1
resolve_dispute 253 (cleared), 267 (retracted) 1 1, or 2 when retracting
expire_dispute 176 1 1
settle_flag 205 1 2 (retracted, lapsed, expired), 3 (cleared: half the bond to the submitter)
publish, 1 co-author 431 1 3
publish, 3 co-authors 513 1 3
publish, 9 co-authors 771 in total 2 4
publish, 25 co-authors 1 451 in total 4 6
claim_article 155 1 5 (the asset opt-in is a transaction of its own)
new_version 191 1 2
set_status 126; 156 when retracting; 287 when retracting a disputed article 1 1, or 2 when retracting
accept_coauthorship 148 1 1
vote_article, 1 author 368 1 2
vote_article, 26 authors 2 965 in total 7 8
claim_reputation 590 1 2
submit_review 305 1 2
vote_review 501 1 2
comment 503 (a reply with 5 mentions and a new reputation box) 1 2
vote_comment 484 1 2
resolve_comment 457 1 2
flag 361 (closing a lapsed round and opening a dispute in one call) 1 2
declare_identity 69 1 1
declare_doi 133 1 1
follow 70 1 1

The tightest margin is claim_reputation, 590 of 600, which does not grow with any input. For the amounts readers and authors see, in ALGO, see Costs and deposits.