Profiles

Tags

All 18 tags: where each applies, its arguments, and whether it is core or experimental.

A tag gives one position of an interface a meaning. Twelve tags are core: the preregistered eval depends on them, so they are stable within v0. Six are experimental and may change. The tables below are checked against spec §5, the JSON Schema (profile/spec/profile-v0.schema.json, the args_* definitions) and the resolver's applicability checks (checks.mjs). Each tag links to the concept page that explains it.

A tag is one annotation: an address, the tag's name and its arguments. This one is from the ICRC-1 pack:

Profile annotation
at
icrc1_transfer(0).amount
tag
amount
args
  • decimals: { "from": "query:icrc1_decimals" }
  • symbol: { "from": "query:icrc1_symbol" }

Value tags#

A value tag sits at a value address (method(i).path, method->(i).path or, in a canister profile, type:Decl.path; see Addresses). opt is transparent: a tag on fee : opt nat applies to the nat. A tag on a type it does not fit is EE_TAG_NOT_APPLICABLE; a value tag at a bare method name is EE_TAG_TARGET.

TagStatusApplies toArguments
amount core nat, int, nat8, nat16, nat32, nat64, int8, int16, int32, int64 decimals, required unless token is given: an integer 0–255, or { "from": "query:<method>" }.
symbol, optional: text of 1–32 characters, or a query: value.
token, EVM only: a relative path to the address field that names the amount's token; decimals and symbol are then left out (below).
threshold, optional, display only: a decimal string in base units; a value at or above it displays as message and the symbol.
message, optional: 1–32 characters, default "Unlimited"; needs threshold.
timestamp core nat, int, nat32, nat64, int64 unit, required: ns, us, ms or s.
epoch, optional: unix.
threshold with message, optional, display only, always together: a value at or above threshold (a decimal string of the tagged unit) displays as message (1–32 characters, no default).
labels, optional, display only: 1–16 exact values (decimal strings of the tagged unit) mapped to words of 1–32 characters.
duration core as timestamp unit, required: ns, us, ms or s.
account core record { owner : principal; subaccount : opt blob } encoding, optional: icrc1.
subaccount core blob none: args is {}.
bytes core blob encoding, required: utf8, hex or base64.
max_len, optional: an integer ≥ 0, counted in bytes after encoding.
default core any position; the value null only on an opt (EE_DEFAULT_NULL) value, required: a Candid textual literal ("null", "opt 0"), or a dynamic value (query:, now, caller).
mode, required: auto (filled by the tool layer, never exposed) or suggest (exposed, pre-filled).
describe core any position, or a method label: 1–80 characters.
help: 1–2000 characters.
At least one of the two.
text experimental text format: plain, markdown, email or url.
max_len: an integer ≥ 0.
pattern: a regular expression.
At least one of the three.
range experimental a numeric type, text, a vec min, max: decimal strings, in base units.
min_len, max_len: integers ≥ 0.
At least one of the four.
ui experimental any position order (integer), group (text), hidden (boolean), widget (text); at least one. UI only: agents ignore it.
named experimental a position a name from a names set can stand for; its Candid type equals the set's field (EE_NAMED_TYPE) set, required: a declared set (EE_NAMED_SET), matching ^[a-z][a-z0-9_]*$, at most 64 characters.
by, optional: value (default) or show.

Dynamic values#

ValueAllowed inRule
{ "from": "query:<method>" } amount.decimals (a number), amount.symbol (text), default.value A query or composite query of the same canister, with no arguments and one result of the expected kind: EE_REF_UNRESOLVED, EE_REF_NOT_QUERY, EE_REF_SHAPE, EE_REF_TYPE.
{ "from": "now" }, { "from": "caller" } default.value only Anywhere else is EE_DYNAMIC_KIND. now is the canister's clock, never the host's.

Integers that can exceed 253 (amounts, bounds) are decimal strings, such as "100000000". The resolver leaves dynamic values unresolved in the resolved form: resolving them needs the network, so it is the consumer's job (Profile and resolved form).

Not checked by the v0 validator

Whether a default.value literal other than null type-checks against its position. The spec requires consumers to check it before they use a default.

Words for extreme values#

threshold, message and labels change what a value displays as, never what it is: input is unchanged, a number at the threshold still parses to that number, and a word is never parsed back (spec §5.3, D-054 for amount, D-055 for timestamp). On a timestamp a label is checked first, then the threshold, then the time. A timestamp shown as words is never read as a time: it is not localized, and in an ICRC-21 fields message it is Text, never TimestampSeconds; an amount at or above its threshold is Text, never a TokenAmount. The words exist for deadlines whose extreme values are not dates: uint256 max for "never", which is otherwise out of range, and Permit2's expiration 0 for "this block only", which otherwise reads as 1970. From the Permit2 profile:

