For AI agents: Documentation index at /llms.txt

Skip to content

Digital Asset Standards

ICP implements digital assets using the ICRC standard family. The specifications use the term “token” throughout: a fungible digital asset is a “fungible token” in ICRC-1, and an NFT is a “non-fungible token” in ICRC-7. This page covers the digital asset standards (ICRC-1 through ICRC-37). For the full list of ICRC standards including wallet signer protocols, see ICRC Standards.

StandardPurposeExtendsStatus
ICRC-1Fungible token base standardnoneAdopted
ICRC-2Approve and transfer-from for fungible tokensICRC-1Adopted
ICRC-3Transaction log and block archiveICRC-1Adopted
ICRC-7Non-fungible token (NFT) base standardnoneAdopted
ICRC-37Approve and transfer-from for NFTsICRC-7Adopted

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).

A ledger can report which extensions it supports through the icrc1_supported_standards endpoint.

An ICRC-1 account consists of two parts:

  • owner: a Principal identifying the account holder
  • subaccount: an optional 32-byte Blob that defaults to all zeros when omitted

This means a single principal can control up to 2^256 distinct accounts by varying the subaccount.

type Account = record {
owner : principal;
subaccount : opt blob;
};
MethodSignatureDescription
icrc1_transfer(TransferArg) -> (variant { Ok : nat; Err : TransferError })Transfer tokens between accounts
icrc1_balance_of(Account) -> (nat) queryReturn the balance of an account
icrc1_total_supply() -> (nat) queryReturn the total token supply
icrc1_metadata() -> (vec record { text; Value }) queryReturn token metadata entries
icrc1_name() -> (text) queryReturn the token name
icrc1_symbol() -> (text) queryReturn the token symbol
icrc1_decimals() -> (nat8) queryReturn the number of decimals
icrc1_fee() -> (nat) queryReturn the default transfer fee
icrc1_minting_account() -> (opt Account) queryReturn the minting account
icrc1_supported_standards() -> (vec record { name : text; url : text }) queryReturn supported standard extensions
type TransferArg = record {
from_subaccount : opt blob;
to : Account;
amount : nat;
fee : opt nat;
memo : opt blob;
created_at_time : opt nat64;
};

Setting created_at_time enables deduplication. The ledger rejects duplicate transfers submitted within a 24-hour window. Without it, identical transfers both execute.

type TransferError = variant {
BadFee : record { expected_fee : nat };
BadBurn : record { min_burn_amount : nat };
InsufficientFunds : record { balance : nat };
TooOld;
CreatedInFuture : record { ledger_time : nat64 };
Duplicate : record { duplicate_of : nat };
TemporarilyUnavailable;
GenericError : record { error_code : nat; message : text };
};
KeyTypeExample
icrc1:symbolText"ICP"
icrc1:nameText"Internet Computer"
icrc1:decimalsNat8
icrc1:feeNat10000

For a few well-known ledger canister IDs and index canisters, see Ledgers. For a broader overview of tokens on ICP, see the ICP Dashboard token list.

Read the full ICRC-1 standard

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.

The workflow has two steps:

  1. The account owner calls icrc2_approve to authorize a spender for up to X tokens.
  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.
MethodSignatureDescription
icrc2_approve(ApproveArg) -> (variant { Ok : nat; Err : ApproveError })Authorize a spender for a token amount
icrc2_transfer_from(TransferFromArg) -> (variant { Ok : nat; Err : TransferFromError })Transfer tokens on behalf of the owner
icrc2_allowance(AllowanceArg) -> (Allowance) queryQuery the remaining allowance for a spender
type ApproveArg = record {
from_subaccount : opt blob;
spender : Account;
amount : nat;
expected_allowance : opt nat;
expires_at : opt nat64;
fee : opt nat;
memo : opt blob;
created_at_time : opt nat64;
};

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.

type ApproveError = variant {
BadFee : record { expected_fee : nat };
InsufficientFunds : record { balance : nat };
AllowanceChanged : record { current_allowance : nat };
Expired : record { ledger_time : nat64 };
TooOld;
CreatedInFuture : record { ledger_time : nat64 };
Duplicate : record { duplicate_of : nat };
TemporarilyUnavailable;
GenericError : record { error_code : nat; message : text };
};
type TransferFromArg = record {
spender_subaccount : opt blob;
from : Account;
to : Account;
amount : nat;
fee : opt nat;
memo : opt blob;
created_at_time : opt nat64;
};
type TransferFromError = variant {
BadFee : record { expected_fee : nat };
BadBurn : record { min_burn_amount : nat };
InsufficientFunds : record { balance : nat };
InsufficientAllowance : record { allowance : nat };
TooOld;
CreatedInFuture : record { ledger_time : nat64 };
Duplicate : record { duplicate_of : nat };
TemporarilyUnavailable;
GenericError : record { error_code : nat; message : text };
};
type AllowanceArg = record {
account : Account;
spender : Account;
};
type Allowance = record {
allowance : nat;
expires_at : opt nat64;
};
  • DEX integrations: a DEX canister is approved to pull tokens from a user’s account during a swap.
  • Subscription payments: a service canister is approved for recurring token withdrawals.
  • Escrow: an intermediary canister holds approval to release tokens when conditions are met.

