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:
type TransferArg = record {
// …
amount : Tokens;
// …
};- 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 meant crate compiles a .did file, through the official Candid
type checker, into a validated Contract graph with content-addressed identities.
A profile attaches tags to positions of that graph: units, intent, effects, retry safety and what each result means.
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.
| Consumer | What it gets from the profile | Where |
|---|---|---|
| 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 messages | An intent line and typed fields, for any method | @meantjs/consent |
| User interfaces | Parsing and display of amounts, times, accounts and bytes; labels and help text | @meantjs/format, meant-format |
| Typed Candid in TypeScript | Schemas, validation and a binary codec for the interface itself | @candid-core/schema |
amount: "1.5 ICP"
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 to | Use |
|---|---|
Compile a .did file and compare or cache on its identity, in Rust | meant (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_idand 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.