Skip to content
388 changes: 388 additions & 0 deletions ICRCs/ICRC-152/ICRC-152.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,388 @@
# `ICRC‑152`: Privileged Mint & Burn API

| Status |
Comment on lines +1 to +3
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description/title references ICRC-522 and icrc522_mint/icrc522_burn, but this document defines ICRC-152 and icrc152_mint/icrc152_burn. Please align the PR metadata with the spec number and method names (or rename the spec if 522 was intended).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the PR metadata was stale from an earlier numbering. The spec is ICRC-152.

|:------:|
| Draft |

## Introduction & Motivation

Most ICRC-based ledgers (e.g., ICRC-1, ICRC-2) treat minting and burning as
system-only operations that occur indirectly.

- Minting is represented as a transfer from the minting account.
- Burning is represented as a transfer into the minting account.

This approach works for decentralized tokens but is insufficient for
administrator-controlled or regulated assets, such as:

- Stablecoins, which must allow an issuer to adjust circulating supply.
- Real-World Asset (RWA) tokens, where compliance requires explicit
mint and burn actions.
- Other managed ledgers (NFTs, reward points, internal credits).

Without a standardized API, integrators and tooling cannot reliably
distinguish user-initiated supply changes from privileged supply
management actions.

ICRC-152 addresses this gap by introducing:

- Two privileged methods, `icrc152_mint` and `icrc152_burn`, callable only by authorized principals.
- A canonical mapping from method inputs to block `tx` fields, including optional metadata and caller tracking.
- Recording of these operations using the typed block kinds **defined in ICRC-122**:
`btype = "122mint"` for mints and `btype = "122burn"` for burns.


## Overview

ICRC-152 standardizes privileged supply management in ICRC-based ledgers.

Specifically, it defines:

- **APIs** for minting and burning tokens under ledger authorization.
- **Canonical `tx` mapping** rules ensuring deterministic block content.
- **Use of ICRC-122 block kinds** to record privileged supply actions
(`btype = "122mint"` and `btype = "122burn"`).
- **Compliance reporting** through ICRC-10 methods.



This allows wallets, explorers, and auditors to:

- Reliably distinguish privileged supply changes from user-initiated transfers.
- Verify compliance with external requirements (e.g., MiCA).
- Interoperate across ledgers that adopt the same API and block semantics.


## Dependencies

This standard does not introduce new block kinds.

- **ICRC-3** — Provides the block log format, hashing, certification, and rules
for canonical `tx` mapping.
- **ICRC-122** — Defines the typed block kinds used by this standard:
- `btype = "122mint"` (authorized mint block)
- `btype = "122burn"` (authorized burn block)

A ledger implementing ICRC-152 MUST:
- Emit `122mint` for successful `icrc152_mint` calls and `122burn` for successful
`icrc152_burn` calls.
- Populate `tx.mthd` with namespaced values **introduced by this standard**:
`"152mint"` and `"152burn"`.


## Common Elements

This standard inherits core conventions from **ICRC-3** (block log format, Value encoding, hashing, certification) and **ICRC-122** (typed block kinds).

- **Accounts**
Encoded as ICRC-3 `Value` `variant { Array = vec { V1 [, V2] } }` where
`V1 = variant { Blob = <owner_principal_bytes> }` and optionally
`V2 = variant { Blob = <32-byte_subaccount_bytes> }`.
If no subaccount is provided, the array contains only the owner principal.

- **Principals**
Encoded as `variant { Blob = <principal_bytes> }`.

- **Timestamps**
Caller-supplied `created_at_time` is in **nanoseconds since Unix epoch**.
Encoded as `Nat` in ICRC-3 `Value` and **MUST** fit in `nat64`.

- **Blocks & Parent Hash**
Resulting blocks for these APIs use `btype = "122mint"` and `btype = "122burn"` (per ICRC-122).
Standard metadata (e.g., `phash`, `ts`) follows ICRC-3.



## Methods

### `icrc152_mint`


This method allows a ledger controller (or other authorized principal) to mint
tokens. It credits the specified account, increases total supply, and records
the action on-chain.


#### Arguments
```
type MintArgs = record {
to : Account;
amount : nat;
created_at_time : nat64;
reason : opt text;
};

type MintError = variant {
Unauthorized : text; // caller not permitted
InvalidAccount : text; // target account invalid
Duplicate : record { duplicate_of : nat };
GenericError : record { error_code : nat; message : text };
};

icrc152_mint : (MintArgs) -> (variant { Ok : nat; Err : MintError });
```


