Skip to content

Arete documentation

Protocol expression reference

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 /reference/protocol-expressions.md.

Guards, derived states, reaction triggers, binds, glue, fees and instruction templates are written as expressions. This page is generated from the reference that Arete’s protocol checker exports, so it lists exactly what an upload accepts. npx arete-protocol check runs a local check of syntax and names. With a secret key, npx arete-protocol check, a4 protocol deploy --dry-run and the MCP tool protocol_check also run the server’s checks, without uploading anything.

  • Integers are written as numbers: 1000, -3. Protocol integer math is exact and fails on overflow or division by zero; there are no floats. A float literal such as 1.5 parses but fails when evaluated.
  • u64 and larger values arrive in JSON as decimal strings (an arg with format: "u64" is "1500"). They are typed as integers, so args.amount >= 1000 works, and comparisons are by value.
  • Strings are JSON strings: "vault-", with JSON escapes.
  • Public keys are base58 strings. A string literal is accepted wherever a public key is expected: wallet == "So11111111111111111111111111111111111111112".
  • Booleans and null: true, false, null (also none).
  • Byte arrays are written [1, 2, 3]: integers from 0 to 255, for seeds and byte arguments. After a value, brackets index it instead: resources.venues[1].reserve (see Operators). After in, they hold a list of values: args.side in ["bid", "ask"].

All binary operators are left-associative; a higher precedence binds tighter. Use parentheses to group. Comparisons other than == and != compare integers.

OperatorPrecedenceReturnsMeaning
x[<integer literal>] (postfix)13the array’s item typeOne item of an array. The index must be an integer literal (an index computed from args fails the check: use a literal index, or in for membership), and an index at or past the array’s maxItems fails as index-out-of-range. Missing items are null. Example: resources.allowedMints[0].
x[<integer literal>].<field> (postfix)13the field’s schema typeA field of an indexed item, typed from the array’s item schema (items.properties). Example: resources.venues[1].reserve.
! (prefix)12boolLogical not; binds tighter than every binary operator.
*11the type of the left sideMultiply.
/11the type of the left sideDivide. In protocols, integer division truncates and fails on division by zero.
%11the type of the left sideRemainder.
+10the type of the left sideAdd. In protocols, integer math is exact and fails on overflow.
-10the type of the left sideSubtract.
<<9the type of the left sideShift left.
>>9the type of the left sideShift right.
<8boolLess than (integers).
<=8boolLess than or equal (integers).
>8boolGreater than (integers).
>=8boolGreater than or equal (integers).
==7boolEqual. Integers compare by value, whether written as numbers or decimal strings.
!=7boolNot equal.
in7boolMembership: x in [a, b] is x == a || x == b; x in resources.allowed checks every element of an array value. Element types must match (a string literal can stand for a public key). Protocols only.
&6the type of the left sideBitwise and.
^5the type of the left sideBitwise exclusive or.
|4the type of the left sideBitwise or.
&&3boolLogical and; the right side is evaluated only when the left is true.
||2boolLogical or; the right side is evaluated only when the left is false.
??1the type of the left sideThe left side, or the right side when it is null. The right side must be a literal.

40 functions are available in protocols. Arguments marked integer literal or string literal must be written as literals.

