Arete documentation
Steps, permissions, and lifecycle
For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending.mdto the URL or sendingAccept: text/markdown.
For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /building-protocols/lifecycle.md.
A protocol describes when an action makes sense and who may perform it. A raise can be open for contributions while one wallet has already reached a per-wallet limit; an instance’s state and a participant’s state answer separate questions.
Step scope
Section titled “Step scope”createstarts an instance. The caller supplies the creation arguments.instanceacts on an existing instance, such as closing a deal.participantacts for a wallet in an instance, such as contributing or claiming.
The client supplies an instance id for the latter two scopes. Steps declare
their arguments with schemas; shorthand such as { amount: "u64" } makes
the argument required. Amounts, public keys, and timestamps are strings with
formats such as u64, pubkey, and unixTimestamp.
Guards explain rejection
Section titled “Guards explain rejection”A guard’s name is the rejection code. Its expression must be true:
guards: { amount_zero: "args.amount > 0", not_open: 'instance.state == "open"',},Use stable codes your app can translate into helpful text. Conditions can use
arguments, parameters, declared reads, and current protocol state. They are
checked on prepare. Wallet action discovery cannot finish checks that depend
on arguments the caller has not supplied; it reports these in argsDependent.
An action shown as allowed may still fail preparation if state changes or argument-dependent checks fail. Treat rejection as a normal product outcome.
Allow-lists and list items
Section titled “Allow-lists and list items”in checks membership in a literal list or in an array value, such as an
array resource. Element types must match; a string literal can stand for a
public key:
guards: { mint_not_allowed: "args.mint in resources.allowedMints", unsupported_side: 'args.side in ["bid", "ask"]',},A rejection’s detail carries the operand and the list it was checked against.
To pick one entry of an array, index it with an integer literal and read its
fields: resources.venues[1].reserve. The checker types the field from the
array’s item schema. An index at or past the array’s maxItems fails the
check, and so does an index computed from arguments, such as
resources.venues[args.i]. Every address a step uses must be decided by
fixed names, so use a literal index, or in to accept a value from the list.
Authority and access are separate
Section titled “Authority and access are separate”Authority identifies the signer: the creator, a participant, or a bound
role such as the scaffold’s recipient. Roles are declared in roles and
bound to wallets by the create step.
authority: { kind: "role", role: "recipient" },An admin step can instead be proposed to a team’s Squads v4 multisig. The
multisig must come from the deployment config (a resource), never from the
instance or the step’s arguments. The
optional vaultIndex (an integer from 0 to 255, default 0) picks which vault
of that multisig the step acts from, so protocols that share a multisig can
keep separate vaults.
authority: { kind: "squadsProposer", multisig: "resources.treasury.multisig", vaultIndex: 1 },Access determines which credentials may prepare a wallet step. public
allows a publishable-key client; secret requires a secret-key session.
Create and role steps default to secret; other wallet steps default to
public. Declare access explicitly when it is important to your interface.
A publishable key does not authorize signing, and a secret key does not replace the required wallet’s signature. Keeper and Squads proposal steps are not ordinary client-callable wallet steps.
Recorded and derived state
Section titled “Recorded and derived state”Declare machines under machines.instance and optionally
machines.participant. Each has an initial state and named states.
A recorded state changes through declared transitions. A derived state
is evaluated from conditions, such as a deadline or chain account field.
A time-based rule can declare at as well as when so it is evaluated when
the deadline arrives.
instance: { initial: "open", states: { open: "recorded", expired: { kind: "derived", terminal: true }, closed: { kind: "recorded", terminal: true }, }, derive: [{ from: ["open"], to: "expired", when: "now >= params.deadline", at: "params.deadline", }],},A step can declare transitions: [{ from: ["open"], to: "closed" }].
Preparing or signing it does not itself mean the transition completed: follow
the execution outcome and read the instance again.
Terminal states
Section titled “Terminal states”A terminal instance rejects all further instance and participant steps with
instance_terminal. A terminal participant rejects that wallet’s participant
steps with participant_terminal; it does not close instance steps.
Both include detail.state. A participant step may explicitly declare a
transition from that terminal participant state, including an allowed self-loop.
Design the machine around the actions people still need. For example, marking an entire failed raise terminal would also close its participant refund steps. Keep the instance open for refunds if that is part of your workflow, and make the participant terminal after refunding.
Test the rules, including failures
Section titled “Test the rules, including failures”Check the right wallet, wrong wallet, before and after deadlines, repeated steps, and terminal states. Extend the tip jar for an example using two roles and a clock-controlled outcome.
Protocol guards govern the Arete API. They do not prevent someone calling the underlying program directly. Use programs whose on-chain permissions and asset rules support your intended product.