#### Semantics

**Authorization**
- The method **MUST** be callable only by a **controller** of the ledger or other explicitly authorized principals.
- Unauthorized calls **MUST** fail with `Unauthorized`.

**Effect (on success, non-retroactive)**
- **Credit** `amount` to `to`.
- **Increase** total supply by `amount`.
- **Append** a block with `btype = "122mint"`.
- The block’s top-level `tx` **MUST** be constructed **exactly** as in **Canonical `tx` Mapping** (keys, types, encodings), and embedded in the block.

**Return value**
- On success, **MUST** return `variant { Ok : nat }` where the `nat` is the index of the created block.
- On failure, **MUST** return `variant { Err : MintError }`.

**Deduplication & idempotency**
- The ledger **MUST** perform deduplication (e.g., using `created_at_time` and any implementation-defined inputs).
- If a duplicate is detected, the ledger **MUST NOT** append a new block and **MUST** return `Err(Duplicate { duplicate_of = <index> })`.

**Error cases (normative)**
- `Unauthorized` — caller not permitted.
- `InvalidAccount` — malformed/invalid target account (e.g., minting account, anonymous principal, invalid subaccount bytes).
- `Duplicate { duplicate_of }` — a semantically identical transaction was already accepted.
- `GenericError { error_code, message }` — any other failure preventing a valid `122mint` block.

**Clarifications**
- Optional fields **MUST be omitted** from `tx` if not supplied in the call (no null placeholders).
- Representation-independent hashing (ICRC-3) applies; field presence and values matter, not map ordering.


#### Canonical `tx` Mapping
A successful call to `icrc152_mint` produces a block of type `122mint`.
The `tx` field is derived deterministically as follows:

#### Canonical `tx` Mapping (normative)

Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The section heading #### Canonical tx Mapping is immediately followed by #### Canonical tx Mapping (normative) with no substantive content between them beyond an intro sentence. Consider collapsing these into a single heading to avoid redundancy and improve readability.

Suggested change
#### Canonical `tx` Mapping
A successful call to `icrc152_mint` produces a block of type `122mint`.
The `tx` field is derived deterministically as follows:
#### Canonical `tx` Mapping (normative)
#### Canonical `tx` Mapping (normative)
A successful call to `icrc152_mint` produces a block of type `122mint`.
The `tx` field is derived deterministically as follows:

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — collapsed into a single heading.

| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule |
|-------------------|------------------------|-----------------------------------------------------------------------------|
| `mthd` | `Text` | **Constant** `"152mint"`. |
| `to` | `Array` (Account) | From `MintArgs.to`, encoded as ICRC-3 Account. |
| `amt` | `Nat` | From `MintArgs.amount`. |
| `ts` | `Nat` | From `MintArgs.created_at_time` (ns since Unix epoch; **MUST** fit `nat64`). |
| `caller` | `Blob` | Principal of the caller (raw bytes). |
| `reason` | `Text` *(optional)* | From `MintArgs.reason` if provided; **omit** if absent. |




### `icrc152_burn`

This method allows a ledger controller (or other authorized principal) to burn
tokens. It debits the specified account, decreases total supply, and records
the action on-chain.

```
type BurnArgs = record {
from : Account;
amount : nat;
created_at_time : nat64;
reason : opt text;
};

type BurnError = variant {
Unauthorized : text; // caller not permitted
InvalidAccount : text; // source account invalid
InsufficientBalance : record { balance : nat };
Duplicate : record { duplicate_of : nat };
GenericError : record { error_code : nat; message : text };
};

icrc152_burn : (BurnArgs) -> (variant { Ok : nat; Err : BurnError });
```


#### Semantics
- **Debits** `amount` tokens from the `from` account.
- **Decreases** the total supply by `amount`.
- **Creates** a block of type `122burn`.
- On success, returns the **index of the created block**.
- On failure, returns an appropriate error.
- Semantics are consistent with the core transition already defined for `122burn` blocks.

#### Return Values
On success, the method returns

- `variant { Ok : nat }`
where the `nat` is the index of the block created in the ledger.

On failure, the method returns

- `variant { Err : BurnError }`
describing the reason for rejection.


