Skip to content

Arete documentation

Define a protocol

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 .md to the URL or sending Accept: 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/definition.md.

Write protocol.ts to describe your workflow. Start with the first protocol tutorial so you have installed programs, configuration, and an environment in which to run it.

Install the TypeScript SDK for each program your protocol uses:

Terminal window
a4 install program <package> --ts
a4 install program ./idl/my_program.json --as my-program --ts

The installer maps @programs/* in tsconfig.json. Import the installed module and register it under a stable alias in programs. The module carries its program identity and instruction shapes; you do not need to copy hashes or restate the IDL.

This is a complete minimal definition using the scaffold’s System Program:

import system from "@programs/system";
import { defineProtocol, ix } from "@usearete/protocol";
export default defineProtocol({
name: "payment",
version: "0.1.0",
programs: { system },
params: { amount: "u64" },
instanceVisibility: "account",
machines: {
instance: {
initial: "paid",
states: { paid: { kind: "recorded", terminal: true } },
},
},
steps: {
pay: {
scope: "create",
authority: "creator",
args: { recipient: "pubkey", amount: "u64" },
guards: { amount_zero: "args.amount > 0" },
build: [
ix(system, "transfer", {
args: { lamports: "args.amount" },
accounts: {
fundingAccount: "wallet",
recipientAccount: "args.recipient",
},
}),
],
},
},
});

args describes what a caller supplies to a step. params describes values retained by an instance; create-step arguments with those names supply the instance parameters. Use decimal strings for integer values at API boundaries.

ix takes an installed program, its IDL instruction name, and expressions for arguments and accounts. Expressions such as args.amount, wallet, and instance.accounts.vault are resolved when preparing the step. The expression reference lists every function, operator and name, where each name is in scope, and every check rejection with its fix.

Instruction, argument, and account names come from the installed IDL. TypeScript catches unknown names and missing required values. Derivable or optional accounts can be omitted when the generated instruction shape permits it. The build checks templates again, including plain JavaScript input.

A template can contain several instructions. For IDLs with trailing accounts, add remainingAccounts in the required order:

remainingAccounts: [
{ pubkey: "instance.accounts.quoteVault", isWritable: true },
{ pubkey: "params.quoteMint" },
],

Remaining accounts cannot be signers. Use a handler when dynamic construction cannot be expressed with templates.

A step that only moves the state machine, such as close, pause or reopen, has an empty template. Don’t add a placeholder instruction:

close: {
scope: "instance",
authority: { kind: "role", role: "operator" },
guards: {
not_operator: "wallet == instance.roles.operator",
not_open: 'instance.state == "open"',
},
transitions: [{ machine: "instance", from: ["open"], to: "closed" }],
build: [],
},

Its transaction carries only the attested memo, plus any fee or glue instructions the step declares. The wallet signs and submits it like any other step, and the transition applies when it confirms. A squadsProposer step can’t be state-only: it proposes its instructions.

The create step’s bind records identities that later steps use. The scaffold keeps the creator and names a recipient role:

bind: {
accounts: { creator: "wallet" },
roles: { recipient: "args.recipient" },
},

Later expressions use instance.accounts.creator and instance.roles.recipient. Declare the role under roles as the scaffold does. An ephemeral declaration names fresh accounts needed during construction; protocol code does not return or store their private keys.

The protocol’s generated stack provides instance and participant views and can track the activity declared in stack.activity. The scaffold demonstrates mapping its open and close transfers into activity with an instance account, wallet account, and amount argument.

Choose those mappings to match the program’s real account relationships. A shared wallet is not an instance-owned account: the tip-jar scaffold tracks its Arete executions rather than treating every transfer to that wallet as protocol activity. Do not assume every direct program interaction can be attributed to an instance.

For additional joined or computed data, author custom live views and run them alongside your protocol.

Terminal window
npx arete-protocol check
npx arete-protocol build

The build writes the canonical spec, optional handler bundle, and generated stack source to .arete/protocol/. Installed program identities are checked against arete.lock; after changing a dependency, run a4 install or update it, then rebuild. Do not edit the generated artifacts.

Continue with steps and lifecycle and automation and data.