FunctionReturnsWhat it doesExample
if(condition, then, else)the type of thenthen when the condition is true, otherwise else. Both branches must have compatible types.if(args.amount > 1000, args.amount, 1000)
some(value)the type of value, nullableMarks a value as optional (Option::Some).some(args.amount)
exists(value)boolTrue when the value is not null.exists(reads.vault.amount)
is_some(value)boolTrue when the value is not null (the same as exists).is_some(reads.vault.amount)
is_none(value)boolTrue when the value is null.is_none(reads.vault.amount)
is_null(value)boolTrue when the value is null (the same as is_none).is_null(reads.vault.amount)
abs(n)the type of n, nullableAbsolute value; null when n is null.abs(params.rate)
min(a, b)the type of a, nullableThe smaller of two numbers; null when either is null.min(args.amount, reads.vault.amount)
max(a, b)the type of a, nullableThe larger of two numbers; null when either is null.max(args.amount, 1000)
ui_amount(amount, decimals)f64A raw token amount scaled down by its decimals. Checks, but protocol evaluation rejects it: avoid it in protocols.ui_amount(args.amount, params.decimals)
raw_amount(amount, decimals)u64A UI token amount scaled up by its decimals. Checks, but protocol evaluation rejects it: avoid it in protocols.raw_amount(args.amount, params.decimals)
concat(a, b, ...)stringJoins two or more scalars into a string; null when any part is null.concat(args.label, "-", args.amount)
starts_with(text, prefix)bool?Whether text begins with prefix; null when either is null.starts_with(args.label, "vault-")
split_part(text, separator, position)string?The position-th field (1-based) of text split on separator; null when out of range.split_part(args.label, ":", 2)
utf8(bytes)string?Bytes as UTF-8 text; null when they are not valid UTF-8.utf8(args.seed)
len(value)u64The number of items in an array, or of bytes in a string.len(args.seed)
sum(array)u64The sum of an array of integers.sum(args.seed)
index(array, i)anyThe item at a literal, 0-based position; null when out of range. The result is untyped.index(args.seed, 0)
slice(array, start, end)the type of arrayThe items from start up to (not including) end, both literal and clamped to the array.slice(args.seed, 0, 8)
u64_from_le_bytes(bytes)u64Reads 8 little-endian bytes as a u64. Checks, but protocol evaluation rejects it: avoid it in protocols.u64_from_le_bytes(reads.vault.data)
u64_from_be_bytes(bytes)u64Reads 8 big-endian bytes as a u64. Checks, but protocol evaluation rejects it: avoid it in protocols.u64_from_be_bytes(reads.vault.data)
keccak256(bytes)bytesThe Keccak-256 hash of the bytes. Checks, but protocol evaluation rejects it: avoid it in protocols.keccak256(args.seed)
slot()u64The slot of the update being processed. Checks, but protocol evaluation rejects it: avoid it in protocols.slot()
timestamp()i64The current Unix time in seconds (in protocols, the same as now).timestamp()
pda(program, name, [seed, ...])pubkeyDerives a PDA the program’s IDL names: the program alias, the PDA’s name, then its non-constant seeds in order. A PDA with no non-constant seeds takes none.pda("vault", "escrow", wallet, args.amount)
ata(owner, mint)pubkeyThe classic SPL associated token account of owner for mint.ata(wallet, resources.mint)
u8(value)u8Converts a value to this type; null stays null. Integer casts fail when the value is out of range.u8(params.decimals)
u16(value)u16Converts a value to this type; null stays null. Integer casts fail when the value is out of range.u16(params.decimals)
u32(value)u32Converts a value to this type; null stays null. Integer casts fail when the value is out of range.u32(args.amount)
u64(value)u64Converts a value to this type; null stays null. Integer casts fail when the value is out of range.u64(params.rate)
u128(value)u128Converts a value to this type; null stays null. Integer casts fail when the value is out of range.u128(args.amount)
i8(value)i8Converts a value to this type; null stays null. Integer casts fail when the value is out of range.i8(params.decimals)
i16(value)i16Converts a value to this type; null stays null. Integer casts fail when the value is out of range.i16(params.decimals)
i32(value)i32Converts a value to this type; null stays null. Integer casts fail when the value is out of range.i32(params.rate)
i64(value)i64Converts a value to this type; null stays null. Integer casts fail when the value is out of range.i64(args.amount)
i128(value)i128Converts a value to this type; null stays null. Integer casts fail when the value is out of range.i128(args.amount)
f32(value)f32Converts a value to this type; null stays null. Integer casts fail when the value is out of range. Checks, but protocol evaluation rejects it: avoid it in protocols.f32(args.amount)
f64(value)f64Converts a value to this type; null stays null. Integer casts fail when the value is out of range. Checks, but protocol evaluation rejects it: avoid it in protocols.f64(args.amount)
string(value)stringConverts a value to this type; null stays null. Integer casts fail when the value is out of range.string(args.amount)
bool(value)boolConverts a value to this type; null stays null. Integer casts fail when the value is out of range.bool(exists(reads.vault.amount))