Profile annotation
at
PermitSingle(0).details.expiration
tag
timestamp
args
  • unit: "s"
  • epoch: "unix"
  • threshold: "281474976710655"
  • message: "Never"
  • labels: { "0": "Only in the block it is used" }

An amount in the token a field names#

experimental EVM only (spec §15.9). amount.token is a relative path from the argument to an address field of the same argument whose path up to its last [] is the amount's own: the token is in the same array element as the amount, or outside every array ("token": "details[].token" for details[].amount). decimals and symbol are not given (the schema refuses them): they are that token's decimals() and symbol(), which the consumer reads and passes per token address (tokens: { "0xA0b8…eB48": { "decimals": 6, "symbol": "USDC" } }); the libraries do no I/O. Without an entry there is no text (EE_REF_UNRESOLVED; §12 rule 10), never a number read in the wrong decimals. A token's symbol() is its own claim, so its address is always shown beside it. A path that breaks these rules is EE_TOKEN_PATH, and so is token on a Candid service, whose ledger a field cannot name yet (open OQ-6, on Open questions). token works on functions too.

Profile annotation
at
PermitBatch(0).details[].amount
tag
amount
args
  • token: "details[].token"
  • threshold: "1461501637330902918203684832716283019655932542975"

Method tags#

A method tag sits at a bare method name. intent and effect may also sit at a case address, an argument address whose last step selects a case of a variant (propose(0).Transfer, D-053); a result address, a type: address or a path through [] is EE_TAG_TARGET, and two tagged cases one value can take both are EE_CASE_OVERLAP.

TagStatusAddressArguments
intent core a method, or a case of a variant in its arguments title, required: 1–60 characters.
template, required: 1–500 characters, the grammar of spec §6 ({relpath} placeholders, [ … ] optional segments, escapes {{ }} [[ ]]).
effect core a method, or a case of a variant in its arguments kind, required: read write transfer approve admin destroy
irreversible, required: boolean.
replicated, optional: boolean; true only with kind: "read" and irreversible: false on an update method (a read-only update, D-038).
grant, optional: an object, what a transfer or approve lets someone take: spender or to, amount, and ends, optional, as argument relpaths. Only on a method's effect, not a case's (EE_GRANT; below, D-056).
idempotency core an update method (EE_IDEMPOTENCY_MODE) mode, required: replay_same_payload.
stamp, required: an argument relpath to the field filled once per intent; it carries a timestamp tag (EE_STAMP_UNTAGGED).
outcome core a method classes: { result relpath → class }; every key selects a variant case (EE_OUTCOME_NOT_CASE).
default: a class.
At least one of the two. Classes: success already_done retry_same retry_new fix_input fail
list experimental a method cursor, required: an argument relpath.
next, items, required: result relpaths.
names experimental a query, composite query or read-only update that takes no arguments (EE_NAMES_MODE, EE_NAMES_SHAPE) set, required: the set's name, declared once (EE_NAMES_SET_DUPLICATE).
items, required: a result relpath to a vec of records.
name, required: a path inside one item, of type text (EE_NAMES_FIELD).
value, required; show, kind, optional: paths inside one item.

Defaults and bounds#

RuleSpec
Without an effect tag, a query or composite query is read; an update is write with irreversible: true. The resolved form records these with "origin": "default". §5.3
A query declared as anything but read is EE_EFFECT_MODE; a read on an update or oneway method is WW_EFFECT_MODE, except the read-only update form. §5.3
A method's effect bounds its cases': when a case effect is transfer, approve, admin or destroy, the method's effect is one of these four, and when a case effect is irreversible, so is the method's (EE_CASE_EFFECT). §5.3, D-053
A result case with no class falls back to default (WW_OUTCOME_UNCLASSIFIED at info level), and is a warning when there is no default. Consumers treat a case with no class and no default as fail. §5.3
classes may be left out when the result has no variant to select; default is then required and classes every reply. §5.3, D-049
Overlapping outcome keys: the most specific (longest path) wins. §5.3

What a call grants#

Spec §5.3, D-056. An effect of kind transfer or approve may carry grant: who may take tokens because of the call or the signature, which token, how many and until when, so that a consumer can compare a request with terms the person confirmed (Confirm terms for a task). Its values are relpaths into the method's arguments, written as an intent template's placeholders are (§6). From the ICRC-2 pack:

Profile annotation
at
icrc2_approve
tag
effect
args
  • kind: "approve"
  • irreversible: false
  • grant: { "spender": "spender", "amount": "amount", "ends": ["expires_at"] }