#### Canonical `tx` Mapping
A successful call to `icrc152_burn` produces a block of type `122burn`.
The `tx` field is derived deterministically as follows:

- `mthd = "152burn"`
- `from = BurnArgs.from`
- `amt = BurnArgs.amount`
- `ts = BurnArgs.created_at_time`
- `caller = caller_principal (as Blob)`
- `reason = BurnArgs.reason` (if provided)

Optional fields (`reason`) MUST be omitted from `tx` if not supplied in the call.

### Reporting Compliance

#### Supported Standards

Ledgers implementing ICRC-152 MUST indicate compliance through the
`icrc1_supported_standards` and `icrc10_supported_standards` methods, by
including in the output of these methods:

```
variant { Vec = vec {
record {
"name"; variant { Text = "ICRC-152" };
"url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-152.md" };
}
}};
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The supported-standards example uses an ICRC-3 Value-style encoding (variant { Vec = ... } / variant { Text = ... }), but per ICRC-10 these methods return Candid vec record { name : text; url : text } (see ICRCs/ICRC-10/ICRC-10.md:13-16). Please update the example to the correct Candid shape, and ensure the URL matches this repo path (likely .../ICRCs/ICRC-152/ICRC-152.md).

Suggested change
variant { Vec = vec {
record {
"name"; variant { Text = "ICRC-152" };
"url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-152.md" };
}
}};
vec {
record {
name = "ICRC-152";
url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-152/ICRC-152.md";
}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — updated to use Candid vec record { name: text; url: text } format, and corrected the URL to include the /ICRC-152/ subdirectory.

```

#### Supported Block Types

ICRC-152 extends ICRC-122 and does not introduce any new block kinds.
Accordingly, ledgers implementing ICRC-152 MUST already advertise support
for `122mint` and `122burn` as required by ICRC-122.
No additional block types need to be reported.

### Example mint call and resulting block

#### Call
The caller invokes
```
icrc152_mint({
to = [principal "f5288412af11b299313a5b5a7c128311de102333c4adbe669f2ea1a308"];
amount = 500_000;
created_at_time = 1_753_344_737_123_456_789 : nat64;
reason = ?"Initial allocation";
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mint call example passes to = [principal "..."], which looks like the ICRC-3 Value account encoding, but the method signature defines to : Account in Candid. In this repo Account is a Candid record { owner : principal; subaccount : opt blob } (e.g., ICRCs/ICRC-7/ICRC-7.did:3). Please rewrite the example to use the Candid Account record and a valid principal textual form (the hex-like string shown isn’t a valid Candid principal literal).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the method call example now uses the Candid Account record format (record { owner = principal "..."; subaccount = null }) with a valid principal textual form.

})
```
Here, `to` is the account of the recipient, `amount` is the number of tokens to mint, `created_at_time` is the caller‑supplied timestamp, and `reason` provides an optional human‑readable note.

#### Resulting block

This call results in a block with btype = "122mint" and the following contents:

```
variant {
Map = vec {
record { "btype"; variant { Text = "122mint" } };
record {
"phash";
variant {
Blob = blob "\a0\5f\d2\f3\4c\26\73\58\00\7f\ea\02\18\43\47\70\85\50\2e\d2\1f\23\e0\dc\e6\af\3c\cf\9e\6f\4a\d8"
};
};
record { "ts"; variant { Nat64 = 1_753_344_738_000_000_000 : nat64 } };
record {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block example encodes the block timestamp as variant { Nat64 = ... }, but the ICRC-3 Value type used in this repo does not define a Nat64 constructor (see ICRCs/ICRC-7/ICRC-7.did:6-13); it uses Nat. Please change the example to use variant { Nat = ... } (keeping the "must fit nat64" constraint if desired).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed all Nat64 to Nat in the block examples. The constraint that values must fit in nat64 is documented in the canonical mapping table.

"tx";
variant {
Map = vec {
record { "mthd"; variant { Text = "152mint" } };
record {
"to";
variant {
Array = vec {
variant { Blob = blob "\15\28\84\12\af\11\b2\99\31\3a\5b\5a\7c\12\83\11\de\10\23\33\c4\ad\be\66\9f\2e\a1\a3\08" }
}
};
};
record { "amt"; variant { Nat64 = 500_000 : nat64 } };
record { "ts"; variant { Nat64 = 1_753_344_737_123_456_789 : nat64 } };
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the tx map example, amt and the caller-supplied ts are encoded as variant { Nat64 = ... }, but this repo’s ICRC-3 Value only supports Nat (no Nat64; see ICRCs/ICRC-7/ICRC-7.did:6-13). This also contradicts the canonical mapping table where amt/ts types are Nat. Please update these to variant { Nat = ... }.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed Nat64 to Nat for both amt and ts in the mint tx map example.

record { "caller"; variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } };
record { "reason"; variant { Text = "Initial allocation" } };
}
};
};
}
};

```