exp, ln, pow are float math, available in stack sources but not in protocols.

FunctionArgumentType
ifconditionbool
ifthenany
ifelseany
somevalueany
existsvalueany
is_somevalueany
is_nonevalueany
is_nullvalueany
absninteger
minainteger
minbinteger
maxainteger
maxbinteger
ui_amountamountnumber
ui_amountdecimalsinteger
raw_amountamountnumber
raw_amountdecimalsinteger
concatascalar
concatbscalar
starts_withtextstring
starts_withprefixstring
split_parttextstring
split_partseparatorstring
split_partpositioninteger literal
utf8bytesbytes
lenvalueany
sumarrayarray
indexarrayarray
indexiinteger literal
slicearrayarray
slicestartinteger literal
sliceendinteger literal
u64_from_le_bytesbytesbytes
u64_from_be_bytesbytesbytes
keccak256bytesbytes
pdaprogramstring literal
pdanamestring literal
pdaseedany
ataownerpubkey
atamintpubkey
u8valueany
u16valueany
u32valueany
u64valueany
u128valueany
i8valueany
i16valueany
i32valueany
i64valueany
i128valueany
f32valueany
f64valueany
stringvalueany
boolvalueany
NameTypeWhat it is
nowi64The current Unix time in seconds.
deployment.idstringThe deployment’s id.
deployment.attestationpubkeyThe deployment’s attestation public key.
deployment.squadsProposerpubkey?The calling account’s Squads proposer; null when no step uses squadsProposer.
params.<name>the param’s schema typeAn instance param, fixed when the instance is created (nested object fields as params.<name>.<field>).
resources.<name>the resource’s schema typeA deployment resource, published before activation (nested fields as resources.<name>.<field>).
walletpubkeyThe wallet calling the step (in a participant machine, the participant’s wallet).
args.<name>the arg’s schema typeAn argument of the step being called.
ephemeral.<name>pubkeyA fresh keypair the step allocates (step.ephemeral).
reads.<read>.existsboolWhether the read account exists.
reads.<read>.addresspubkeyThe read account’s address.
reads.<read>.lamportsu64The read account’s lamports.
reads.<read>.amountu64A token account read’s balance (tokenAccount reads).
reads.<read>.mintpubkeyA token account read’s mint (tokenAccount reads).
reads.<read>.ownerpubkeyA token account read’s owner (tokenAccount reads).
reads.<read>.<field>anyA decoded field of a program account read (account reads), typed by the program’s IDL at run time.
instance.idstringThe instance’s id.
instance.statestringThe instance machine’s current state.
instance.accounts.<name>pubkeyAn account the create step bound (create step bind.accounts).
instance.roles.<role>pubkeyThe wallet holding a role.
participant.idstringThe participant’s id.
participant.statestringThe participant machine’s current state.
participant.walletpubkeyThe participant’s wallet.

The checker gives each expression the names of the place it sits in. A name outside its place is an unknown-reference. “When declared” roots exist only if the spec declares them: args.* when the step has args, reads.* for the reads the step or machine lists, and so on.