ICP, ckBTC, and ckETH all implement ICRC-2.

Read the full ICRC-2 standard

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.

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.

MethodSignatureDescription
icrc3_get_blocks(vec record { start : nat; length : nat }) -> (GetBlocksResult) queryRetrieve blocks by index range; returns callbacks for archived blocks
icrc3_get_archives(GetArchivesArgs) -> (vec record { canister_id; start; end }) queryList archive canisters and the block ranges they hold
icrc3_get_tip_certificate() -> (opt DataCertificate) queryReturn a certificate for the last block hash and index
icrc3_supported_block_types() -> (vec record { block_type : text; url : text }) queryList the block types the ledger produces

ICRC-3 blocks use a generic Value representation that preserves all data for verification. Each block contains:

  • phash: hash of the previous block (absent for the genesis block)
  • btype: block type string (e.g., "1xfer" for ICRC-1 transfers, "2approve" for ICRC-2 approvals)
  • ts: timestamp in nanoseconds
  • Transaction-specific fields: vary by block type (e.g., from, to, amt for transfers)

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.

Block typeStandardDescription
1xferICRC-1Transfer
1burnICRC-1Burn
1mintICRC-1Mint
2approveICRC-2Approval
2xferICRC-2Transfer-from

The following block types are currently in the ICRC proposal process and not yet adopted:

Block type(s)ProposalStatus
ICRC-122Ledger notification blocksProposed
ICRC-123Batch call blocksProposed
ICRC-124Canister management blocksProposed
ICRC-152RWA compliance blocksProposed
ICRC-153RWA regulatory blocksProposed
ICRC-154RWA lifecycle blocksProposed

Read the full ICRC-3 standard

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.

ICRC-7 uses the same account model as ICRC-1 (principal + optional 32-byte subaccount).

MethodSignatureDescription
icrc7_collection_metadata() -> (vec record { text; Value }) queryReturn collection metadata
icrc7_name() -> (text) queryReturn collection name
icrc7_symbol() -> (text) queryReturn collection symbol
icrc7_total_supply() -> (nat) queryReturn total number of tokens
icrc7_supply_cap() -> (opt nat) queryReturn maximum supply (if set)
icrc7_token_metadata(vec nat) -> (vec opt vec record { text; Value }) queryReturn metadata for specific token IDs
icrc7_owner_of(vec nat) -> (vec opt Account) queryReturn the owner of specific token IDs
icrc7_balance_of(vec Account) -> (vec nat) queryReturn the number of tokens owned by each account
icrc7_tokens(opt nat, opt nat) -> (vec nat) queryList token IDs with pagination
icrc7_tokens_of(Account, opt nat, opt nat) -> (vec nat) queryList token IDs owned by an account
icrc7_transfer(vec TransferArg) -> (vec opt TransferResult)Batch transfer tokens
KeyTypeDescription
icrc7:symbolTextToken symbol
icrc7:nameTextToken name
icrc7:descriptionTextCollection description
icrc7:logoTextURL of the collection logo
icrc7:total_supplyNatCurrent number of tokens
icrc7:supply_capNatMaximum number of tokens (optional)

Read the full ICRC-7 standard

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.

A ledger that implements ICRC-37 must also implement all ICRC-7 methods. Support for ICRC-37 is optional for ICRC-7 ledgers.

MethodSignatureDescription
icrc37_approve_tokens(vec ApproveTokenArg) -> (vec opt ApproveTokenResult)Approve a spender for specific token IDs
icrc37_approve_collection(vec ApproveCollectionArg) -> (vec opt ApproveCollectionResult)Approve a spender for all tokens in the collection
icrc37_revoke_token_approvals(vec RevokeTokenApprovalArg) -> (vec opt RevokeTokenApprovalResult)Revoke approvals for specific tokens
icrc37_revoke_collection_approvals(vec RevokeCollectionApprovalArg) -> (vec opt RevokeCollectionApprovalResult)Revoke collection-level approvals
icrc37_is_approved(vec IsApprovedArg) -> (vec bool) queryCheck if a spender is approved for specific tokens
icrc37_get_token_approvals(nat, opt nat, opt nat) -> (vec TokenApproval) queryList approvals for a token ID
icrc37_get_collection_approvals(Account, opt nat, opt nat) -> (vec CollectionApproval) queryList collection-level approvals
icrc37_transfer_from(vec TransferFromArg) -> (vec opt TransferFromResult)Transfer tokens using an approval
KeyTypeDescription
icrc37:max_approvals_per_token_or_collectionNatMaximum active approvals allowed per principal or token
icrc37:max_revoke_approvalsNatMaximum approvals that can be revoked in one call (optional)

Read the full ICRC-37 standard