What is Meant?

Two layers for one job: the interface says what a call's values are, the profile says what they mean.

Here is one line of an ICRC-1 ledger's interface, and the line of the ICRC-1 profile pack that gives it a meaning:

candidprofile/packages/consent/test/fixtures/ledger.did
type TransferArg = record {
  // …
  amount : Tokens;
  // …
};
Profile annotation
at
icrc1_transfer(0).amount
tag
amount
args
  • decimals: query:icrc1_decimals
  • symbol: query:icrc1_symbol

The interface says the amount is a nat. It does not say that the ledger counts in units of 10-8, so that "1.5 ICP" is 150000000 and a guess of 6 decimals sends a hundredth of that. It does not say that created_at_time is in nanoseconds and is what makes a retry safe, or that Err.Duplicate means the payment already went through. Agents, wallets and user interfaces each guess, and each guesses differently. A profile writes the meaning down once, bound to the interface, and every consumer reads the same answer.

Two layers#

The interface layer answers "what is this interface?" exactly. A .did file is text: two files can describe the same callable interface with different type names, field order and comments. Compiled, both get the same interface_id, and a change that matters to a caller moves it. The graph also gives every value a position that can be named: icrc1_transfer(0).amount is the amount field of the first argument of icrc1_transfer, whatever the type that holds it is called. The interface layer →

The profile layer answers "what does this call mean?". A profile is a JSON document of annotations, each an address, a tag and its arguments. It binds to an interface_id and never changes it, the way a sidecar describes a file without editing it. A profile can come from the canister's author, from a third party you trust, from a standard pack that matches the interface by subtyping, or from a model that proposes one for a person to approve; the four layers merge per tag and per position. Profiles, layers and binding →

What one profile gives you#

Consumers never resolve addresses themselves. @meantjs/profile merges every layer that applies and writes the resolved form: one tree per method, with its tags attached to each position. Everything else reads that.

ConsumerWhat it gets from the profileWhere
Agent tools (MCP)Input in human units, filled defaults, a preview the person confirms, safe retries, classified outcomes@meantjs/tools, @meantjs/reconcile
ICRC-21 consent messagesAn intent line and typed fields, for any method@meantjs/consent
User interfacesParsing and display of amounts, times, accounts and bytes; labels and help text@meantjs/format, meant-format
Typed Candid in TypeScriptSchemas, validation and a binary codec for the interface itself@candid-core/schema
The model sees1.5 ICP
amount: "1.5 ICP"
The call sends150000000
amount = 150_000_000 : nat

Three rules run through all of it. Nothing is rounded: input with more digits than the token has is refused. A call that moves value waits for the person who owns the funds, and the model never confirms. A retry reuses the exact bytes of the first attempt, so it cannot pay twice. Security model →

Which package do you need?#

You want toUse
Compile a .did file and compare or cache on its identity, in Rustmeant (crates.io)
Do the same from JavaScript, or generate a TypeScript module@candid-core/cli (npm)
Validate and encode Candid values in TypeScript@candid-core/schema (npm)
Check a profile, merge it with packs, write the resolved form@meantjs/profile and its candid-profile command
Parse and show amounts, times, accounts, subaccounts and bytes@meantjs/format; meant-format for amounts in Rust
Answer icrc21_consent_message, or render an SNS proposal@meantjs/consent
Give an agent tools for a canister@meantjs/tools
Let the person confirm once what an agent's task may grant, and check each request against it@meantjs/consent/terms
Find out whether a transfer with an unknown outcome went through@meantjs/reconcile

Every crate and package, with versions and dependencies →

Where Meant stops#

  • It does not call canisters. The packages compile, describe, convert and render; sending a call belongs to the layer above. B3Pay's IC Reactor is that layer for React on the Internet Computer.
  • A profile is a claim, not a proof. An author can describe a drain as "claim reward". Binding to interface_id and provenance on every tag limit the damage; they do not remove it. Security model →
  • The Internet Computer first. Profiles for EVM contracts are experimental: consent text for calls and for EIP-712 signing requests, and ERC-7730 files, not yet agent tools. Solana is planned and not supported.
  • assumption Typed tools built from profiles reduce the value errors agents make. That is what the project's preregistered evaluation tests, and the profile spec stays a draft, with breaking changes expected, until it reports.