SiteWhereAlways in scopeWhen declaredNotes
create-step/steps/<step>/guards/<i>/expr
/steps/<step>/bind/accounts/<name>
/steps/<step>/bind/roles/<role>
/steps/<step>/fee/asset/mint
/steps/<step>/glue/atas/<i>/owner
/steps/<step>/glue/atas/<i>/mint
/steps/<step>/glue/wrapSol/amount
/steps/<step>/build/instructions/<i>/args/<name>
/steps/<step>/build/instructions/<i>/accounts/<name>
/steps/<step>/build/instructions/<i>/remainingAccounts/<k>/pubkey
now, deployment, walletparams, resources, args, ephemeral, readsA step with scope create. No instance exists yet, so instance.* is out of scope.
instance-step/steps/<step>/guards/<i>/expr
/steps/<step>/bind/accounts/<name>
/steps/<step>/bind/roles/<role>
/steps/<step>/fee/asset/mint
/steps/<step>/glue/atas/<i>/owner
/steps/<step>/glue/atas/<i>/mint
/steps/<step>/glue/wrapSol/amount
/steps/<step>/build/instructions/<i>/args/<name>
/steps/<step>/build/instructions/<i>/accounts/<name>
/steps/<step>/build/instructions/<i>/remainingAccounts/<k>/pubkey
now, deployment, wallet, instanceparams, resources, args, ephemeral, readsA step with scope instance.
participant-step/steps/<step>/guards/<i>/expr
/steps/<step>/bind/accounts/<name>
/steps/<step>/bind/roles/<role>
/steps/<step>/fee/asset/mint
/steps/<step>/glue/atas/<i>/owner
/steps/<step>/glue/atas/<i>/mint
/steps/<step>/glue/wrapSol/amount
/steps/<step>/build/instructions/<i>/args/<name>
/steps/<step>/build/instructions/<i>/accounts/<name>
/steps/<step>/build/instructions/<i>/remainingAccounts/<k>/pubkey
now, deployment, wallet, instance, participantparams, resources, args, ephemeral, readsA step with scope participant.
instance-machine/machines/<machine>/derive/<i>/when
/machines/<machine>/derive/<i>/at
/reactions/<i>/trigger/time
/reactions/<i>/trigger/condition
now, deployment, instanceparams, resources, readsDerivations of the instance machine and reactions on it. reads are the machine’s reads. No wallet and no args.
participant-machine/machines/<machine>/derive/<i>/when
/machines/<machine>/derive/<i>/at
/reactions/<i>/trigger/time
/reactions/<i>/trigger/condition
now, deployment, instance, participant, walletparams, resources, readsDerivations of the participant machine and reactions on it; wallet is the participant’s wallet.
read-address/reads/<read>/addressdeploymentparams, resources, wallet, args, ephemeral, instance, participantChecked once for every step or machine that uses the read, with that user’s names minus now and reads. A name must be in scope at every use. A read no step or machine uses is not checked.
squads-multisig/steps/<step>/authority/multisignoneresourcesA step’s names, but the expression may read only resources.* and literals, and must read at least one resource.

Each expression may be at most 2048 bytes, 1024 nodes (literals, names, operators and calls) and 64 levels of nesting. Over a limit, the check answers source-limit-exceeded.

Instead ofWrite
resources.venues[args.i] (an index computed from args)A literal index, resources.venues[1], or args.venue in resources.venues to accept any entry, so every address stays reviewable
index(list, 0).fieldlist[0].field: index() returns an untyped value, so no field can follow it
cond ? a : bif(cond, a, b)
x.len(), x.startsWith(y) (methods)len(x), starts_with(x, y)
-x on a name0 - x (a leading - is only part of a number literal)
Float math (1.5 * x, pow)Scaled integer math: x * 15 / 10
A PDA the program’s IDL does not nameOnly the IDL’s named PDAs: pda("alias", "name", seeds...)
Variables or letRepeat the sub-expression, or derive a machine state from it
x ?? y with a non-literal defaultA literal default (x ?? 0), or if(exists(x), x, y)

An upload (and the server check) answers with a problem whose code names the stage and whose errors list names each failure: path (a JSON pointer into the spec), code and message.