KeyRequiredThe position it names
spender or to exactly one Who may take the tokens. spender is for an allowance, or for a signature someone else submits; to is for a transfer that pays them directly. An approve's grant names a spender. The position is an address (EVM, §15) or carries an account tag.
amount yes A position with an amount tag, of an unsigned type, where a value at or above the tag's threshold means unlimited; or a bool, where true is unlimited and false grants nothing (DAI's permit, §15.9).
ends no One to eight positions with a timestamp tag. The grant ends at the latest of them, and one at or above its tag's threshold, or an optional one left out, means it never ends. With no ends, an approve never ends and a transfer ends when it is made.
  • spender, to and amount are in every call: neither optional nor inside a case of a variant, and no position of a grant is inside one.
  • No position steps into []: a grant covers one token, so a request that grants several (Permit2's PermitBatch) has none in v0.
  • The token is the one the amount tag names (token, §15.9), else the contract or canister the call is for.
  • A deadline that bounds only when a signature may be used is not an end of what it grants: EIP-2612's deadline and DAI's expiry set allowances that never end, so neither is in ends.
  • The amount, timestamp and account tags a grant needs may come from another profile of the merge: the resolver checks the grant against the merged tags.

A grant on another kind, on a case effect (§5.2), or whose positions break these rules is EE_GRANT. A pack applied to a service where its grant does not hold keeps its effect and loses the grant (II_PACK_ANNOTATION_SKIPPED): dropping the effect would leave a transfer or approve with the default effect. A grant is the author's claim, like any effect (§11.1). The grants the standard packs carry are on Standard packs.

Tags on an EVM contract#

Experimental

EVM support is spec §15, phase 1 and the first part of phase 2: profiles, consent text for calldata and for EIP-712 signing requests, and ERC-7730 export. Agent tools, outcome classes over reverts and idempotency by nonce are later phases. See EVM contracts.

An ABI is read as the graph a .did compiles to, so the same tags apply, with these types (spec §15.4):

TagOn an EVM contract
amount uint8…uint256, int8…int256. query: refs are eth_calls of a view or pure function of the same contract with no argument and one result. token names the token by an address field instead (§15.9).
timestamp, duration uint32…uint256, int64…int256. timestamp words (§5.3) are for EVM deadlines first.
bytes bytes, bytes1…bytes32
text string
range an integer type, string, an array
describe, default, ui, intent, effect as on a canister. A template has no [ ] segments (EE_TEMPLATE): an ABI value is never absent. effect defaults as in §5.3; a grant's spender or to is an address.
account, subaccount none (EE_TAG_NOT_APPLICABLE): an address needs no tag; it is shown with its EIP-55 checksum.
idempotency, outcome, list, names, named not yet (EE_TAG_TARGET): they need nonces, receipts and reads that a later phase adds.

The native value a payable function receives has no position yet (open O2, on Open questions).

Tags on EIP-712 messages#

experimental Spec §15.9, D-055. A contract profile or an EVM pack may bind EIP-712 struct types in binds.eip712.types (Profile and resolved form). A message is a type the same document tags with intent at its bare name; its struct is argument 0, as a function's parameters are, so its members are addressed like a function's:

Profile annotation
at
PermitSingle
tag
intent
args
  • title: "Permit2: allow token spending"
  • template: "Let {spender} spend up to {details.amount}. Allowance expires: {details.expiration}. Signature expires: {sigDeadline}."
TagOn an EIP-712 message
intent At the type's bare name (PermitSingle): this is what makes the type a message. A type without an intent is a type only, and an address that names it is EE_ADDR_UNRESOLVED; its members are reached through a message that contains it (PermitSingle(0).details.amount).
effect A signature runs nothing when it is signed, so effect says what it allows, of any kind: approve for an allowance, transfer for a one-time transfer. Default write, irreversible. replicated does not apply (EE_EFFECT_MODE). Its grant names the struct's members, as its template does (below).
every other tag As on an EVM contract, in the table above (§15.4), at the message's member addresses: the value tags and describe apply, and the tags not yet defined for EVM are refused the same way. amount.token names the token a member holds ("token": "details.token").

A message may not share its name with a function, and two profiles may not give one type name different members (the later profile is invalid): each is EE_EIP712. A signing request matches a message by its type hash, never by its name: DAI's Permit and EIP-2612's share a name and not a hash. The standard messages are on Standard packs.

A message's grant#

Spec §15.9, D-056. A message's grant names its positions as its template does, from the struct. Permit2's PermitSingle:

Profile annotation
at
PermitSingle
tag
effect
args
  • kind: "approve"
  • irreversible: false
  • grant: { "spender": "spender", "amount": "details.amount", "ends": ["details.expiration", "sigDeadline"] }

fact (Uniswap permit2 at main, read 2026-10-30, src/libraries/Allowance.sol, as spec §15.9 records) An expiration of 0 is stored as the time of the block the permit is used in, and that block may come as late as sigDeadline; the latest of the two is the end, so 0 needs no rule of its own. The token is the one amount.token names (details.token), not the verifying contract.

Where to read more#