Skip to content

Commit eeb9a4e

Browse files
authored
docs: token standards reference (#33)
## Summary - Reference page for ICRC token and wallet signer standards - Covers ICRC-1 (fungible tokens), ICRC-2 (approve/transfer-from), ICRC-3 (transaction log with archive model), ICRC-7 (NFTs), ICRC-37 (NFT approvals), and wallet signer standards (ICRC-21/25/27/29/49) - Includes Candid type definitions, method signatures, metadata entries, and canonical canister IDs for DFINITY-maintained ledgers - ICRC-3 section covers the archive canister model, adopted block types, and proposed block types (ICRC-122/123/124/152/153/154) - Links to ICP Dashboard for comprehensive token registry beyond the listed DFINITY-maintained tokens ## Sync recommendation informed by dfinity/portal — docs/defi/token-standards/index.mdx, icrc-1.mdx, icrc-2.mdx, icrc-7.mdx, icrc-37.mdx; dfinity/icskills — skills/icrc-ledger/SKILL.md, skills/wallet-integration/SKILL.md
1 parent 8b28563 commit eeb9a4e

1 file changed

Lines changed: 359 additions & 10 deletions

File tree

docs/reference/token-standards.md

Lines changed: 359 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,365 @@ sidebar:
66
icskills: [icrc-ledger, wallet-integration]
77
---
88

9-
TODO: Write content for this page.
9+
ICP uses the ICRC standard family for tokens and token-related operations. This page covers the token standards (ICRC-1 through ICRC-37) and wallet signer standards (ICRC-21 through ICRC-49) that developers need to build DeFi applications, wallets, and token integrations.
1010

11-
<!-- Content Brief -->
12-
Reference for ICP token standards. Document ICRC-1 (fungible token transfers, balances, metadata), ICRC-2 (approve/transferFrom), ICRC-3 (transaction log/history), ICRC-7 (NFT standard), and related standards (ICRC-21/25/27/29/49 for wallet signers). Include standard interfaces, canonical ledger canister IDs, and compliance requirements.
11+
ICRC stands for Internet Computer Request for Comments. Standards are proposed by the [ICRC working group](https://github.com/dfinity/ICRC), refined through community consensus, and adopted or rejected through NNS governance proposals.
1312

14-
<!-- Source Material -->
15-
- Portal: defi/tokens/token-standards files
16-
- icskills: icrc-ledger, wallet-integration
13+
## Standards overview
1714

18-
<!-- Cross-Links -->
19-
- guides/defi/token-ledgers -- practical token operations
20-
- guides/defi/wallet-integration -- ICRC signer standards
21-
- guides/defi/chain-key-tokens -- ckBTC/ckETH are ICRC-1 tokens
15+
| Standard | Purpose | Extends | Status |
16+
|----------|---------|---------|--------|
17+
| [ICRC-1](#icrc-1-fungible-tokens) | Fungible token base standard | -- | Adopted |
18+
| [ICRC-2](#icrc-2-approve-and-transfer-from) | Approve and transfer-from for fungible tokens | ICRC-1 | Adopted |
19+
| [ICRC-3](#icrc-3-transaction-log) | Transaction log and block archive | ICRC-1 | Adopted |
20+
| [ICRC-7](#icrc-7-non-fungible-tokens) | Non-fungible token (NFT) base standard | -- | Adopted |
21+
| [ICRC-37](#icrc-37-nft-approvals) | Approve and transfer-from for NFTs | ICRC-7 | Adopted |
22+
| [ICRC-21](#wallet-signer-standards) | Canister call consent messages | -- | Adopted |
23+
| [ICRC-25](#wallet-signer-standards) | Signer interaction (permissions) | -- | Adopted |
24+
| [ICRC-27](#wallet-signer-standards) | Account discovery | -- | Adopted |
25+
| [ICRC-29](#wallet-signer-standards) | Window PostMessage transport | -- | Adopted |
26+
| [ICRC-49](#wallet-signer-standards) | Call canister via signer | -- | Adopted |
27+
28+
## ICRC-1: Fungible tokens
29+
30+
ICRC-1 is the base standard for fungible tokens on ICP. It defines transfer, balance, and metadata interfaces. The standard intentionally excludes certain features — transaction notifications, block structure, and pre-signed transactions — which are provided by extension standards (ICRC-2, ICRC-3).
31+
32+
A ledger can report which extensions it supports through the `icrc1_supported_standards` endpoint.
33+
34+
### Account model
35+
36+
An ICRC-1 account consists of two parts:
37+
38+
- **`owner`** — a `Principal` identifying the account holder
39+
- **`subaccount`** — an optional 32-byte `Blob` that defaults to all zeros when omitted
40+
41+
This means a single principal can control up to 2^256 distinct accounts by varying the subaccount.
42+
43+
```candid
44+
type Account = record {
45+
owner : principal;
46+
subaccount : opt blob;
47+
};
48+
```
49+
50+
### Core methods
51+
52+
| Method | Signature | Description |
53+
|--------|-----------|-------------|
54+
| `icrc1_transfer` | `(TransferArg) -> (variant { Ok : nat; Err : TransferError })` | Transfer tokens between accounts |
55+
| `icrc1_balance_of` | `(Account) -> (nat) query` | Return the balance of an account |
56+
| `icrc1_total_supply` | `() -> (nat) query` | Return the total token supply |
57+
| `icrc1_metadata` | `() -> (vec record { text; Value }) query` | Return token metadata entries |
58+
| `icrc1_name` | `() -> (text) query` | Return the token name |
59+
| `icrc1_symbol` | `() -> (text) query` | Return the token symbol |
60+
| `icrc1_decimals` | `() -> (nat8) query` | Return the number of decimals |
61+
| `icrc1_fee` | `() -> (nat) query` | Return the default transfer fee |
62+
| `icrc1_minting_account` | `() -> (opt Account) query` | Return the minting account |
63+
| `icrc1_supported_standards` | `() -> (vec record { name : text; url : text }) query` | Return supported standard extensions |
64+
65+
### Transfer arguments
66+
67+
```candid
68+
type TransferArg = record {
69+
from_subaccount : opt blob;
70+
to : Account;
71+
amount : nat;
72+
fee : opt nat;
73+
memo : opt blob;
74+
created_at_time : opt nat64;
75+
};
76+
```
77+
78+
Setting `created_at_time` enables deduplication — the ledger rejects duplicate transfers submitted within a 24-hour window. Without it, identical transfers both execute.
79+
80+
### Transfer errors
81+
82+
```candid
83+
type TransferError = variant {
84+
BadFee : record { expected_fee : nat };
85+
BadBurn : record { min_burn_amount : nat };
86+
InsufficientFunds : record { balance : nat };
87+
TooOld;
88+
CreatedInFuture : record { ledger_time : nat64 };
89+
Duplicate : record { duplicate_of : nat };
90+
TemporarilyUnavailable;
91+
GenericError : record { error_code : nat; message : text };
92+
};
93+
```
94+
95+
### Metadata entries
96+
97+
| Key | Type | Example |
98+
|-----|------|---------|
99+
| `icrc1:symbol` | `Text` | `"ICP"` |
100+
| `icrc1:name` | `Text` | `"Internet Computer"` |
101+
| `icrc1:decimals` | `Nat` | `8` |
102+
| `icrc1:fee` | `Nat` | `10000` |
103+
104+
### Common ICRC-1 ledgers
105+
106+
The following are DFINITY-maintained ledgers. Many other ICRC-1 tokens exist on ICP — see the [ICP Dashboard token list](https://dashboard.internetcomputer.org/tokens) for a comprehensive registry. Anyone can deploy an ICRC-1 compliant ledger.
107+
108+
| Token | Ledger canister ID | Decimals |
109+
|-------|-------------------|----------|
110+
| ICP | `ryjl3-tyaaa-aaaaa-aaaba-cai` | 8 |
111+
| ckBTC | `mxzaz-hqaaa-aaaar-qaada-cai` | 8 |
112+
| ckETH | `ss2fx-dyaaa-aaaar-qacoq-cai` | 18 |
113+
114+
> Fees can change at any time. Always call `icrc1_fee` to get the current fee rather than hardcoding values.
115+
116+
Index canisters (for transaction history):
117+
118+
| Token | Index canister ID |
119+
|-------|------------------|
120+
| ICP | `qhbym-qaaaa-aaaaa-aaafq-cai` |
121+
| ckBTC | `n5wcd-faaaa-aaaar-qaaea-cai` |
122+
| ckETH | `s3zol-vqaaa-aaaar-qacpa-cai` |
123+
124+
[Read the full ICRC-1 standard](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1)
125+
126+
## ICRC-2: Approve and transfer-from
127+
128+
ICRC-2 extends ICRC-1 with an approve/transfer-from workflow, similar to ERC-20 allowances on Ethereum. An account owner delegates spending authority to a third party, who can then transfer tokens on the owner's behalf.
129+
130+
The workflow has two steps:
131+
132+
1. The account owner calls `icrc2_approve` to authorize a spender for up to X tokens.
133+
2. The spender calls `icrc2_transfer_from` to move tokens from the owner's account. Multiple transfers are allowed as long as the total does not exceed the approved amount.
134+
135+
### Methods
136+
137+
| Method | Signature | Description |
138+
|--------|-----------|-------------|
139+
| `icrc2_approve` | `(ApproveArg) -> (variant { Ok : nat; Err : ApproveError })` | Authorize a spender for a token amount |
140+
| `icrc2_transfer_from` | `(TransferFromArg) -> (variant { Ok : nat; Err : TransferFromError })` | Transfer tokens on behalf of the owner |
141+
| `icrc2_allowance` | `(AllowanceArg) -> (Allowance) query` | Query the remaining allowance for a spender |
142+
143+
### Approve arguments
144+
145+
```candid
146+
type ApproveArg = record {
147+
from_subaccount : opt blob;
148+
spender : Account;
149+
amount : nat;
150+
expected_allowance : opt nat;
151+
expires_at : opt nat64;
152+
fee : opt nat;
153+
memo : opt blob;
154+
created_at_time : opt nat64;
155+
};
156+
```
157+
158+
The `expected_allowance` field provides protection against race conditions — the call fails if the current allowance does not match the expected value. The `expires_at` field sets a deadline (in nanoseconds since the Unix epoch) after which the approval is no longer valid.
159+
160+
### Approve errors
161+
162+
```candid
163+
type ApproveError = variant {
164+
BadFee : record { expected_fee : nat };
165+
InsufficientFunds : record { balance : nat };
166+
AllowanceChanged : record { current_allowance : nat };
167+
Expired : record { ledger_time : nat64 };
168+
TooOld;
169+
CreatedInFuture : record { ledger_time : nat64 };
170+
Duplicate : record { duplicate_of : nat };
171+
TemporarilyUnavailable;
172+
GenericError : record { error_code : nat; message : text };
173+
};
174+
```
175+
176+
### Transfer-from arguments
177+
178+
```candid
179+
type TransferFromArg = record {
180+
spender_subaccount : opt blob;
181+
from : Account;
182+
to : Account;
183+
amount : nat;
184+
fee : opt nat;
185+
memo : opt blob;
186+
created_at_time : opt nat64;
187+
};
188+
```
189+
190+
### Transfer-from errors
191+
192+
```candid
193+
type TransferFromError = variant {
194+
BadFee : record { expected_fee : nat };
195+
BadBurn : record { min_burn_amount : nat };
196+
InsufficientFunds : record { balance : nat };
197+
InsufficientAllowance : record { allowance : nat };
198+
TooOld;
199+
CreatedInFuture : record { ledger_time : nat64 };
200+
Duplicate : record { duplicate_of : nat };
201+
TemporarilyUnavailable;
202+
GenericError : record { error_code : nat; message : text };
203+
};
204+
```
205+
206+
### Allowance query
207+
208+
```candid
209+
type AllowanceArg = record {
210+
account : Account;
211+
spender : Account;
212+
};
213+
214+
type Allowance = record {
215+
allowance : nat;
216+
expires_at : opt nat64;
217+
};
218+
```
219+
220+
### Common use cases
221+
222+
- **DEX integrations** — a DEX canister is approved to pull tokens from a user's account during a swap.
223+
- **Subscription payments** — a service canister is approved for recurring token withdrawals.
224+
- **Escrow** — an intermediary canister holds approval to release tokens when conditions are met.
225+
226+
ICP, ckBTC, and ckETH all implement ICRC-2.
227+
228+
[Read the full ICRC-2 standard](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-2)
229+
230+
## ICRC-3: Transaction log
231+
232+
ICRC-3 extends ICRC-1 with a standardized transaction log interface. It defines how ledgers expose their block history, enabling clients and index canisters to retrieve and verify transaction records.
233+
234+
### Archive model
235+
236+
Ledgers store recent blocks directly and move older blocks to **archive canisters** to manage memory. When fetching blocks, `icrc3_get_blocks` returns blocks the ledger holds directly plus callbacks to fetch archived blocks from the appropriate archive canister. Use `icrc3_get_archives` to discover all archive canisters and the block ranges they hold.
237+
238+
### Methods
239+
240+
| Method | Signature | Description |
241+
|--------|-----------|-------------|
242+
| `icrc3_get_blocks` | `(vec record { start : nat; length : nat }) -> (GetBlocksResult) query` | Retrieve blocks by index range; returns callbacks for archived blocks |
243+
| `icrc3_get_archives` | `(GetArchivesArgs) -> (vec record { canister_id; start; end }) query` | List archive canisters and the block ranges they hold |
244+
| `icrc3_get_tip_certificate` | `() -> (opt DataCertificate) query` | Return a certificate for the last block hash and index |
245+
| `icrc3_supported_block_types` | `() -> (vec record { block_type : text; url : text }) query` | List the block types the ledger produces |
246+
247+
### Block schema
248+
249+
ICRC-3 blocks use a generic `Value` representation that preserves all data for verification. Each block contains:
250+
251+
- **`phash`** — hash of the previous block (absent for the genesis block)
252+
- **`btype`** — block type string (e.g., `"1xfer"` for ICRC-1 transfers, `"2approve"` for ICRC-2 approvals)
253+
- **`ts`** — timestamp in nanoseconds
254+
- **Transaction-specific fields** — vary by block type (e.g., `from`, `to`, `amt` for transfers)
255+
256+
### Adopted block types
257+
258+
Block type identifiers follow the naming convention `<icrc-number><operation>` (e.g., `1xfer` for ICRC-1 transfer). Anyone can define new block types for custom standards following this convention.
259+
260+
| Block type | Standard | Description |
261+
|------------|----------|-------------|
262+
| `1xfer` | ICRC-1 | Transfer |
263+
| `1burn` | ICRC-1 | Burn |
264+
| `1mint` | ICRC-1 | Mint |
265+
| `2approve` | ICRC-2 | Approval |
266+
| `2xfer` | ICRC-2 | Transfer-from |
267+
268+
### Proposed block types
269+
270+
The following block types are currently in the ICRC proposal process and not yet adopted:
271+
272+
| Block type(s) | Proposal | Status |
273+
|---------------|----------|--------|
274+
| ICRC-122 | [Ledger notification blocks](https://github.com/dfinity/ICRC/pull/125) | Proposed |
275+
| ICRC-123 | [Batch call blocks](https://github.com/dfinity/ICRC/pull/134) | Proposed |
276+
| ICRC-124 | [Canister management blocks](https://github.com/dfinity/ICRC/pull/135) | Proposed |
277+
| ICRC-152 | [RWA compliance blocks](https://github.com/dfinity/ICRC/pull/156) | Proposed |
278+
| ICRC-153 | [RWA regulatory blocks](https://github.com/dfinity/ICRC/pull/157) | Proposed |
279+
| ICRC-154 | [RWA lifecycle blocks](https://github.com/dfinity/ICRC/pull/158) | Proposed |
280+
281+
[Read the full ICRC-3 standard](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3)
282+
283+
## ICRC-7: Non-fungible tokens
284+
285+
ICRC-7 defines the base standard for non-fungible tokens (NFTs) on ICP. It can be used to create and manage NFT collections. Like ICRC-1 for fungible tokens, ICRC-7 is intentionally minimal and excludes transaction notifications, block structure, and pre-signed transactions — these can be added through extensions.
286+
287+
ICRC-7 uses the same account model as ICRC-1 (principal + optional 32-byte subaccount).
288+
289+
### Core methods
290+
291+
| Method | Signature | Description |
292+
|--------|-----------|-------------|
293+
| `icrc7_collection_metadata` | `() -> (vec record { text; Value }) query` | Return collection metadata |
294+
| `icrc7_name` | `() -> (text) query` | Return collection name |
295+
| `icrc7_symbol` | `() -> (text) query` | Return collection symbol |
296+
| `icrc7_total_supply` | `() -> (nat) query` | Return total number of tokens |
297+
| `icrc7_supply_cap` | `() -> (opt nat) query` | Return maximum supply (if set) |
298+
| `icrc7_token_metadata` | `(vec nat) -> (vec opt vec record { text; Value }) query` | Return metadata for specific token IDs |
299+
| `icrc7_owner_of` | `(vec nat) -> (vec opt Account) query` | Return the owner of specific token IDs |
300+
| `icrc7_balance_of` | `(vec Account) -> (vec nat) query` | Return the number of tokens owned by each account |
301+
| `icrc7_tokens` | `(opt nat, opt nat) -> (vec nat) query` | List token IDs with pagination |
302+
| `icrc7_tokens_of` | `(Account, opt nat, opt nat) -> (vec nat) query` | List token IDs owned by an account |
303+
| `icrc7_transfer` | `(vec TransferArg) -> (vec opt TransferResult)` | Batch transfer tokens |
304+
305+
### Collection metadata
306+
307+
| Key | Type | Description |
308+
|-----|------|-------------|
309+
| `icrc7:symbol` | `Text` | Token symbol |
310+
| `icrc7:name` | `Text` | Token name |
311+
| `icrc7:description` | `Text` | Collection description |
312+
| `icrc7:logo` | `Text` | URL of the collection logo |
313+
| `icrc7:total_supply` | `Nat` | Current number of tokens |
314+
| `icrc7:supply_cap` | `Nat` | Maximum number of tokens (optional) |
315+
316+
[Read the full ICRC-7 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md)
317+
318+
## ICRC-37: NFT approvals
319+
320+
ICRC-37 extends ICRC-7 with an approval workflow for NFTs, analogous to how ICRC-2 extends ICRC-1 for fungible tokens. It adds support for creating approvals, revoking approvals, querying approval state, and executing transfers based on approvals.
321+
322+
A ledger that implements ICRC-37 must also implement all ICRC-7 methods. Support for ICRC-37 is optional for ICRC-7 ledgers.
323+
324+
### Methods
325+
326+
| Method | Signature | Description |
327+
|--------|-----------|-------------|
328+
| `icrc37_approve_tokens` | `(vec ApproveTokenArg) -> (vec opt ApproveTokenResult)` | Approve a spender for specific token IDs |
329+
| `icrc37_approve_collection` | `(vec ApproveCollectionArg) -> (vec opt ApproveCollectionResult)` | Approve a spender for all tokens in the collection |
330+
| `icrc37_revoke_token_approvals` | `(vec RevokeTokenApprovalArg) -> (vec opt RevokeTokenApprovalResult)` | Revoke approvals for specific tokens |
331+
| `icrc37_revoke_collection_approvals` | `(vec RevokeCollectionApprovalArg) -> (vec opt RevokeCollectionApprovalResult)` | Revoke collection-level approvals |
332+
| `icrc37_is_approved` | `(vec IsApprovedArg) -> (vec bool) query` | Check if a spender is approved for specific tokens |
333+
| `icrc37_get_token_approvals` | `(nat, opt nat, opt nat) -> (vec TokenApproval) query` | List approvals for a token ID |
334+
| `icrc37_get_collection_approvals` | `(Account, opt nat, opt nat) -> (vec CollectionApproval) query` | List collection-level approvals |
335+
| `icrc37_transfer_from` | `(vec TransferFromArg) -> (vec opt TransferFromResult)` | Transfer tokens using an approval |
336+
337+
### Additional metadata
338+
339+
| Key | Type | Description |
340+
|-----|------|-------------|
341+
| `icrc37:max_approvals_per_token_or_collection` | `Nat` | Maximum active approvals allowed per principal or token |
342+
| `icrc37:max_revoke_approvals` | `Nat` | Maximum approvals that can be revoked in one call (optional) |
343+
344+
[Read the full ICRC-37 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md)
345+
346+
## Wallet signer standards
347+
348+
The ICRC signer standards define how wallets interact with dApps on ICP. They use a popup-based model where every action requires explicit user approval, communicated via JSON-RPC 2.0 over `window.postMessage`.
349+
350+
| Standard | Purpose |
351+
|----------|---------|
352+
| **ICRC-21** | Canister call consent messages — enables canisters to provide human-readable descriptions of what a call will do, displayed to the user before signing |
353+
| **ICRC-25** | Signer interaction standard — defines the permission lifecycle (`granted`, `denied`, `ask_on_use`) for signer methods |
354+
| **ICRC-27** | Account discovery — allows dApps to request the list of accounts available in the wallet |
355+
| **ICRC-29** | Window PostMessage transport — defines the communication channel between dApp and signer using `window.postMessage` |
356+
| **ICRC-49** | Call canister — allows dApps to request the signer to execute a canister call on behalf of the user |
357+
358+
These standards are distinct from delegation-based authentication (such as Internet Identity). The signer model requires per-action user approval and does not create sessions or delegated identities.
359+
360+
For implementation details and code examples, see the [wallet integration guide](../guides/defi/wallet-integration.md).
361+
362+
## Next steps
363+
364+
- [Token ledgers guide](../guides/defi/token-ledgers.md) — deploy and interact with ICRC-1/ICRC-2 ledgers
365+
- [Chain-key tokens guide](../guides/defi/chain-key-tokens.md) — work with ckBTC, ckETH, and other chain-key tokens
366+
- [Wallet integration guide](../guides/defi/wallet-integration.md) — integrate wallet signer standards into your dApp
367+
- [ICRC-1 standard specification](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1) — full specification on GitHub
368+
- [ICRC-7 standard specification](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md) — full NFT specification on GitHub
369+
370+
<!-- Upstream: informed by dfinity/portal — docs/defi/token-standards/index.mdx, icrc-1.mdx, icrc-2.mdx, icrc-7.mdx, icrc-37.mdx; dfinity/icskills — skills/icrc-ledger/SKILL.md, skills/wallet-integration/SKILL.md -->

0 commit comments

Comments
 (0)