CodeWhenFix
protocol-invalid-documentThe spec does not match the contract schema. contractCode and path say whereRebuild with the current @usearete/protocol; fix the field at path
protocol-spec-check-failedAn expression failed its check. Each errors entry has one of the expression codes belowFix each entry at its path
protocol-template-check-failedA template does not match the program it calls. Each errors entry has one of the template codes belowFix each entry at its path
protocol-bundle-mismatchA handler step without handlers.mjs, or a bundle with a template-only specUpload exactly the build’s files
protocol-bundle-invalid, protocol-bundle-too-largeThe bundle is not canonical base64, or is over the size limitUpload the build’s handlers.mjs
protocol-template-check-pendingActivation: Arete does not have a program spec a template pins yet. errors names each one (protocol-program-spec-missing)Publish or install the program, then activate again
protocol-resource-unpublishedActivation: a resource the spec declares has no published version. errors names each onePublish a value for each resource
protocol-fee-policy-missingActivation: the deployment’s fee policy is missingCreate the deployment with a fee policy
protocol-review-requiredActivation of a release with handler codeAn operator reviews the release first
plan-capability-requiredThe account’s plan cannot create protocolsSee a4 auth whoami
CodeCauseFix
invalid-expressionThe expression does not parse, a function got the wrong number of arguments, an argument that must be a literal is not one, or an index is not an integer literal.Fix the syntax at the reported offset; check the function’s arguments in this reference. Index with a literal (resources.venues[1]), or use in to accept any value of a list.
index-out-of-rangeA literal index is at or past the array’s maxItems, so it can never hold a value.Use an index below maxItems, or raise maxItems in the array’s schema.
unknown-referenceA name that is not in scope at this place in the spec.Check the scope table: declare the arg, read, resource or param, or move the expression to a site where the name exists.
unsupported-featureA function, operator or character the language does not have, float math in a protocol, or a ?? default that is not a literal.Use a listed function or operator; see what is not supported, with its workaround.
expression-type-mismatchThe expression has the wrong type for its place (a guard that is not boolean, an account that is not a public key, a wrap amount that is not an integer) or a function got the wrong argument type.Compare the message’s expected and found types; cast with u64(…), string(…), etc. where a conversion is meant.
source-limit-exceededThe expression is over a limit: source bytes, nodes or nesting depth.Split the logic: derive a value in a machine state, or move a constant into a resource.
squads-multisig-not-from-resourcesA Squads step’s multisig reads something other than resources.* and literals.Declare the multisig as a resource and use resources.<name>.
CodeCauseFix
template_program_spec_missingThe template’s program alias is not declared in the spec’s programs, so there is no pinned ProgramSpec to check it against.Declare the program: install it and use its alias, or declare a builtin with builtins(…) (system, splToken, associatedToken, computeBudget, memo). Registry names such as spl-ata map to the builtin associatedToken.
template_unknown_instructionThe program’s IDL has no instruction with this name.Use the instruction name from the IDL (camelCase and snake_case both match).
template_unknown_argThe template supplies an argument the instruction does not take.Remove it, or correct its name to the IDL’s.
template_missing_argThe template does not supply one of the instruction’s arguments.Add every IDL argument to the template’s args.
template_arg_type_mismatchAn argument’s expression has a type the IDL argument cannot take.Cast or change the expression to the IDL type.
template_unknown_accountThe template names an account the instruction does not have.Remove it, or correct its name to the IDL’s.
template_missing_accountA required account is neither supplied nor derivable from the IDL (a PDA or a known program address).Supply it in the template’s accounts.

Template codes that appear when a step is prepared:

CodeCauseFix
template_unknown_stepThe step being prepared does not exist.Call a step the deployment’s spec declares.
template_not_a_templateThe step builds with a handler, not a template.Internal: report it.
template_evaluation_failedAn expression failed while building the transaction (a null where a value is needed, an overflow, a missing account).Read the message; guard against the case in the step’s guards.
template_uncheckedAn expression was not checked at upload.Internal: report it.
template_encode_failedA value does not fit the IDL type when the instruction is encoded (out of range, wrong length).Bound the value with a guard, or cast it to the IDL type.

Other prepare and submit answers:

CodeWhenFix
guard_rejectedA guard was falseThe guard field names it; check the step’s preconditions with protocol_actions
instance_terminal, participant_terminalThe instance or the wallet’s participant has finishedUse another instance
step_not_client_callableThe step is a keeper or admin stepKeepers and operators run it
step_requires_secret_keyThe step is secretPrepare it with a secret-key session
step_scope_mismatchThe step is not prepared on this route (a create step on an instance, say)Use the route for the step’s scope
protocol-args-invalidThe args do not match the step’s args schemaFix the field the message names
protocol-deployment-inactiveThe deployment is not activeActivate it
transaction-entitlement-requiredThe account cannot use the managed transaction gatewaySee a4 auth whoami
idempotency_key_conflictThe idempotency key was used for a different requestUse a new key
execution_intent_expiredThe prepared execution expired before submitPrepare it again