The block records the method (`mthd = "152mint"`), the recipient account (`to`),
the minted amount (`amt`), the timestamp supplied by the caller (`ts`), the caller’s
principal (`caller`), and the optional `reason`.

In the method call example above, the recipient is shown as a human-readable
principal literal. In the resulting block, the same principal is encoded canonically
as `variant { Blob = ... }`, where the `Blob` contains the raw byte representation
of the principal as defined by ICRC-3. Both forms represent the same identity;
the difference is only in presentation.


### Example burn call and resulting block

#### Call
The caller invokes
```
icrc152_burn({
from = [principal "abcd0123456789abcdef0123456789abcdef0123456789abcdef0123456789"];
amount = 200_000;
created_at_time = 1_753_344_740_000_000_000 : nat64;
reason = ?"Burn to reduce supply";
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The burn call example passes from = [principal "..."], which doesn’t match the Candid Account record type used by ICRC ledgers in this repo (e.g., ICRCs/ICRC-7/ICRC-7.did:3 / ICRCs/ICRC-106/ICRC-106.md:75-78). Also, the principal string shown is hex-like and not a valid Candid principal literal. Please update the example to use a Candid Account record with a valid principal text form.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same as the mint example, now uses Candid Account record format with a valid principal.

})

```
Here, `from` is the account to be debited, `amount` is the number of tokens to burn, `created_at_time` is the caller‑supplied timestamp, and `reason` provides an optional human‑readable note.

#### Resulting block

This call results in a block with btype = "122burn" and the following contents:

```
variant {
Map = vec {
record { "btype"; variant { Text = "122burn" } };
record {
"phash";
variant {
Blob = blob "\7f\89\42\a5\be\4d\af\50\3b\6e\2a\8e\9c\c7\dd\f1\c9\e8\24\f0\98\bb\d7\af\ae\d2\90\10\67\df\1e\c1\0a"
};
};
record { "ts"; variant { Nat64 = 1_753_344_740_500_000_000 : nat64 } };
record {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The burn block example encodes the block timestamp as variant { Nat64 = ... }, but the ICRC-3 Value type used in this repo does not define Nat64 (see ICRCs/ICRC-7/ICRC-7.did:6-13); it uses Nat. Please change this to variant { Nat = ... }.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed Nat64 to Nat in the burn block timestamp.

"tx";
variant {
Map = vec {
record { "mthd"; variant { Text = "152burn" } };
record {
"from";
variant {
Array = vec {
variant { Blob = blob "\ab\cd\01\23\45\67\89\ab\cd\ef\01\23\45\67\89\ab\cd\ef\01\23\45\67\89\ab\cd\ef\01\23\45\67\89\ab" }
}
};
};
record { "amt"; variant { Nat64 = 200_000 : nat64 } };
record { "ts"; variant { Nat64 = 1_753_344_740_000_000_000 : nat64 } };
record { "caller"; variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } };
Comment on lines +374 to +377
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the burn tx map example, amt and the caller-supplied ts are encoded as variant { Nat64 = ... }, but this repo’s ICRC-3 Value only supports Nat (no Nat64; see ICRCs/ICRC-7/ICRC-7.did:6-13). Please update these to variant { Nat = ... } to match the canonical mapping.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed Nat64 to Nat for both amt and ts in the burn tx map example.

record { "reason"; variant { Text = "Burn to reduce supply" } };
}
};
};
}
};


```

Here, the block records the method (`mthd = "152burn"`), the account being debited (`from`),
the burned amount (`amt`), the caller-provided timestamp (`ts`), the caller’s principal (`caller`),
and the optional `reason`.

In the method call example, the account is shown as a principal literal. In the resulting block,
the same principal is encoded canonically as `variant { Blob = ... }`. These two forms
represent the same identity; the difference is only in presentation.

Loading