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.
What changed in v4
Section titled “What changed in v4”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_flagsettles 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_articletakes the list of the article’s authors;resolve_commenttakes a payment and grants only from a resolver with reputation 10. - Governance changes in two steps (
propose_governance,accept_governance);set_governanceis gone. - Amendments only by an accepted author of the parent; DOIs only by the submitter.
- Events carry
schema_version2 and a gap-freeevent_seq;Voted,Flagged,DisputeResolved,FieldAddedgain fields;FlagSettledandGovernanceProposedare new.
Conventions
Section titled “Conventions”| 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. |
Enumerations
Section titled “Enumerations”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 |
Methods at a glance
Section titled “Methods at a glance”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
Section titled “Governance”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
Section titled “create”create(governance: address) -> voidCreation 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
Section titled “add_field”add_field(pay: pay, field_id: uint16, name: string) -> void- Sender: the governance address.
field_idis not 0 and not registered yet;nameis 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
Section titled “propose_governance”propose_governance(new_governance: address) -> void- Sender: the current governance address.
new_governanceis 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
Section titled “accept_governance”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
Section titled “resolve_dispute”resolve_dispute(article_id: uint64, outcome: uint8, reasons: byte[36]) -> void- Sender: the governance address. The article must be
disputed. outcome1 (cleared), at any time: restores the status the article had before the dispute.outcome2 (retracted), only oncelatest_timestamp ≥ disputed_at + 14 days: retracts the article and revokes its ASA (one inner asset-configuration transaction).reasons: the raw CID (codec0x55) 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
DisputeResolvedwithresolved_by= the governance address and the reasons. NoStatusChangedis emitted.
Disputes
Section titled “Disputes”Ending a dispute
Section titled “Ending a dispute”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
Section titled “expire_dispute”expire_dispute(article_id: uint64) -> void- Sender: anyone. The article must be
disputed, andlatest_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
DisputeResolvedwithresolved_by= the caller andreasonsall zero bytes.
settle_flag
Section titled “settle_flag”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 andlatest_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.
Publishing
Section titled “Publishing”publish
Section titled “publish”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 (codec0x70).primary_fieldis a registered field;secondary_fieldis 0 or a registered field different from the primary.article_typeis 1 to 5.parent_articleis 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_idand emitsPublished. The article starts aspreprint, 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
Section titled “extend”extend() -> voidChanges 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
Section titled “claim_article”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
Section titled “new_version”new_version(article_id: uint64, cid: byte[36]) -> void- Sender: the submitter. Status
preprintorunder_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_cidto the old CID. - Emits
Versioned. The whole version history is in these events.
set_status
Section titled “set_status”set_status(article_id: uint64, new_status: uint8) -> voidSender: 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.
Co-authorship
Section titled “Co-authorship”accept_coauthorship
Section titled “accept_coauthorship”accept_coauthorship(article_id: uint64) -> void- Sender: an address declared in
publishthat 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 (seeclaim_reputation). - Emits
CoauthorAccepted. One-time and irreversible; there is no way to add, remove or replace a co-author.
Damping
Section titled “Damping”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.
Reviews
Section titled “Reviews”submit_review
Section titled “submit_review”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.
cidis a raw CID (codec0x55);recommendationis 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 emitsReviewed. Reviews are immutable.
vote_review
Section titled “vote_review”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, withnfrom 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), thenReputationChangedwhen something was granted.
Votes and reputation
Section titled “Votes and reputation”vote_article
Section titled “vote_article”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: exactlyinvited_count + 1addresses, each with a co-author box of the article.n= 1 + the largest damping counter (voter, author) over the authors; the vote weighsw // n, and every author’s counter grows by one.- Adds the damped weight to the article’s
vote_totaland 1 tovote_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_seq0) and neverReputationChanged. Many authors needextendcalls (above).
claim_reputation
Section titled “claim_reputation”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 andT=vote_total, a confirmed co-author is entitled tofloor(T / N)and the submitter toceil(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 oneReputationChangedper field that received something.
See Reputation and vote weight for the reasoning and worked examples.
Comments
Section titled “Comments”comment
Section titled “comment”comment(pay: pay, article_id: uint64, cid: byte[36], reply_to: uint64, mentions: address[]) -> uint64- Sender: anyone, accepted authors included. Allowed in every status,
retractedincluded. cidis a raw CID (codec0x55).reply_tois 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_seqand emitsCommented.
vote_comment
Section titled “vote_comment”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, withnfrom 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), thenReputationChangedwhen something was granted.
resolve_comment
Section titled “resolve_comment”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 // nto the comment’s author under the daily cap, withnfrom 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, thenReputationChangedwhen 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. reasonis 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 setsround_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_disputeanddisputed_atare 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 << clearanceswhilelatest_timestamp < cleared_at + 365 days, elseFLAG_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 untilsettle_flag. - Creates a flag box. Emits
Flagged, whosenew_statusgives the status after the call (noStatusChanged).
See Flags, disputes and governance.
Declarations
Section titled “Declarations”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
Section titled “declare_identity”declare_identity(scheme: uint8, value: string) -> voidscheme 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
Section titled “declare_doi”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).
versionis 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
Section titled “follow”follow(target: address, on: bool) -> voidtarget 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.
Method selectors
Section titled “Method selectors”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 (ARC-28)
Section titled “Events (ARC-28)”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_seqis 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
ReputationChangedevents (primary field before secondary field for a claim).vote_articlenever emitsReputationChanged; a grant of 0 emits none. The submitter’s retraction of a disputed article emitsStatusChanged, thenDisputeResolved. target_seqis 0 for article votes, the review or comment number otherwise.Voted.weightis the damped weight, the one the vote box records and the totals add;raw_weightis1 + isqrt(rep)andnthe damping divisor (for an article vote, 1 + the largest counter over its authors). With them an indexer rebuilds every damping counter.Flagged.dispute_roundis the round the flag counts in (after closing a lapsed round);bondis the bond deposited (doubled after recent clearances);thresholdis always 30. Enteringdisputedemits noStatusChanged: readFlagged.new_status.DisputeResolved.outcomeis 1 cleared, 2 retracted, 3 expired;resolved_byis governance, the submitter (own retraction) or whoever expired the dispute;reasonsis the raw CID of governance’s published reasons, all zero bytes for an expiry and an own retraction. Leavingdisputedthrough governance or an expiry emits noStatusChanged.FlagSettled.outcomeis 0 for a lapsed round (never a dispute), else the round’s outcome.bond_tois the flagger (lapsed, retracted), the article’s submitter (cleared) or the zero address (expired: nothing paid);bondis what went tobond_to(the whole bond, half of it, or 0), the flagger’s deposit refund apart;retainedis what the application keeps for ever (0, the other half, or the whole bond). A closed flagger’s deposit is added to the submitter’sbond(cleared) or toretained(expired).Published.coauthorsandCommented.mentionsare in ascending address order, not in the order of any text.Followed.onis canonical:0x80or0x00.stringvalues 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:
Published122 bytes plus 32 per co-author,Commented126 plus 32 per mention,Flagged97,DisputeResolved101,FlagSettled112,Voted94.
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).
Storage
Section titled “Storage”Global state
Section titled “Global state”| 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.
Box values
Section titled “Box values”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.
The article token (ASA)
Section titled “The article token (ASA)”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.
Constants
Section titled “Constants”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 costs
Section titled “Measured costs”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.