From 346ec7f20fca83538228f20c74b6e1742f48f3fd Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 2 Apr 2025 17:11:23 +0200 Subject: [PATCH 01/11] initial draft --- ICRCs/ICRC-124/ICRC-124.md | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 ICRCs/ICRC-124/ICRC-124.md diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md new file mode 100644 index 00000000..0659daae --- /dev/null +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -0,0 +1,67 @@ +## Motivation + +ICRC-124 provides essential administrative controls for token ledgers on the Internet Computer to: + +1. **Emergency Response**: Enable temporary suspension during security incidents or suspected fraud +2. **Regulatory Compliance**: Support mechanisms for meeting legal requirements +3. **Lifecycle Management**: Allow for transparent and standardized token retirement +4. **Auditability**: Create clear records of administrative actions and their authorization +5. **Operational Stability**: Provide standardized recovery paths from exceptional states + + +## Overview of Block Types + +- **Pause Ledger**: `124pause` +- **Unpause Ledger**: `124unpause` +- **Deactivate Ledger**: `124deactivate` + +## Common Structure + +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the specific administrative action that was submitted to the ledger and which resulted in the block being created. These blocks follow the ICRC-3 structure and include: + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|----------|------------------------|----------|-------------| +| `btype` | `Text` | Yes | One of: `"124pause"`, `"124unpause"`, or `"124deactivate"` | +| `ts` | `Nat` | Yes | Timestamp (in nanoseconds) of block creation | +| `phash` | `Blob` | Yes | Hash of the previous block | +| `tx` | `Map(Text, Value)` | Yes | Encodes the pause, unpause, or deactivate transaction | + +### `tx` Field Schema + +All three block types share the same schema: + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|------------------------|----------|-------------| +| `authorizer` | `Blob` | Yes | Principal that authorized the action | +| `metadata` | `Map(Text, Value)` | Optional | Additional context (e.g., reason, governance proposal) | + +## Semantics +- When a `124pause` block is recorded, the ledger MUST reject all new transactions **except** a `124unpause` transaction. +- When a `124unpause` block is recorded, the ledger MUST resume accepting transactions (unless in terminal state). +- When a `124deactivate` block is recorded, the ledger MUST transition to a **terminal state** where: + - All new state-modifying transactions are permanently rejected + - Historical transaction data remains available for querying + - The token's transaction history is preserved as an immutable record + +Once a `124deactivate` block is recorded, the ledger's state becomes **immutable** for transfer operations while maintaining read access to historical data. + + +## Compliance Reporting + +Ledgers implementing this standard MUST return the following entries from `icrc3_supported_block_types`: + +```motoko +vec { + variant { Record = vec { + record { "btype"; variant { Text = "124pause" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "124unpause" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "124deactivate" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; + }}; +} From 52d2660f1bf84e7083728169ef2431f6aea843e4 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:00:13 +0200 Subject: [PATCH 02/11] example blocks --- ICRCs/ICRC-124/ICRC-124.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index 0659daae..953ebc04 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -46,6 +46,35 @@ All three block types share the same schema: Once a `124deactivate` block is recorded, the ledger's state becomes **immutable** for transfer operations while maintaining read access to historical data. +## Example blocks + +``` +variant { Map = vec { + // Block type identifier + record { "btype"; variant { Text = "124pause" }}; + + // Timestamp when the block was appended (nanoseconds since epoch) + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; + + // Hash of the previous block in the ledger chain + record { "phash"; variant { + Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\c0" + }}; + + // Pause transaction payload + record { "tx"; variant { Map = vec { + // Principal that authorized the pause + record { "authorizer"; variant { Blob = blob "\ab\cd\01\23\45\67\89\ef\01\23\45\67\89\ab\cd\ef\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f" }}; + + // Optional metadata + record { "metadata"; variant { Map = vec { + record { "reason"; variant { Text = "DAO vote: pause due to upgrade prep" }} + }}} + }}}; +}}; +``` + + ## Compliance Reporting Ledgers implementing this standard MUST return the following entries from `icrc3_supported_block_types`: From 735d4def2efb36df3abcb8f59b199d0aad57750c Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:03:26 +0200 Subject: [PATCH 03/11] example names --- ICRCs/ICRC-124/ICRC-124.md | 40 +++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index 953ebc04..f045be43 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -48,6 +48,8 @@ Once a `124deactivate` block is recorded, the ledger's state becomes **immutable ## Example blocks +### 124pause Example + ``` variant { Map = vec { // Block type identifier @@ -64,7 +66,7 @@ variant { Map = vec { // Pause transaction payload record { "tx"; variant { Map = vec { // Principal that authorized the pause - record { "authorizer"; variant { Blob = blob "\ab\cd\01\23\45\67\89\ef\01\23\45\67\89\ab\cd\ef\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f" }}; + record { "authorizer"; variant { Blob = blob "\ab\cd\01\23\45\67\89\ef\01\23\45\67\89\ab\cd\ef\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\01" }}; // Optional metadata record { "metadata"; variant { Map = vec { @@ -74,7 +76,43 @@ variant { Map = vec { }}; ``` +### 124unpause Example + +``` +variant { Map = vec { + record { "btype"; variant { Text = "124unpause" }}; + record { "ts"; variant { Nat = 1_741_312_737_200_000_000 : nat }}; + record { "phash"; variant { + Blob = blob "\be\ba\fe\ca\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b" + }}; + record { "tx"; variant { Map = vec { + record { "authorizer"; variant { Blob = blob "\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\01" }}; + record { "metadata"; variant { Map = vec { + record { "note"; variant { Text = "Ledger resumes after maintenance window" }} + }}} + }}}; +}}; +``` + +### 124deactivate Example + +``` +variant { Map = vec { + record { "btype"; variant { Text = "124deactivate" }}; + record { "ts"; variant { Nat = 1_741_312_737_250_000_000 : nat }}; + record { "phash"; variant { + Blob = blob "\c0\ff\ee\00\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00\11\22\33\44\55\66\77\88\99\aa\bb\cc" + }}; + record { "tx"; variant { Map = vec { + record { "authorizer"; variant { Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\01" }}; + record { "metadata"; variant { Map = vec { + record { "finalized_by"; variant { Text = "SNS DAO proposal 314" }}, + record { "note"; variant { Text = "Ledger permanently frozen to preserve historical state" }} + }}} + }}}; +}}; +``` ## Compliance Reporting Ledgers implementing this standard MUST return the following entries from `icrc3_supported_block_types`: From baa31dbbd93dcaaf96442bb4befad6015e585b34 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:11:54 +0200 Subject: [PATCH 04/11] appended -> recorded --- ICRCs/ICRC-124/ICRC-124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index f045be43..e6ee7ab8 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -55,7 +55,7 @@ variant { Map = vec { // Block type identifier record { "btype"; variant { Text = "124pause" }}; - // Timestamp when the block was appended (nanoseconds since epoch) + // Timestamp when the block was recorded (nanoseconds since epoch) record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; // Hash of the previous block in the ledger chain From 4180d2bfaf0a68450b64e749bf94b7aa34c2e3d1 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:01:31 +0200 Subject: [PATCH 05/11] consistent intro --- ICRCs/ICRC-124/ICRC-124.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index e6ee7ab8..bc327162 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -1,12 +1,8 @@ -## Motivation +# ICRC-124: Ledger Pausing, Unpausing, and Deactivation + +ICRC-124 introduces new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger. These blocks allow ledgers to be paused (temporarily halted), unpaused (resumed), or permanently deactivated (made immutable). This standard provides a consistent, auditable way to represent these state transitions within the ledger itself, ensuring transparency and enabling robust governance and recovery mechanisms. -ICRC-124 provides essential administrative controls for token ledgers on the Internet Computer to: -1. **Emergency Response**: Enable temporary suspension during security incidents or suspected fraud -2. **Regulatory Compliance**: Support mechanisms for meeting legal requirements -3. **Lifecycle Management**: Allow for transparent and standardized token retirement -4. **Auditability**: Create clear records of administrative actions and their authorization -5. **Operational Stability**: Provide standardized recovery paths from exceptional states ## Overview of Block Types From 161de4254c81b00460fa1a8cfca4e7d56b0c88f3 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 15 Apr 2025 10:42:08 +0200 Subject: [PATCH 06/11] memo -> reason --- ICRCs/ICRC-124/ICRC-124.md | 142 ++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index bc327162..1c5ed2bb 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -1,130 +1,142 @@ # ICRC-124: Ledger Pausing, Unpausing, and Deactivation -ICRC-124 introduces new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger. These blocks allow ledgers to be paused (temporarily halted), unpaused (resumed), or permanently deactivated (made immutable). This standard provides a consistent, auditable way to represent these state transitions within the ledger itself, ensuring transparency and enabling robust governance and recovery mechanisms. +## Status +Draft +## Introduction +This standard defines new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger: pausing (`124pause`), unpausing (`124unpause`), and deactivating (`124deactivate`). These actions allow ledger operations to be temporarily halted (e.g., for maintenance), resumed, or permanently stopped (making the ledger immutable). This standard provides a consistent, auditable way to represent these ledger-wide state transitions within the ledger's block history, ensuring transparency and enabling robust governance mechanisms. -## Overview of Block Types +## Motivation -- **Pause Ledger**: `124pause` -- **Unpause Ledger**: `124unpause` -- **Deactivate Ledger**: `124deactivate` +Ledger lifecycle management may require administrative actions like pausing for upgrades, unpausing after checks, or deactivating at the end of a token's useful life. These significant events must be recorded transparently on-chain. This standard provides explicit block types for these actions, defining a minimal block structure sufficient for recording the action and basic context, while relying on the ledger implementation to provide access to the full invocation details (like the authorizing principal) for comprehensive auditability. -## Common Structure +## Common Elements +This standard follows the conventions set by ICRC-3, inheriting key structural components. +- **Principals** involved (like the authorizer, though not stored *in* the `tx` field) are represented using the ICRC-3 `Value` type as `variant { Blob = }`. +- Each block includes `phash`, a `Blob` representing the hash of the parent block, and `ts`, a `Nat` representing the timestamp of the block. -Each block introduced by this standard MUST include a `tx` field containing a map that encodes the specific administrative action that was submitted to the ledger and which resulted in the block being created. These blocks follow the ICRC-3 structure and include: +## Block Types & Schema + +Each block introduced by this standard MUST include a `tx` field containing a map that encodes minimal information about the administrative action, primarily an optional reason. + +**Important Note on Transaction Recoverability:** The `tx` field defined below is intentionally minimal, containing only an optional reason string. For full auditability and transparency, ledger implementations compliant with ICRC-124 **MUST** ensure that the complete details of the original transaction invocation that led to the pause, unpause, or deactivation can be recovered independently. This includes, but is not limited to, the principal that invoked the ledger operation (the authorizer/caller), the specific ledger method called (e.g., `pause_ledger`), and the full arguments passed to that method. Mechanisms for recovering this data (e.g., via archive queries or specific lookup methods) are implementation-dependent but necessary for compliance. The `tx` field itself is *not* designed to hold this exhaustive information. + +Each block defined by this standard consists of the following top-level fields: | Field | Type (ICRC-3 `Value`) | Required | Description | |----------|------------------------|----------|-------------| -| `btype` | `Text` | Yes | One of: `"124pause"`, `"124unpause"`, or `"124deactivate"` | -| `ts` | `Nat` | Yes | Timestamp (in nanoseconds) of block creation | -| `phash` | `Blob` | Yes | Hash of the previous block | -| `tx` | `Map(Text, Value)` | Yes | Encodes the pause, unpause, or deactivate transaction | +| `btype` | `Text` | Yes | MUST be one of: `"124pause"`, `"124unpause"`, or `"124deactivate"`. | +| `ts` | `Nat` | Yes | Timestamp in nanoseconds when the block was added to the ledger. | +| `phash` | `Blob` | Yes | Hash of the parent block. | +| `tx` | `Map(Text, Value)` | Yes | Encodes minimal information about the pause/unpause/deactivate operation. See schema below. | ### `tx` Field Schema -All three block types share the same schema: +The `tx` field schema is the same for `124pause`, `124unpause`, and `124deactivate`: | Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|------------------------|----------|-------------| -| `authorizer` | `Blob` | Yes | Principal that authorized the action | -| `metadata` | `Map(Text, Value)` | Optional | Additional context (e.g., reason, governance proposal) | +| `reason` | `Text` | Optional | Human-readable reason for the administrative action. | ## Semantics -- When a `124pause` block is recorded, the ledger MUST reject all new transactions **except** a `124unpause` transaction. -- When a `124unpause` block is recorded, the ledger MUST resume accepting transactions (unless in terminal state). -- When a `124deactivate` block is recorded, the ledger MUST transition to a **terminal state** where: - - All new state-modifying transactions are permanently rejected - - Historical transaction data remains available for querying - - The token's transaction history is preserved as an immutable record -Once a `124deactivate` block is recorded, the ledger's state becomes **immutable** for transfer operations while maintaining read access to historical data. +The recording of these blocks MUST influence the behavior of the ledger according to the following semantics: +### Pause Ledger (`124pause`) +- When a `124pause` block is recorded, the ledger MUST enter a "paused" state. +- While paused, the ledger MUST reject all incoming requests that attempt to modify the ledger state (e.g., `icrc1_transfer`, `icrc2_approve`, `icrc122_mint`, `icrc123_freezeaccount`, etc.), **except** for requests that would result in recording a `124unpause` block. +- Query calls SHOULD generally remain operational. -## Example blocks +### Unpause Ledger (`124unpause`) +- When a `124unpause` block is recorded, the ledger MUST exit the "paused" state and resume normal operation, accepting transactions as defined by its implementation and other active states (unless it is in a terminal state). +- An `124unpause` block has no effect if the ledger is already unpaused or if it is in a terminal state due to deactivation. -### 124pause Example +### Deactivate Ledger (`124deactivate`) +- When a `124deactivate` block is recorded, the ledger MUST transition to a permanent "terminal" or "deactivated" state. +- In this terminal state: + - All incoming requests that attempt to modify the ledger state MUST be permanently rejected. This includes transfers, approvals, mints, burns, freezes, pauses, unpauses, and any other state-changing operations. + - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available. +- The deactivated state is irreversible. The ledger effectively becomes an immutable historical record. + +## Compliance Reporting + +Ledgers implementing this standard MUST return the following entries (including entries for other supported types like ICRC-1, ICRC-3, etc.) from `icrc3_supported_block_types`, with URLs pointing to the standards defining each block type: +```candid +vec { + // ... other supported types like ICRC-1, ICRC-3, ICRC-122, ICRC-123 ... + variant { Record = vec { + record { "btype"; variant { Text = "124pause" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL + }}; + variant { Record = vec { + record { "btype"; variant { Text = "124unpause" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL + }}; + variant { Record = vec { + record { "btype"; variant { Text = "124deactivate" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL + }}; +} ``` + +## Example Blocks + +### 124pause Example + +```candid variant { Map = vec { // Block type identifier record { "btype"; variant { Text = "124pause" }}; // Timestamp when the block was recorded (nanoseconds since epoch) - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; + record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z // Hash of the previous block in the ledger chain record { "phash"; variant { Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\c0" }}; - // Pause transaction payload + // Minimal pause transaction details record { "tx"; variant { Map = vec { - // Principal that authorized the pause - record { "authorizer"; variant { Blob = blob "\ab\cd\01\23\45\67\89\ef\01\23\45\67\89\ab\cd\ef\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\01" }}; - - // Optional metadata - record { "metadata"; variant { Map = vec { - record { "reason"; variant { Text = "DAO vote: pause due to upgrade prep" }} - }}} + // Optional reason + record { "reason"; variant { Text = "DAO vote: pause due to upgrade prep" }}; }}}; }}; ``` ### 124unpause Example -``` +```candid variant { Map = vec { record { "btype"; variant { Text = "124unpause" }}; - record { "ts"; variant { Nat = 1_741_312_737_200_000_000 : nat }}; + record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z record { "phash"; variant { Blob = blob "\be\ba\fe\ca\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b" }}; + // Minimal unpause transaction details record { "tx"; variant { Map = vec { - record { "authorizer"; variant { Blob = blob "\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\01" }}; - record { "metadata"; variant { Map = vec { - record { "note"; variant { Text = "Ledger resumes after maintenance window" }} - }}} + // Optional reason + record { "reason"; variant { Text = "Ledger resumes after maintenance window" }}; }}}; }}; ``` ### 124deactivate Example -``` +```candid variant { Map = vec { record { "btype"; variant { Text = "124deactivate" }}; - record { "ts"; variant { Nat = 1_741_312_737_250_000_000 : nat }}; + record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z record { "phash"; variant { Blob = blob "\c0\ff\ee\00\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00\11\22\33\44\55\66\77\88\99\aa\bb\cc" }}; + // Minimal deactivate transaction details record { "tx"; variant { Map = vec { - record { "authorizer"; variant { Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\01" }}; - record { "metadata"; variant { Map = vec { - record { "finalized_by"; variant { Text = "SNS DAO proposal 314" }}, - record { "note"; variant { Text = "Ledger permanently frozen to preserve historical state" }} - }}} + // Optional reason + record { "reason"; variant { Text = "Finalized by SNS DAO proposal 314. Ledger permanently frozen to preserve historical state." }}; }}}; }}; - ``` -## Compliance Reporting - -Ledgers implementing this standard MUST return the following entries from `icrc3_supported_block_types`: - -```motoko -vec { - variant { Record = vec { - record { "btype"; variant { Text = "124pause" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "124unpause" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "124deactivate" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; - }}; -} From 0ad037d3838e2b1002e0a2ed342733283e4d2855 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 15 Apr 2025 10:44:26 +0200 Subject: [PATCH 07/11] more relaxed semantics --- ICRCs/ICRC-124/ICRC-124.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index 1c5ed2bb..4c61d1b2 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -46,8 +46,9 @@ The recording of these blocks MUST influence the behavior of the ledger accordin ### Pause Ledger (`124pause`) - When a `124pause` block is recorded, the ledger MUST enter a "paused" state. -- While paused, the ledger MUST reject all incoming requests that attempt to modify the ledger state (e.g., `icrc1_transfer`, `icrc2_approve`, `icrc122_mint`, `icrc123_freezeaccount`, etc.), **except** for requests that would result in recording a `124unpause` block. -- Query calls SHOULD generally remain operational. +- While paused, the ledger MUST reject incoming requests for standard token operations such as `icrc1_transfer` and `icrc2_approve`, and potentially other non-administrative state changes like `icrc122_mint` or `icrc122_burn`. +- However, while paused, the ledger MUST continue to accept specific administrative or management operations necessary for governance or recovery. This includes operations defined in ICRC-123 (e.g., `123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) and, critically, requests that result in recording a `124unpause` block. The exact set of allowed administrative operations during a pause SHOULD be defined by the specific ledger implementation's policy. +- Query calls SHOULD generally remain operational while the ledger is paused. ### Unpause Ledger (`124unpause`) - When a `124unpause` block is recorded, the ledger MUST exit the "paused" state and resume normal operation, accepting transactions as defined by its implementation and other active states (unless it is in a terminal state). @@ -56,9 +57,9 @@ The recording of these blocks MUST influence the behavior of the ledger accordin ### Deactivate Ledger (`124deactivate`) - When a `124deactivate` block is recorded, the ledger MUST transition to a permanent "terminal" or "deactivated" state. - In this terminal state: - - All incoming requests that attempt to modify the ledger state MUST be permanently rejected. This includes transfers, approvals, mints, burns, freezes, pauses, unpauses, and any other state-changing operations. - - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available. -- The deactivated state is irreversible. The ledger effectively becomes an immutable historical record. + - All ingress calls attempting to modify the ledger state MUST be permanently rejected. This includes, but is not limited to, transfers, approvals, mints, burns, freezes, unfreezes, pauses, unpauses, and any other state-changing operations defined now or in the future. **No transactions that alter state are permitted.** + - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available indefinitely to preserve the immutable record. +- The deactivated state is irreversible. ## Compliance Reporting From 124a173940876ab702a0749d6a62fd20d1e2baad Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 19 May 2025 15:40:54 +0200 Subject: [PATCH 08/11] added caller to the tx, adapted the examples, refined semantics for stopped ledgers --- ICRCs/ICRC-124/ICRC-124.md | 142 ++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index 4c61d1b2..f0ba56b2 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -6,39 +6,40 @@ Draft ## Introduction -This standard defines new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger: pausing (`124pause`), unpausing (`124unpause`), and deactivating (`124deactivate`). These actions allow ledger operations to be temporarily halted (e.g., for maintenance), resumed, or permanently stopped (making the ledger immutable). This standard provides a consistent, auditable way to represent these ledger-wide state transitions within the ledger's block history, ensuring transparency and enabling robust governance mechanisms. +This standard defines new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger: pausing (`124pause`), unpausing (`124unpause`), and deactivating (`124deactivate`). These actions allow ledger operations to be temporarily halted (e.g., for maintenance), resumed, or permanently stopped (making the ledger immutable). This standard provides a consistent, auditable way to represent these ledger-wide state transitions within the ledger's block history, ensuring transparency and enabling robust governance mechanisms. The transaction details (`tx`) within each block explicitly include the `caller` principal that authorized the operation. ## Motivation -Ledger lifecycle management may require administrative actions like pausing for upgrades, unpausing after checks, or deactivating at the end of a token's useful life. These significant events must be recorded transparently on-chain. This standard provides explicit block types for these actions, defining a minimal block structure sufficient for recording the action and basic context, while relying on the ledger implementation to provide access to the full invocation details (like the authorizing principal) for comprehensive auditability. +Ledger lifecycle management may require administrative actions like pausing for upgrades, unpausing after checks, or deactivating at the end of a token's useful life. These significant events must be recorded transparently on-chain. This standard provides explicit block types for these actions, defining a block structure that includes the initiator (`caller`) and essential details for the operation, enhancing on-chain auditability. ## Common Elements This standard follows the conventions set by ICRC-3, inheriting key structural components. -- **Principals** involved (like the authorizer, though not stored *in* the `tx` field) are represented using the ICRC-3 `Value` type as `variant { Blob = }`. +- **Principals** (such as the `caller`) are represented using the ICRC-3 `Value` type as `variant { Blob = }`. - Each block includes `phash`, a `Blob` representing the hash of the parent block, and `ts`, a `Nat` representing the timestamp of the block. ## Block Types & Schema -Each block introduced by this standard MUST include a `tx` field containing a map that encodes minimal information about the administrative action, primarily an optional reason. +Each block introduced by this standard MUST include a `tx` field containing a map. This map encodes information about the administrative action, including the `caller` principal and an optional reason. -**Important Note on Transaction Recoverability:** The `tx` field defined below is intentionally minimal, containing only an optional reason string. For full auditability and transparency, ledger implementations compliant with ICRC-124 **MUST** ensure that the complete details of the original transaction invocation that led to the pause, unpause, or deactivation can be recovered independently. This includes, but is not limited to, the principal that invoked the ledger operation (the authorizer/caller), the specific ledger method called (e.g., `pause_ledger`), and the full arguments passed to that method. Mechanisms for recovering this data (e.g., via archive queries or specific lookup methods) are implementation-dependent but necessary for compliance. The `tx` field itself is *not* designed to hold this exhaustive information. +**Important Note on Transaction Recoverability:** The `tx` field defined below now includes the `caller` principal. However, for full auditability and transparency in complex scenarios, ledger implementations compliant with ICRC-124 **MUST** ensure that any other details of the original transaction invocation not captured in `tx` can be recovered independently. This could include, but is not limited to, the full arguments passed to the ledger method (if more complex than the data in `tx`), or any intermediary calls if the operation was part of a multi-step process. Mechanisms for recovering such extended data (e.g., via archive queries or specific lookup methods) remain implementation-dependent. The rules determining *who* is authorized to invoke these ledger state change operations are an implementation detail of the ledger's governance model. Each block defined by this standard consists of the following top-level fields: -| Field | Type (ICRC-3 `Value`) | Required | Description | +| Field    | Type (ICRC-3 `Value`) | Required | Description | |----------|------------------------|----------|-------------| -| `btype` | `Text` | Yes | MUST be one of: `"124pause"`, `"124unpause"`, or `"124deactivate"`. | -| `ts` | `Nat` | Yes | Timestamp in nanoseconds when the block was added to the ledger. | -| `phash` | `Blob` | Yes | Hash of the parent block. | -| `tx` | `Map(Text, Value)` | Yes | Encodes minimal information about the pause/unpause/deactivate operation. See schema below. | +| `btype`  | `Text`                 | Yes      | MUST be one of: `"124pause"`, `"124unpause"`, or `"124deactivate"`. | +| `ts`     | `Nat`                  | Yes      | Timestamp in nanoseconds when the block was added to the ledger. | +| `phash`  | `Blob`                 | Yes      | Hash of the parent block. | +| `tx`     | `Map(Text, Value)`     | Yes      | Encodes information about the pause/unpause/deactivate operation, including the caller. See schema below. | ### `tx` Field Schema The `tx` field schema is the same for `124pause`, `124unpause`, and `124deactivate`: -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|------------------------|----------|-------------| -| `reason` | `Text` | Optional | Human-readable reason for the administrative action. | +| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | +|--------------|----------------------------------------------------------|----------|-------------| +| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | +| `reason`     | `Text`                                                   | Optional | Human-readable reason for the administrative action. | ## Semantics @@ -46,8 +47,8 @@ The recording of these blocks MUST influence the behavior of the ledger accordin ### Pause Ledger (`124pause`) - When a `124pause` block is recorded, the ledger MUST enter a "paused" state. -- While paused, the ledger MUST reject incoming requests for standard token operations such as `icrc1_transfer` and `icrc2_approve`, and potentially other non-administrative state changes like `icrc122_mint` or `icrc122_burn`. -- However, while paused, the ledger MUST continue to accept specific administrative or management operations necessary for governance or recovery. This includes operations defined in ICRC-123 (e.g., `123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) and, critically, requests that result in recording a `124unpause` block. The exact set of allowed administrative operations during a pause SHOULD be defined by the specific ledger implementation's policy. +- While paused, the ledger MUST reject incoming requests for standard token operations such as `icrc1_transfer`, `icrc2_approve`, and other non-administrative state changes such as those defined in ICRC-122 (e.g., `122mint`, `122burn`). +- However, while paused, the ledger MUST continue to accept specific administrative or management operations necessary for governance or recovery. This includes operations defined in ICRC-123 (e.g., `123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) and, critically, requests that result in recording a `124unpause` block. Ledger implementations MAY also permit requests that result in recording a `124deactivate` block while the ledger is paused, according to their defined governance policies. The exact set of allowed administrative operations during a pause SHOULD be defined by the specific ledger implementation's policy. - Query calls SHOULD generally remain operational while the ledger is paused. ### Unpause Ledger (`124unpause`) @@ -57,8 +58,8 @@ The recording of these blocks MUST influence the behavior of the ledger accordin ### Deactivate Ledger (`124deactivate`) - When a `124deactivate` block is recorded, the ledger MUST transition to a permanent "terminal" or "deactivated" state. - In this terminal state: - - All ingress calls attempting to modify the ledger state MUST be permanently rejected. This includes, but is not limited to, transfers, approvals, mints, burns, freezes, unfreezes, pauses, unpauses, and any other state-changing operations defined now or in the future. **No transactions that alter state are permitted.** - - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available indefinitely to preserve the immutable record. +    - All ingress calls attempting to modify the ledger state MUST be permanently rejected. This includes, but is not limited to, transfers, approvals, mints, burns, freezes, unfreezes, pauses, unpauses, and any other state-changing operations defined now or in the future. **No transactions that alter state are permitted.** +    - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available indefinitely to preserve the immutable record. - The deactivated state is irreversible. ## Compliance Reporting @@ -67,20 +68,21 @@ Ledgers implementing this standard MUST return the following entries (including ```candid vec { - // ... other supported types like ICRC-1, ICRC-3, ICRC-122, ICRC-123 ... - variant { Record = vec { - record { "btype"; variant { Text = "124pause" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL - }}; - variant { Record = vec { - record { "btype"; variant { Text = "124unpause" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL - }}; - variant { Record = vec { - record { "btype"; variant { Text = "124deactivate" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }}; // Placeholder URL - }}; +    // ... other supported types like ICRC-1, ICRC-3, ICRC-122, ICRC-123 ... +    variant { Record = vec { +        record { "btype"; variant { Text = "124pause" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL +    }}; +    variant { Record = vec { +        record { "btype"; variant { Text = "124unpause" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL +    }}; +    variant { Record = vec { +        record { "btype"; variant { Text = "124deactivate" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL +    }}; } + ``` ## Example Blocks @@ -89,39 +91,44 @@ vec { ```candid variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "124pause" }}; - - // Timestamp when the block was recorded (nanoseconds since epoch) - record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z - - // Hash of the previous block in the ledger chain - record { "phash"; variant { - Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\c0" - }}; - - // Minimal pause transaction details - record { "tx"; variant { Map = vec { - // Optional reason - record { "reason"; variant { Text = "DAO vote: pause due to upgrade prep" }}; - }}}; +    // Block type identifier +    record { "btype"; variant { Text = "124pause" }}; + +    // Timestamp when the block was recorded (nanoseconds since epoch) +    record { "ts"; variant { Nat = 1_747_774_560_000_000_000 : nat }}; // Example: 2025-05-19T12:56:00Z + +    // Hash of the previous block in the ledger chain +    record { "phash"; variant { +        Blob = blob "\de\ad\be\ef\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff\10\20\30\40\50\60\70\80\90\a0\b0\c0" +    }}; + +    // Pause transaction details +    record { "tx"; variant { Map = vec { + // The principal that invoked the pause_ledger operation +        record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\03" }}; // Example caller principal (e.g., a governance canister) +        // Optional reason +        record { "reason"; variant { Text = "DAO vote #78: pause for scheduled maintenance." }}; +    }}}; }}; + ``` ### 124unpause Example ```candid variant { Map = vec { - record { "btype"; variant { Text = "124unpause" }}; - record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z - record { "phash"; variant { - Blob = blob "\be\ba\fe\ca\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b" - }}; - // Minimal unpause transaction details - record { "tx"; variant { Map = vec { - // Optional reason - record { "reason"; variant { Text = "Ledger resumes after maintenance window" }}; - }}}; +    record { "btype"; variant { Text = "124unpause" }}; +    record { "ts"; variant { Nat = 1_747_778_160_000_000_000 : nat }}; // Example: 2025-05-19T13:56:00Z +    record { "phash"; variant { +        Blob = blob "\be\ba\fe\ca\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b" +    }}; +    // Unpause transaction details +    record { "tx"; variant { Map = vec { + // The principal that invoked the unpause_ledger operation +        record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\03" }}; // Example caller principal +        // Optional reason +        record { "reason"; variant { Text = "Ledger resumes after maintenance window (DAO vote #79)." }}; +    }}}; }}; ``` @@ -129,15 +136,18 @@ variant { Map = vec { ```candid variant { Map = vec { - record { "btype"; variant { Text = "124deactivate" }}; - record { "ts"; variant { Nat = 1_741_384_155_000_000_000 : nat }}; // Approx 2025-04-15T10:29:15Z - record { "phash"; variant { - Blob = blob "\c0\ff\ee\00\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00\11\22\33\44\55\66\77\88\99\aa\bb\cc" - }}; - // Minimal deactivate transaction details - record { "tx"; variant { Map = vec { - // Optional reason - record { "reason"; variant { Text = "Finalized by SNS DAO proposal 314. Ledger permanently frozen to preserve historical state." }}; - }}}; +    record { "btype"; variant { Text = "124deactivate" }}; +    record { "ts"; variant { Nat = 1_747_864_560_000_000_000 : nat }}; // Example: 2025-05-20T12:56:00Z +    record { "phash"; variant { +        Blob = blob "\c0\ff\ee\00\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00\11\22\33\44\55\66\77\88\99\aa\bb\cc" +    }}; +    // Deactivate transaction details +    record { "tx"; variant { Map = vec { + // The principal that invoked the deactivate_ledger operation +        record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\04" }}; // Example caller (e.g., project multisig or final DAO vote) +        // Optional reason +        record { "reason"; variant { Text = "Token project sunset. Ledger permanently archived as per SNS DAO proposal #314." }}; +    }}}; }}; + ``` From a5e8887c164ee51dd66fd36f2b8950c2987a3611 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 12 Sep 2025 12:58:58 +0200 Subject: [PATCH 09/11] alignment with the new ICRC-3 version --- ICRCs/ICRC-124/ICRC-124.md | 137 ++++++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index f0ba56b2..19d169c4 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -6,83 +6,101 @@ Draft ## Introduction -This standard defines new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger: pausing (`124pause`), unpausing (`124unpause`), and deactivating (`124deactivate`). These actions allow ledger operations to be temporarily halted (e.g., for maintenance), resumed, or permanently stopped (making the ledger immutable). This standard provides a consistent, auditable way to represent these ledger-wide state transitions within the ledger's block history, ensuring transparency and enabling robust governance mechanisms. The transaction details (`tx`) within each block explicitly include the `caller` principal that authorized the operation. +This standard defines new block types for recording administrative actions that control the operational state of an ICRC-compliant ledger: pausing (`124pause`), unpausing (`124unpause`), and deactivating (`124deactivate`). These actions allow ledger operations to be temporarily halted (e.g., for maintenance), resumed, or permanently stopped (making the ledger immutable). This standard provides a consistent, auditable way to represent these ledger-wide state transitions within the ledger's block history, ensuring transparency and enabling robust governance mechanisms. ## Motivation -Ledger lifecycle management may require administrative actions like pausing for upgrades, unpausing after checks, or deactivating at the end of a token's useful life. These significant events must be recorded transparently on-chain. This standard provides explicit block types for these actions, defining a block structure that includes the initiator (`caller`) and essential details for the operation, enhancing on-chain auditability. +Ledger lifecycle management may require administrative actions like pausing for upgrades, unpausing after checks, or deactivating at the end of a token's useful life. These significant events must be recorded transparently on-chain. This standard provides explicit block types for these actions, defining a minimal block structure sufficient for semantics, while allowing optional provenance for auditability. ## Common Elements -This standard follows the conventions set by ICRC-3, inheriting key structural components. -- **Principals** (such as the `caller`) are represented using the ICRC-3 `Value` type as `variant { Blob = }`. -- Each block includes `phash`, a `Blob` representing the hash of the parent block, and `ts`, a `Nat` representing the timestamp of the block. + +This standard follows the conventions set by ICRC-3, inheriting key structural components: + +- **Principals** are represented using the ICRC-3 `Value` type as `variant { Blob = }`. +- **Timestamps:** `ts` (and any optional `created_at_time`) are **nanoseconds since the Unix epoch**, encoded as `Nat` but **MUST fit into `nat64`**. +- **Parent hash:** `phash : Blob` **MUST** be present if the block has a parent (omit for the genesis block). ## Block Types & Schema -Each block introduced by this standard MUST include a `tx` field containing a map. This map encodes information about the administrative action, including the `caller` principal and an optional reason. +Each block introduced by this standard MUST include a `tx` field containing a map. This map encodes the **minimal information** about the ledger state change. Additional provenance MAY be included but is not required for semantics. -**Important Note on Transaction Recoverability:** The `tx` field defined below now includes the `caller` principal. However, for full auditability and transparency in complex scenarios, ledger implementations compliant with ICRC-124 **MUST** ensure that any other details of the original transaction invocation not captured in `tx` can be recovered independently. This could include, but is not limited to, the full arguments passed to the ledger method (if more complex than the data in `tx`), or any intermediary calls if the operation was part of a multi-step process. Mechanisms for recovering such extended data (e.g., via archive queries or specific lookup methods) remain implementation-dependent. The rules determining *who* is authorized to invoke these ledger state change operations are an implementation detail of the ledger's governance model. +Each block consists of the following top-level fields: -Each block defined by this standard consists of the following top-level fields: +| Field | Type (ICRC-3 `Value`) | Required | Description | +|-------|------------------------|----------|-------------| +| `btype` | `Text` | Yes | MUST be one of: `"124pause"`, `"124unpause"`, or `"124deactivate"`. | +| `ts` | `Nat` | Yes | Timestamp (ns since Unix epoch) when the block was added to the ledger. MUST fit in `nat64`. | +| `phash` | `Blob` | Yes/No | Hash of the parent block; omitted only for the genesis block. | +| `tx` | `Map(Text, Value)` | Yes | Minimal operation details (see below). | -| Field    | Type (ICRC-3 `Value`) | Required | Description | -|----------|------------------------|----------|-------------| -| `btype`  | `Text`                 | Yes      | MUST be one of: `"124pause"`, `"124unpause"`, or `"124deactivate"`. | -| `ts`     | `Nat`                  | Yes      | Timestamp in nanoseconds when the block was added to the ledger. | -| `phash`  | `Blob`                 | Yes      | Hash of the parent block. | -| `tx`     | `Map(Text, Value)`     | Yes      | Encodes information about the pause/unpause/deactivate operation, including the caller. See schema below. | +### `tx` Field Schema (minimal) -### `tx` Field Schema +For all `124pause`, `124unpause`, and `124deactivate` blocks: -The `tx` field schema is the same for `124pause`, `124unpause`, and `124deactivate`: +- No required fields are needed for semantics. +- The presence of the block type alone (`btype`) determines the state transition. -| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | -|--------------|----------------------------------------------------------|----------|-------------| -| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | -| `reason`     | `Text`                                                   | Optional | Human-readable reason for the administrative action. | +### Optional Provenance (non-semantic) -## Semantics +Producers MAY include fields such as: + +- `caller : Blob` — principal that invoked the operation. +- `reason : Text` — human-readable context. +- `created_at_time : Nat` — caller-supplied timestamp (ns; MUST fit nat64). +- `policy_ref : Text` — identifier for proposal/vote/policy. +- `op : Text` — namespaced operation identifier, e.g. `148pause_ledger`. -The recording of these blocks MUST influence the behavior of the ledger according to the following semantics: +These fields MUST NOT affect semantics or verification. Verifiers MUST ignore them. + +> **Informative note (recoverability):** Implementations **SHOULD** provide mechanisms (e.g., archives or lookups) to retrieve extended invocation context not present in `tx` when useful for audits. The authorization model that permits these actions is implementation-defined. + +--- + +## Semantics ### Pause Ledger (`124pause`) - When a `124pause` block is recorded, the ledger MUST enter a "paused" state. -- While paused, the ledger MUST reject incoming requests for standard token operations such as `icrc1_transfer`, `icrc2_approve`, and other non-administrative state changes such as those defined in ICRC-122 (e.g., `122mint`, `122burn`). -- However, while paused, the ledger MUST continue to accept specific administrative or management operations necessary for governance or recovery. This includes operations defined in ICRC-123 (e.g., `123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) and, critically, requests that result in recording a `124unpause` block. Ledger implementations MAY also permit requests that result in recording a `124deactivate` block while the ledger is paused, according to their defined governance policies. The exact set of allowed administrative operations during a pause SHOULD be defined by the specific ledger implementation's policy. -- Query calls SHOULD generally remain operational while the ledger is paused. +- While paused, the ledger MUST reject all state-changing operations except those required for governance or recovery (e.g., `124unpause`, optionally `124deactivate`, and operations like freeze/unfreeze if permitted by governance policy). +- Query calls SHOULD remain operational. ### Unpause Ledger (`124unpause`) -- When a `124unpause` block is recorded, the ledger MUST exit the "paused" state and resume normal operation, accepting transactions as defined by its implementation and other active states (unless it is in a terminal state). -- An `124unpause` block has no effect if the ledger is already unpaused or if it is in a terminal state due to deactivation. +- When a `124unpause` block is recorded, the ledger MUST exit the "paused" state and resume normal operation, unless it is already in the terminal state due to deactivation. +- An `124unpause` block has no effect if the ledger is already unpaused or deactivated. ### Deactivate Ledger (`124deactivate`) -- When a `124deactivate` block is recorded, the ledger MUST transition to a permanent "terminal" or "deactivated" state. -- In this terminal state: -    - All ingress calls attempting to modify the ledger state MUST be permanently rejected. This includes, but is not limited to, transfers, approvals, mints, burns, freezes, unfreezes, pauses, unpauses, and any other state-changing operations defined now or in the future. **No transactions that alter state are permitted.** -    - Query calls retrieving historical data (e.g., transaction history, past balances via `icrc3_get_blocks`) MUST remain available indefinitely to preserve the immutable record. +- When a `124deactivate` block is recorded, the ledger MUST transition to a permanent "terminal" state. +- In this state: + - All ingress calls that modify state MUST be rejected (transfers, approvals, mints, burns, freezes, pauses, unpauses, etc.). + - Query calls retrieving historical data MUST remain available. - The deactivated state is irreversible. +--- + +## Guidance for Standards That Define Methods + +A standard that defines ledger methods which produce ICRC-124 blocks (e.g., “pause ledger” or “deactivate ledger”) SHOULD: + +1. **Include `tx.op`** in the resulting block’s `tx` map. + - Use a namespaced value per ICRC-3: `` (e.g., `148pause_ledger`). + - This makes the call uniquely identifiable and prevents collisions across standards. + +2. **Define a canonical mapping** from the method’s call parameters to the block’s minimal `tx` fields. + - Since 124 blocks have no required fields, only provenance may be mapped. + +3. **Document deduplication inputs** (if any). If the method uses a caller-supplied timestamp, put it in `tx.created_at_time`. + +--- + ## Compliance Reporting -Ledgers implementing this standard MUST return the following entries (including entries for other supported types like ICRC-1, ICRC-3, etc.) from `icrc3_supported_block_types`, with URLs pointing to the standards defining each block type: +Ledgers implementing this standard MUST return the following entries (along with entries for other supported block types) from `icrc3_supported_block_types`: ```candid vec { -    // ... other supported types like ICRC-1, ICRC-3, ICRC-122, ICRC-123 ... -    variant { Record = vec { -        record { "btype"; variant { Text = "124pause" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL -    }}; -    variant { Record = vec { -        record { "btype"; variant { Text = "124unpause" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL -    }}; -    variant { Record = vec { -        record { "btype"; variant { Text = "124deactivate" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md)" }}; // Placeholder URL -    }}; + record { block_type = "124pause"; url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }; + record { block_type = "124unpause"; url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }; + record { block_type = "124deactivate"; url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-124.md" }; } - ``` ## Example Blocks @@ -151,3 +169,30 @@ variant { Map = vec { }}; ``` + +### Informative Example: Integration with a Standardized Method +ICRC-124 defines only block types and their semantics. It does not define any ledger methods. +However, future standards may specify methods that map directly to these block types. + +For illustration, suppose a future standard (e.g., ICRC-148) introduces the method: +``` +icrc148_pause_ledger : (opt text) -> result nat +``` + +Invoking this method with an optional reason could produce a `124pause` block: +``` +variant { Map = vec { + record { "btype"; variant { Text = "124pause" }}; + record { "ts"; variant { Nat = 1_747_900_000_000_000_000 : nat }}; + record { "phash"; variant { + Blob = blob "\aa\bb\cc\dd\ee\ff\00\11\22\33\44\55\66\77\88\99" + }}; + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "148pause_ledger" }}; + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\03" }}; + record { "reason"; variant { Text = "DAO vote #101: emergency pause" }}; + }}}; +}}; +``` + +This example is non-normative and illustrates how a standardized method can map into the ICRC-124 block structure while using a namespaced `tx.op` for unambiguous identification. The authoritative semantics remain defined by the ICRC-124 block types. \ No newline at end of file From 47a8c6255b5d8c8414469f4de9b67190f30966a8 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 26 Sep 2025 10:15:33 +0200 Subject: [PATCH 10/11] clarify it's blocks that are standardised --- ICRCs/ICRC-124/ICRC-124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-124/ICRC-124.md b/ICRCs/ICRC-124/ICRC-124.md index 19d169c4..90b00654 100644 --- a/ICRCs/ICRC-124/ICRC-124.md +++ b/ICRCs/ICRC-124/ICRC-124.md @@ -1,4 +1,4 @@ -# ICRC-124: Ledger Pausing, Unpausing, and Deactivation +# ICRC-124: Pause, Unpause & Deactivate Blocks ## Status From 2057b4173cc9f01d6de65839f7cdfc59e86bba49 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 26 Sep 2025 10:37:11 +0200 Subject: [PATCH 11/11] copied content from ICRC-1 here --- ICRCs/BUILD.bazel | 25 ++ ICRCs/HASHINGVALUES.md | 102 +++++ ICRCs/ICRC-1.did | 52 +++ ICRCs/ICRC-2.did | 62 +++ ICRCs/ICRC-3.did | 57 +++ ICRCs/README.md | 814 +++++++++++++++++++++++++++++++++++++++ ICRCs/TextualEncoding.md | 79 ++++ 7 files changed, 1191 insertions(+) create mode 100644 ICRCs/BUILD.bazel create mode 100644 ICRCs/HASHINGVALUES.md create mode 100644 ICRCs/ICRC-1.did create mode 100644 ICRCs/ICRC-2.did create mode 100644 ICRCs/ICRC-3.did create mode 100644 ICRCs/README.md create mode 100644 ICRCs/TextualEncoding.md diff --git a/ICRCs/BUILD.bazel b/ICRCs/BUILD.bazel new file mode 100644 index 00000000..a094347a --- /dev/null +++ b/ICRCs/BUILD.bazel @@ -0,0 +1,25 @@ +load("//bazel:didc_test.bzl", "didc_subtype_test") + +exports_files([ + "ICRC-2.did", +]) + +genrule( + name = "candid", + srcs = [":README.md"], + outs = ["ICRC-2-generated.did"], + cmd_bash = "$(location @lmt) $(SRCS); mv ICRC-2.did $@", + exec_tools = ["@lmt"], +) + +didc_subtype_test( + name = "check_generated_subtype", + did = ":ICRC-2-generated.did", + previous = "ICRC-2.did", +) + +didc_subtype_test( + name = "check_source_subtype", + did = "ICRC-2.did", + previous = ":ICRC-2-generated.did", +) diff --git a/ICRCs/HASHINGVALUES.md b/ICRCs/HASHINGVALUES.md new file mode 100644 index 00000000..52489cf2 --- /dev/null +++ b/ICRCs/HASHINGVALUES.md @@ -0,0 +1,102 @@ +## Representation independent hashing + +The following pseudocode specifies how to calculate the (representation independent) hash of an element of the Value type. Some test vectors to check compliance of an implementation with this specification follow. + +``` +type Value = variant { + Blob : blob, + Text : text, + Nat : nat, + Int : int, + Array : vec Value, + Map : vec (text, Value) +}; + +Function hash_value(value) + Initialize hasher as a new instance of SHA256 + + Match value with + Nat: + Return SHA256_hash(LEB128_encode(value)) + Int: + Return SHA256_hash(SLEB128_encode(value)) + Text: + Return SHA256_hash(UTF8_encode(value)) + Blob: + Return SHA256_hash(value) + Array: + For each element in value + Update hasher with hash_value(element) + Return hasher.finalize() + Map: + Initialize hashes as empty list + For each (key, val) in value + Add (SHA256_hash(UTF8_encode(key)), hash_value(val)) to hashes + Sort hashes in lexicographical order + For each (key_hash, val_hash) in hashes + Update hasher with key_hash + Update hasher with val_hash + Return hasher.finalize() + Else: + Return error "unsupported value type" +End Function + +Function LEB128_encode(nat_input) + Convert nat_input to LEB128 byte encoding +End Function + +Function SLEB128_encode(integer_input) + Convert integer_input to SLEB128 byte encoding +End Function + +Function UTF8_encode(text) + Convert text to UTF-8 byte array and return it +End Function + +Function SHA256_hash(data) + Initialize a new SHA256 hasher + Update hasher with data + Return hasher.finalize() +End Function + +``` + +## Test vectors + + +```ignorelang +input: Nat(42) +expected output: 684888c0ebb17f374298b65ee2807526c066094c701bcc7ebbe1c1095f494fc1 +``` + +```ignorelang +input: Int(-42) +expected output: de5a6f78116eca62d7fc5ce159d23ae6b889b365a1739ad2cf36f925a140d0cc +``` + + +```ignorelang +input: Text("Hello, World!"), +expected output: dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f +``` + +```ignorelang +input: Blob(b'\x01\x02\x03\x04') +expected output: 9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a +``` + +```ignorelang +input: Array([Nat(3), Text("foo"), Blob(b'\x05\x06')]) +expected output: 514a04011caa503990d446b7dec5d79e19c221ae607fb08b2848c67734d468d6 +``` + +```ignorelang +input: Map([("from", Blob(b'\x00\xab\xcd\xef\x00\x12\x34\x00\x56\x78\x9a\x00\xbc\xde\xf0\x00\x01\x23\x45\x67\x89\x00\xab\xcd\xef\x01')), + ("to", Blob(b'\x00\xab\x0d\xef\x00\x12\x34\x00\x56\x78\x9a\x00\xbc\xde\xf0\x00\x01\x23\x45\x67\x89\x00\xab\xcd\xef\x01')), + ("amount", Nat(42)), + ("created_at", Nat(1699218263)), + ("memo", Nat(0)) + ]) + +expected output: c56ece650e1de4269c5bdeff7875949e3e2033f85b2d193c2ff4f7f78bdcfc75 +``` \ No newline at end of file diff --git a/ICRCs/ICRC-1.did b/ICRCs/ICRC-1.did new file mode 100644 index 00000000..f9307ca8 --- /dev/null +++ b/ICRCs/ICRC-1.did @@ -0,0 +1,52 @@ +// Number of nanoseconds since the UNIX epoch in UTC timezone. +type Timestamp = nat64; + +// Number of nanoseconds between two [Timestamp]s. +type Duration = nat64; + +type Subaccount = blob; + +type Account = record { + owner : principal; + subaccount : opt Subaccount; +}; + +type TransferArgs = record { + from_subaccount : opt Subaccount; + to : Account; + amount : nat; + fee : opt nat; + memo : opt blob; + created_at_time : opt Timestamp; +}; + +type TransferError = variant { + BadFee : record { expected_fee : nat }; + BadBurn : record { min_burn_amount : nat }; + InsufficientFunds : record { balance : nat }; + TooOld; + CreatedInFuture: record { ledger_time : Timestamp }; + Duplicate : record { duplicate_of : nat }; + TemporarilyUnavailable; + GenericError : record { error_code : nat; message : text }; +}; + +type Value = variant { + Nat : nat; + Int : int; + Text : text; + Blob : blob; +}; + +service : { + icrc1_metadata : () -> (vec record { text; Value; }) query; + icrc1_name : () -> (text) query; + icrc1_symbol : () -> (text) query; + icrc1_decimals : () -> (nat8) query; + icrc1_fee : () -> (nat) query; + icrc1_total_supply : () -> (nat) query; + icrc1_minting_account : () -> (opt Account) query; + icrc1_balance_of : (Account) -> (nat) query; + icrc1_transfer : (TransferArgs) -> (variant { Ok : nat; Err : TransferError }); + icrc1_supported_standards : () -> (vec record { name : text; url : text }) query; +} diff --git a/ICRCs/ICRC-2.did b/ICRCs/ICRC-2.did new file mode 100644 index 00000000..153bf49d --- /dev/null +++ b/ICRCs/ICRC-2.did @@ -0,0 +1,62 @@ +type Account = record { + owner : principal; + subaccount : opt blob; +}; + +type ApproveArgs = 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; +}; + +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 TransferFromArgs = 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 AllowanceArgs = record { + account : Account; + spender : Account; +}; + +service : { + icrc1_supported_standards : () -> (vec record { name : text; url : text }) query; + + icrc2_approve : (ApproveArgs) -> (variant { Ok : nat; Err : ApproveError }); + icrc2_transfer_from : (TransferFromArgs) -> (variant { Ok : nat; Err : TransferFromError }); + icrc2_allowance : (AllowanceArgs) -> (record { allowance : nat; expires_at : opt nat64 }) query; +} diff --git a/ICRCs/ICRC-3.did b/ICRCs/ICRC-3.did new file mode 100644 index 00000000..70c5fc39 --- /dev/null +++ b/ICRCs/ICRC-3.did @@ -0,0 +1,57 @@ +type Value = variant { + Blob : blob; + Text : text; + Nat : nat; + Int : int; + Array : vec Value; + Map : vec record { text; Value }; +}; + +type GetArchivesArgs = record { + // The last archive seen by the client. + // The Ledger will return archives coming + // after this one if set, otherwise it + // will return the first archives. + from : opt principal; +}; + +type GetArchivesResult = vec record { + // The id of the archive + canister_id : principal; + + // The first block in the archive + start : nat; + + // The last block in the archive + end : nat; +}; + +type GetBlocksArgs = vec record { start : nat; length : nat }; + +type GetBlocksResult = record { + // Total number of blocks in the + // block log + log_length : nat; + + blocks : vec record { id : nat; block: Value }; + + archived_blocks : vec record { + args : GetBlocksArgs; + callback : func (GetBlocksArgs) -> (GetBlocksResult) query; + }; +}; + +type DataCertificate = record { + // See https://internetcomputer.org/docs/current/references/ic-interface-spec#certification + certificate : blob; + + // CBOR encoded hash_tree + hash_tree : blob; +}; + +service : { + icrc3_get_archives : (GetArchivesArgs) -> (GetArchivesResult) query; + icrc3_get_tip_certificate : () -> (opt DataCertificate) query; + icrc3_get_blocks : (GetBlocksArgs) -> (GetBlocksResult) query; + icrc3_supported_block_types : () -> (vec record { block_type : text; url : text }) query; +}; diff --git a/ICRCs/README.md b/ICRCs/README.md new file mode 100644 index 00000000..652c5da7 --- /dev/null +++ b/ICRCs/README.md @@ -0,0 +1,814 @@ +# `ICRC-3`: Block Log + +| Status | +|:------:| +| [Accepted](https://dashboard.internetcomputer.org/proposal/128824) | + +`ICRC-3` is a standard for accessing the block log of a Ledger on the [Internet Computer](https://internetcomputer.org). + +`ICRC-3` specifies: +1. A way to fetch the archive nodes of a Ledger +2. A generic format for sharing the block log without information loss. This includes the fields that a block must have +3. A mechanism to verify the block log on the client side to allow downloading the block log via query calls +4. A way for new standards to define new transaction types compatible with ICRC-3 + +## Archive Nodes + +The Ledger must expose an endpoint `icrc3_get_archives` listing all the canisters containing its blocks. + +## Block Log + +The block log is a list of blocks where each block contains the hash of its parent (`phash`). The parent of a block `i` is block `i-1` for `i>0` and `null` for `i=0`. + +``` + ┌─────────────────────────┐ ┌─────────────────────────┐ + | Block i | | Block i+1 | + ├─────────────────────────┤ ├─────────────────────────┤ +◄──| phash = hash(Block i-1) |◄─────────| phash = hash(Block i) | + | ... | | ... | + └─────────────────────────┘ └─────────────────────────┘ + +``` + +## Value + +The [candid](https://github.com/dfinity/candid) format supports sharing information even when the client and the server involved do not have the same schema (see the [Upgrading and subtyping](https://github.com/dfinity/candid/blob/master/spec/Candid.md#upgrading-and-subtyping) section of the candid spec). While this mechanism allows to evolve services and clients +independently without breaking them, it also means that a client may not receive all the information that the server is sending, e.g. in case the client schema lacks some fields that the server schema has. + +This loss of information is not an option for `ICRC-3`. The client must receive the same exact data the server sent in order to verify it. Verification is done by hashing the data and checking that the result is consistent with what has been certified by the server. + +For this reason, `ICRC-3` introduces the `Value` type which never changes: + +``` +type Value = variant { + Blob : blob; + Text : text; + Nat : nat; + Int : int; + Array : vec Value; + Map : vec record { text; Value }; +}; +``` + +Servers MUST serve the block log as a list of `Value` where each `Value` represents a single block in the block log. + +## Value Hash + +`ICRC-3` specifies a standard hash function over `Value`. + +This hash function SHOULD be used by Ledgers to calculate the hash of the parent of a block and by clients to verify the downloaded block log. + +The hash function is the [representation-independent hashing of structured data](https://internetcomputer.org/docs/current/references/ic-interface-spec#hash-of-map) used by the IC: +- the hash of a `Blob` is the hash of the bytes themselves +- the hash of a `Text` is the hash of the bytes representing the text +- the hash of a `Nat` is the hash of the [`leb128`](https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128) encoding of the number +- the hash of an `Int` is the hash of the [`sleb128`](https://en.wikipedia.org/wiki/LEB128#Signed_LEB128) encoding of the number +- the hash of an `Array` is the hash of the concatenation of the hashes of all the elements of the array +- the hash of a `Map` is the hash of the concatenation of all the hashed items of the map sorted lexicographically. A hashed item is the tuple composed by the hash of the key and the hash of the value. + +Pseudocode for representation-independent hashing of `Value`, together with test vectors to check compliance with the specification can be found [`here`](HASHINGVALUES.md). + +## Blocks Verification + +The Ledger MUST certify the last block (tip) recorded. The Ledger MUST allow to download the certificate via the `icrc3_get_tip_certificate` endpoint. The certificate follows the [IC Specification for Certificates](https://internetcomputer.org/docs/current/references/ic-interface-spec#certification). The certificate is comprised of a tree containing the certified data and the signature. The tree MUST contain two labeled values (leaves): +1. `last_block_index`: the index of the last block in the chain. The value MUST be expressed as [`leb128`](https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128) +2. `last_block_hash`: the hash of the last block in the chain + +Clients SHOULD download the tip certificate first and then download the blocks backward starting from `last_block_index` and validate the blocks in the process. + +Validation of block `i` is done by checking the block hash against +1. if `i + 1 < len(chain)` then the parent hash `phash` of the block `i+1` +2. otherwise the `last_block_hash` in the tip certificate. + +## Generic Block Schema + +An ICRC-3 compliant Block + +1. MUST be a `Value` of variant `Map` +2. MUST contain a field `phash: Blob` which is the hash of its parent if it has a parent block +3. SHOULD contain a field `btype: Text` which uniquely describes the type of the Block. If this field is not set then the block type falls back to ICRC-1 and ICRC-2 for backward compatibility purposes + + +### Kinds of Blocks + +An ICRC-3 block can record different kinds of information. Some blocks record the result of a transaction submitted by a user. These typically contain a `tx` field describing the user’s intent and any parameters they provided. + +Other blocks may be created by the ledger itself, for example during an upgrade, migration, or system operation, to record changes in ledger state that did not come from a user call. + +The `tx` field, when present, encodes the **intent** or **state change payload** associated with the block: +- In user-initiated blocks, `tx` reflects the call parameters, subject to the canonical mapping defined for that block type. +- In system-generated blocks, `tx` may capture the minimal structure required to interpret the block’s meaning and effect, as defined in the specification for that block type. + +The exact meaning of a block and its `tx` structure is determined by its block type. +Block types and their schemas are defined either by legacy standards (e.g., ICRC-1, ICRC-2) or by newer standards introducing `btype`-tagged blocks. + + + +## Principles and Rules for ICRC-3 Blocks + +The following principles guide the evolution and interpretation of ICRC-3 and any standards that build on it. + +### 1. Core State Transitions +- Every block type MUST define the **core state transition** it represents: the deterministic change to ledger state implied by the block’s minimal `tx` structure, *ignoring fees or ledger-specific policies*. +- This transition is the canonical meaning of a block — what balances, allowances, or other state variables change as a direct consequence of the block. +- Fee handling, metadata, and ledger-specific policies are layered on top of this transition. + +### 2. Separation of `btype` and `tx` +- The `btype` field defines the **minimal semantic structure** of a block — the set of fields in `tx` required to fully determine its core state transition. +- Standards that introduce a new `btype` MUST: + - Assign a unique identifier for the `btype`. + - Specify the minimal `tx` structure required for interpreting that block type. + - Define the block’s **core state transition** in terms of this minimal structure. +- Standards that define methods producing blocks MUST: + - Specify which `btype` the method produces. + - Define the **canonical mapping** from method call parameters to the `tx` field of the resulting block. + - Ensure that `tx` contains only parameters explicitly provided by the caller (except where the block type definition requires otherwise). + +### 3. Avoiding Collisions in `tx` +- No two standardized methods may produce `tx` values that are indistinguishable when interpreted under ICRC-3 rules. +- To avoid collisions across standards, `tx` MUST include an operation field (`op`) whose value is namespaced with the introducing standard’s number as a prefix (e.g., `122freeze_account`). This namespacing requirement applies to typed blocks; legacy ICRC-1/2 blocks keep their historical `op` values (e.g., `"xfer"`, `"mint"`, `"burn"`). + + +### 4. Inclusion of the User Call in `tx` +- The `tx` field must faithfully capture the structure of the user call that triggered the block. +- All call parameters that are part of the method’s canonical mapping MUST be included exactly as provided by the caller. +- Optional parameters that were not present in the call MUST be omitted from `tx`. + +### 5. Future-Proofing and Extensibility +- Additional non-semantic fields (e.g., metadata, hashes, references) MAY be added to `tx` without introducing a new `btype`, provided: + - They do not affect the block’s **core state transition**. + - They are ignored by block verification and interpretation logic that only relies on the minimal `tx` structure defined by the `btype`. +- Any change to the minimal semantic structure of a block REQUIRES introducing a new `btype`. + +### Note on Ledger-Specific Fields +- Blocks may include additional fields specific to a given standard or ledger (e.g., `fee`, metadata, references). +- ICRC-3 defines how such fields are recorded and verified, but **does not define their economic or behavioral semantics**. Those semantics must be specified by the standard that introduces the block type (e.g., fee rules in ICRC-107). + +## Semantics of Blocks: Evaluation Model + +To ensure consistency across standards and implementations, the semantics of any block must be interpretable through the following evaluation model. Each standard that defines a block type specifies how to “plug into” this model (by defining its minimal `tx` schema, pre-fee transition, fee payer, etc.). + +1. Identify block type + • If `btype` is present, use it. + • If no `btype`, fall back to legacy ICRC-1/2 inference from `tx.op`. + +2. Validate `tx` structure + • Check that `tx` includes all required fields defined for the block type. + • Ensure no extra *semantic* fields beyond those defined by the block type are present. + • Optional caller-provided fields may appear if allowed by the canonical mapping. + +3. Derive pre-fee state transition + • Apply the deterministic state change implied by `tx`, ignoring any fees. + • Example: debit/credit balances, mint, burn, update allowance. + +4. Apply fee (if applicable) + • If the block type involves fees, determine the **effective fee** according to the rules defined for that block type. + • Deduct the fee from the account designated as the **fee payer** for this block type. + • Adjust balances accordingly (e.g., for mints: `to` receives `amt - fee`). + • The destination or handling of the fee (burn, treasury, etc.) may be specified by the block type or by a separate fee standard (e.g., ICRC-107). When unspecified, the destination/handling of the fee is ledger-defined; ledgers may burn fees or route them to a treasury. See ICRC-107 for a standardized way to expose fee handling. + + +5. Enforce validity conditions + • Validate that all preconditions and invariants defined by the block type’s standard are satisfied. + • This includes checks such as sufficient balances, allowance coverage, or limits on fees, as applicable. + + + +## Interaction with Other Standards + +ICRC-3 defines how blocks are structured and verified. Other standards extend this by either: +(1) introducing new block types (`btype`), or +(2) defining canonical mappings from standardized method calls to existing block types. + +### Standards That Introduce Block Types +A standard that defines a new block type MUST: +- Assign a unique `btype`. +- Specify the minimal `tx` structure required to interpret the block and determine its effect on ledger state. +- Define semantics using the **Semantics of Blocks: Evaluation Model** (pre-fee transition, fee hook, post-conditions). +- If the block type involves fees, clarify what the **effective fee** is (i.e., the fee that is actually charged) and **define who pays**, via a fee payer expression resolvable from block fields. +- Optionally reference the applicable fee standard (e.g., ICRC-107) to specify **where the fee goes** (burn, treasury, etc.). + + +### Standards That Define Methods +A standard that defines a method which produces blocks MUST: +- Specify which `btype` (if any) the method produces. +- Define the canonical mapping from method inputs to the `tx` field of the resulting block. +- Ensure all required fields from the block type’s minimal schema are populated. +- Include only caller-provided optional fields; omit optionals that were not supplied. +- Include an `op` field in `tx` to identify the operation and avoid collisions. + +This division of responsibility ensures that: +- Block types define **what blocks mean** (semantics). +- Methods define **how blocks are created** (intent capture). +- Tooling and clients can rely on predictable, non-colliding `tx` values. + + +#### Namespacing for Operations +To avoid collisions across standards, `tx.op` MUST be namespaced: +- `op = icrc_number op_name` +- `icrc_number`: a non-zero digit followed by zero or more digits +- `op_name`: starts with a lowercase letter, then lowercase letters, digits, `_` or `-` + +**Examples:** `1transfer`, `2transfer_from`, `123freeze_account`. +Legacy ICRC-1/2 blocks are not retrofitted with namespaced `op` values; they retain their historical operation names (e.g., `"xfer"`, `"mint"`, `"burn"`). + + +### Note on Fees +ICRC-3 standardizes how fees are recorded in blocks, but it does not prescribe how fees are calculated or collected. +Every standard that introduces a block type involving fees MUST specify who the fee payer is so that responsibility is unambiguous. +The rules for interpreting the amount and destination of fees are defined in ICRC-107 (Fee Handling in Blocks). Ledgers that do not yet implement ICRC-107 MAY still produce valid ICRC-3 blocks, but their fee behavior will be ledger-specific until aligned with ICRC-107. + + +## Supported Standards + +An ICRC-3 compatible Ledger MUST expose an endpoint listing all the supported block types via the endpoint `icrc3_supported_block_types`. + +- For **typed** blocks, the ledger MUST only produce blocks whose `"btype"` value is included in this list. +- For **legacy** ICRC-1/2 blocks (no `"btype"`), the ledger MUST include the conventional identifiers (e.g., `"1xfer"`, `"2approve"`) in this list to advertise support, even though the blocks themselves do not carry a `"btype"` field. + + +## [ICRC-1](../ICRC-1/README.md) and [ICRC-2](../ICRC-2/README.md) Block Schema + + +This section describes how ICRC-1 and ICRC-2 operations are represented in ICRC-3-compliant blocks. These blocks follow the **legacy format**, meaning they do not have a `btype` field. +Instead, their type is inferred directly from the content of the `tx` field, which records the canonical mapping of the original method call. + +### Legacy ICRC-1 and ICRC-2 Block Structure + +ICRC-1 and ICRC-2 blocks **MUST NOT** include a `btype` field. These standards use the legacy block format where the block type is determined exclusively from the content of the `tx` field. + +Legacy blocks therefore follow a fixed generic structure, with semantics inferred from `tx.op`. + +--- + +#### Generic Legacy Block + +A legacy block: + +- **MUST** be a `Value::Map` containing at least: + - `"phash"`: `Blob` — the parent hash. + - `"ts"`: `Nat` — the timestamp (in nanoseconds since Unix epoch) set by the ledger when the block was created. + - `"tx"`: `Value::Map` — representing the user’s transaction intent. +- **MAY** include: + - `"fee": Nat` — the fee actually charged by the ledger, if any. + + + + +### Effective Fee + +The **effective fee** is the fee charged by the ledger. For a block, this is computed as: + +1. If a top-level `"fee"` is present, then `effective_fee = fee`. +2. Otherwise, if `tx.fee` is present, then `effective_fee = tx.fee`. +3. Otherwise, `effective_fee = 0`. + +- `tx.fee` records what the caller supplied; when the top-level `"fee"` is absent, it also implies the ledger charged that same amount. +- If both top-level `"fee"` and `tx.fee` are present and differ, the top-level `"fee"` is authoritative. +- Ledgers **MAY** omit the top-level `"fee"` when it equals `tx.fee` to save space. +- What happens with the effective fee (e.g., burning it, sending it to a collector account, redistributing) is up to the ledger implementation. A common policy is to burn fees. +- **ICRC-107** specifies how fee collection and handling are formalized. Ledgers that wish to expose their fee policy in a standardized way should follow that specification. + + +--- + +#### Transfer Block (`op = "xfer"`) + +**Structure** +- **MUST** contain `tx.op = "xfer"`. +- **MUST** contain `tx.from : Account`. +- **MUST** contain `tx.to : Account`. +- **MUST** contain `tx.amt : Nat`. +- **MAY** contain `tx.fee : Nat` if provided by the caller. +- **MAY** contain `tx.memo : Blob` if provided by the caller. +- **MAY** contain `tx.ts : Nat` if provided by the caller (`created_at_time`). +- **MAY** contain `tx.spender : Account` if created via `icrc2_transfer_from`. + +**Semantics** +Transfers debit `tx.amt` (and any fee) from `tx.from` and credit `tx.amt` to `tx.to`. +If `tx.spender` is present, the operation is executed under an approval, which must cover at least `tx.amt + effective_fee`. The allowance is reduced accordingly. + +**Minting-account prohibition.** A block with `tx.op = "xfer"` MUST NOT have either endpoint equal to the minting account. If `tx.from` equals the minting account, the operation MUST be represented as `op = "mint"`. If `tx.to` equals the minting account, the operation MUST be represented as `op = "burn"`. It is strictly prohibited for both `tx.from` and `tx.to` to be the minting account simultaneously. + + +**Fee payer:** `tx.from`. + +--- + +#### Mint Block (`op = "mint"`) + +**Structure** +- **MUST** contain `tx.op = "mint"`. +- **MUST** contain `tx.to : Account`. +- **MUST** contain `tx.amt : Nat`. +- **MUST NOT** contain `tx.from`. +- **MUST NOT** contain `tx.fee`. +- **MAY** contain `tx.spender : Account`. +- **MAY** contain `tx.memo : Blob` if provided by the caller. +- **MAY** contain `tx.ts : Nat` if provided by the caller. + +Mints create `tx.amt` new tokens. If an effective fee is charged, it is deducted from `tx.to` immediately, so `tx.to` receives `tx.amt - effective_fee` (require `effective_fee ≤ tx.amt`). +If `tx.spender` is present, the mint is executed under an approval on the minting account; that approval **MUST** be at least `tx.amt` and **MUST** be reduced by `tx.amt`. + + +**Fee payer:** `tx.to`. + +--- + +#### Burn Block (`op = "burn"`) + +**Structure** +- **MUST** contain `tx.op = "burn"`. +- **MUST** contain `tx.from : Account`. +- **MUST** contain `tx.amt : Nat`. +- **MUST NOT** contain `tx.to`. +- **MUST NOT** contain `tx.fee`. +- **MAY** contain `tx.memo : Blob` if provided by the caller. +- **MAY** contain `tx.ts : Nat` if provided by the caller. + +**Semantics** +Burns remove `tx.amt` tokens from `tx.from`. Any fee is also debited from `tx.from`. + +**Fee payer:** `tx.from`. + +--- + +#### Approve Block (`op = "approve"`) + +**Structure** +- **MUST** contain `tx.op = "approve"`. +- **MUST** contain `tx.from : Account`. +- **MUST** contain `tx.spender : Account`. +- **MUST** contain the allowance field as defined by ICRC-2 (e.g., `tx.amt : Nat`). +- **MAY** contain `tx.fee : Nat` if provided by the caller. +- **MAY** contain `tx.memo : Blob` if provided by the caller. +- **MAY** contain `tx.ts : Nat` if provided by the caller. + +**Semantics** +Approvals set or update the allowance of `tx.spender` on `tx.from`. +Any subsequent `xfer` block with `tx.spender` consumes the allowance. +Fees (if any) are debited from `tx.from`. +If the approval is set on the minting account, it can be consumed by `icrc2_transfer_from` mints; such mints reduce the allowance by `tx.amt`. + + +**Fee payer:** `tx.from`. + +--- + + + + +### Compliance Reporting + +Although legacy ICRC-1 and ICRC-2 blocks do not include the `btype` field, ledgers **MUST** still report their supported block types via the `icrc3_supported_block_types` endpoint. By convention, the following identifiers are used to describe the types of these legacy blocks: + +- "1burn" for burn blocks +- "1mint" for mint blocks +- "1xfer" for `icrc1_transfer` blocks +- "2xfer" for `icrc2_transfer_from` transfer blocks +- "2burn" for `icrc2_transfer_from` burn blocks +- "2mint" for `icrc2_transfer_from` delegated mint blocks +- "2approve" for `icrc2_approve` blocks + + +### Account Type + +ICRC-1 Account is represented as an `Array` containing the `owner` bytes and optionally the subaccount bytes. Two examples of accounts, one with subaccount and the second without are below. + +Example of account representation as an array with two blobs, one for the owner principal and the second for the subaccount: +``` +variant { + Array = vec { + variant { Blob = blob "\00\00\00\00\00\f0\13x\01\01" }; + variant { Blob = blob "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" }; + }; +}; +``` + + +Example of account representation as an array with one blob encoding the owner principal. +``` +variant { + Array = vec { + variant { Blob = blob "\00\00\00\00\00\f0\13x\01\01" }; + }; +}; + +``` + + +### Canonical `tx` Mapping + +Each ICRC-1 or ICRC-2 method call maps deterministically to the `tx` field of the resulting block. Only parameters provided by the user are included — optional fields that are omitted in the call MUST NOT appear in `tx`. + +All fields are encoded using the ICRC-3 `Value` type. + +--- + +#### `icrc1_transfer` + +**Call parameters:** + +``` +icrc1_transfer: record { + to: Account; + amount: Nat; + fee: opt Nat; + memo: opt Blob; + from_subaccount: opt blob; + created_at_time: opt Nat; +} +``` + +**Regular Transfer** — when neither the sender nor recipient is the minting account: + +- `op = "xfer"` +- `from = [caller]` if `from_subaccount` is not provided +- `from = [caller, from_subaccount]` if provided +- `to = to` +- `amt = amount` +- `fee = fee` if provided +- `memo = memo` if provided +- `ts = created_at_time` if provided + + + + +**Transfer from the Minting Account (→ Mint)** — when `[caller]` or `[caller, from_subaccount]` equals the minting account: + +- `op = "mint"` +- `to = to` +- `amt = amount` +- `memo = memo` if provided +- `ts = created_at_time` if provided +- `from` and `fee` MUST NOT be present + +**Transfer to the Minting Account (→ Burn)** — when `to` equals the minting account: + +- `op = "burn"` +- `from = [caller]` if `from_subaccount` is not provided +- `from = [caller, from_subaccount]` if provided +- `amt = amount` +- `memo = memo` if provided +- `ts = created_at_time` if provided +- `to` and `fee` MUST NOT be present + + +**Invalid combination (rejected).** If both the sender and the recipient resolve to the minting account in the same call, the ledger MUST reject the call; no legacy `xfer` block is produced for this case. + + + +### Canonical Examples of `icrc1_transfer` Blocks + +Each of the following examples represents a canonical block resulting from an `icrc1_transfer` call. These examples illustrate different scenarios depending on which optional fields were included in the call. Only parameters explicitly provided by the caller appear in the resulting `tx`. + + + +#### Example 1: Transfer with required parameters only +This example shows an `icrc1_transfer` call where the caller only specifies the mandatory fields: `to` and `amount`. No `memo`, `created_at_time`, or explicit `fee` are provided. The block still contains a top-level `fee` field since the ledger applies the default transfer fee. + +``` +variant { + Map = vec { + record { "fee"; variant { Nat64 = 10_000 : nat64 } }; + record { + "phash"; + variant { + Blob = blob "\b8\0d\29\e5\91\60\4c\d4\60\3a\2a\7c\c5\33\14\21\27\b8\23\e9\a5\24\b7\14\43\24\4b\2d\d5\b0\86\13" + }; + }; + record { "ts"; variant { Nat64 = 1_753_344_727_778_561_060 : nat64 } }; + record { + "tx"; + variant { + Map = vec { + record { "amt"; variant { Nat64 = 85_224_322_205 : nat64 } }; + record { "from"; variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } } } }; + record { "op"; variant { Text = "xfer" } }; + record { + "to"; + variant { + Array = vec { + variant { Blob = blob "\09\14\61\93\79\7a\6c\ab\86\17\ee\f9\5f\16\40\94\d3\f8\7c\e9\0d\9e\b2\7e\01\40\0c\79\02" }; + } + }; + }; + } + }; + }; + } +}; +``` + +--- + +#### Example 2: Mint to user account +This example represents an `icrc1_transfer` call where the `from` account is the minting account. This results in a mint block. The caller specifies `to` and `amount`. No `fee`, `memo`, or `created_at_time` are provided. + +``` +variant { + Map = vec { + record { + "phash"; + variant { + Blob = blob "\c2\b1\32\6a\5e\09\0e\10\ad\be\f3\4c\ba\fd\bc\90\18\3f\38\a7\3e\73\61\cc\0a\fa\99\89\3d\6b\9e\47" + }; + }; + record { "ts"; variant { Nat64 = 1_753_344_737_123_456_789 : nat64 } }; + record { + "tx"; + variant { + Map = vec { + record { "amt"; variant { Nat64 = 500_000_000 : nat64 } }; + 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 { "op"; variant { Text = "mint" } }; + } + }; + }; + } +}; +``` + +--- + +#### Example 3: Burn from user account +This example represents an `icrc1_transfer` call where the destination `to` is the minting account. This results in a burn block. The caller specifies `from` and `amount`. No `fee`, `memo`, or `created_at_time` are provided. + +``` +variant { + Map = vec { + 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_000_000_000 : nat64 } }; + record { + "tx"; + variant { + Map = vec { + record { "amt"; variant { Nat64 = 42_000_000 : nat64 } }; + record { + "from"; + variant { + Array = vec { + variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" }; + } + }; + }; + record { "op"; variant { Text = "burn" } }; + } + }; + }; + } +}; +``` + +##### `icrc2_transfer_from` + +**Call parameters:** + +``` + icrc2_transfer_from: record { + spender_subaccount: opt blob; + from: Account; + to: Account; + amount: Nat; + fee: opt Nat; + memo: opt Blob; + created_at_time: opt Nat; +} +``` + +**Regular Transfer** — when the `to` account is not the minting account: + +- `op = "xfer"` +- `from = from` (as passed in the call) +- `spender = [caller]` if `spender_subaccount` is not provided +- `spender = [caller, spender_subaccount]` if provided +- `to = to` +- `amt = amount` +- `fee = fee` if provided +- `memo = memo` if provided +- `ts = created_at_time` if provided + + +**Burn Transfer** — when the `to` account is the minting account: + +- `op = "burn"` +- `from = from` (as passed in the call) +- `spender = [caller]` if `spender_subaccount` is not provided +- `spender = [caller, spender_subaccount]` if provided +- `amt = amount` +- `memo = memo` if provided +- `ts = created_at_time` if provided + + +**Mint Transfer** — when the `from` account is the minting account: + +- `op = "mint"` +- `spender = [caller]` if `spender_subaccount` is not provided +- `spender = [caller, spender_subaccount]` if provided +- `to = to` +- `amt = amount` +- `memo = memo` if provided +- `ts = created_at_time` if provided +- `from` and `fee` **MUST NOT** be present + +**Invalid combination (rejected).** If both `from` and `to` are the minting account in the same `icrc2_transfer_from` call, the ledger MUST reject the call; no `xfer`/`burn`/`mint` block is produced for this case. + + + +### Canonical Examples of `icrc2_transfer_from` Blocks + +Each of the following examples represents a canonical block resulting from an `icrc2_transfer_from` call. These examples illustrate different scenarios depending on which optional fields were included in the call. Only parameters explicitly provided by the caller appear in the resulting `tx`. + + +#### Example 4: Transfer from approval +This example shows an `icrc2_transfer_from` call where the recipient is a regular user account. Only the required fields are provided: `from`, `to`, and `amount`, and the spender subaccount is omitted (defaults to `null`, i.e., the default subaccount). + +``` +variant { + Map = vec { + record { "fee"; variant { Nat64 = 10_000 : nat64 } }; + 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_728_820_625_931 : nat64 } }; + record { + "tx"; + variant { + Map = vec { + record { "amt"; variant { Nat64 = 50_419_165_435 : nat64 } }; + record { + "from"; + variant { + Array = vec { + variant { Blob = blob "\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc\cc" } + } + }; + }; + record { + "spender"; + variant { + Array = vec { + variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } + } + }; + }; + record { "op"; variant { Text = "xfer" } }; + record { + "to"; + variant { + Array = vec { + variant { Blob = blob "\3a\b2\17\29\53\18\70\89\73\bf\db\61\ed\28\c7\22\dc\63\2e\60\3d\50\cd\6c\9e\36\b2\ef\02" } + } + }; + }; + } + }; + }; + } +}; +``` + +--- + +#### Example 5: Burn from approval (to minting account with memo) +This example shows an `icrc2_transfer_from` call where the destination `to` is the minting account, resulting in a burn block. The call includes a `memo`, and no `spender_subaccount` is provided. Therefore, the `spender` field consists only of the caller's principal (default subaccount). This example demonstrates a minimal burn operation initiated via approval, with memo included. + +``` +variant { + Map = vec { + record { + "phash"; + variant { + Blob = blob "\9a\cd\20\3f\b0\11\fb\7f\e2\2a\1d\f2\c1\dd\22\6a\2f\1e\f6\88\d3\b0\9f\be\8d\2e\c5\70\f2\b4\a1\77" + }; + }; + record { "ts"; variant { Nat64 = 1_753_344_750_000_000_000 : nat64 } }; + record { + "tx"; + variant { + Map = vec { + record { "amt"; variant { Nat64 = 200_000 : nat64 } }; + 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 { + "spender"; + variant { + Array = vec { + variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } + } + }; + }; + record { "op"; variant { Text = "burn" } }; + record { "memo"; variant { Blob = blob "burn by spender" } }; + } + }; + }; + } +}; +``` + + + + +## Specification + +### `icrc3_get_blocks` + +``` +type Value = variant { + Blob : blob; + Text : text; + Nat : nat; + Int : int; + Array : vec Value; + Map : vec record { text; Value }; +}; + +type GetArchivesArgs = record { + // The last archive seen by the client. + // The Ledger will return archives coming + // after this one if set, otherwise it + // will return the first archives. + from : opt principal; +}; + +type GetArchivesResult = vec record { + // The id of the archive + canister_id : principal; + + // The first block in the archive + start : nat; + + // The last block in the archive + end : nat; +}; + +type GetBlocksArgs = vec record { start : nat; length : nat }; + +type GetBlocksResult = record { + // Total number of blocks in the block log + log_length : nat; + + // Blocks found locally to the Ledger + blocks : vec record { id : nat; block: Value }; + + // List of callbacks to fetch the blocks that are not local + // to the Ledger, i.e. archived blocks + archived_blocks : vec record { + args : GetBlocksArgs; + callback : func (GetBlocksArgs) -> (GetBlocksResult) query; + }; +}; + +service : { + icrc3_get_archives : (GetArchivesArgs) -> (GetArchivesResult) query; + icrc3_get_blocks : (GetBlocksArgs) -> (GetBlocksResult) query; +}; +``` + +### `icrc3_get_tip_certificate` + +``` +// See https://internetcomputer.org/docs/current/references/ic-interface-spec#certification +type DataCertificate = record { + + // Signature of the root of the hash_tree + certificate : blob; + + // CBOR encoded hash_tree + hash_tree : blob; +}; + +service : { + icrc3_get_tip_certificate : () -> (opt DataCertificate) query; +}; +``` + +### `icrc3_supported_block_types` + +``` +service : { + icrc3_supported_block_types : () -> (vec record { block_type : text; url : text }) query; +}; +``` diff --git a/ICRCs/TextualEncoding.md b/ICRCs/TextualEncoding.md new file mode 100644 index 00000000..14accb56 --- /dev/null +++ b/ICRCs/TextualEncoding.md @@ -0,0 +1,79 @@ +# Textual encoding of ICRC-1 accounts + +| Status | +|:------:| +| Accepted | + +This document specifies the canonical textual representation of ICRC-1 accounts. + +ICRC-1 accounts have two components: the owner (up to 29 bytes) and the subaccount (32 bytes). +If the subaccount is not present, it's considered to be equal to an array with 32 zero bytes. + +```candid +type Account = { owner : principal; subaccount : opt blob }; +``` + +## Default accounts + +The textual representation of the account coincides with the textual encoding of the account owner's principal if the `subaccount` is not set or equal to an array with 32 zero bytes. + +``` +Account.toText(record { + owner = principal "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae"; + subaccount = null; +}) = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae" +``` + +``` +Account.toText(record { + owner = principal "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + subaccount = opt vec {0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0}; +}) = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae" +``` + +## Non-default accounts + +The textual representation of accounts with non-default subaccounts consists of the following parts: + 1. The textual encoding of the owner's principal as described in the [Internet Computer Interface Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#textual-ids). + 2. A dash ('-') separating the principal from the checksum. + 3. The CRC-32 checksum of concatenated bytes of the principal (up to 29 bytes) and the subaccount (32 bytes), encoded in [Base 32 encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-6), without padding, and using lower-case letters. + 4. A period ('.') separating the checksum from the subaccount. + 5. The hex-encoded bytes of the subaccount with all the leading '0' characters removed. + +``` +-. +``` + +``` +Account.toText({ owner; ?subaccount }) = { + let checksum = bigEndianBytes(crc32(concatBytes(Principal.toBytes(owner), subaccount))); + Principal.toText(owner) # '-' # base32LowerCaseNoPadding(checksum) # '.' # trimLeading('0', hex(subaccount)) +} +``` + +In the following example, `dfxgiyy` is the checksum and `102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20` is the hex representation of the subaccount with stripped leading zeros. + +``` +Account.toText(record { + owner = principal "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", + subaccount = opt vec {1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29;30;31;32}; +}) = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-dfxgiyy.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" +``` + +## Examples + +| Text | Result | Comment | +|:----:|:------:|:-------:| +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae` | OK: `{ owner = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", subaccount = null }` | A valid principal is a valid account. | +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-q6bn32y.` | Error | The representation is not canonical: default subaccount should be omitted. | +| `k2t6j2nvnp4zjm3-25dtz6xhaac7boj5gayfoj3xs-i43lp-teztq-6ae` | Error | Invalid principal encoding. | +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-6cc627i.1` | OK: `{ owner = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", subaccount = opt blob "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01" }` | | +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-6cc627i.01` | Error | The representation is not canonical: leading zeros are not allowed in subaccounts. | +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae.1` | Error | Missing check sum. | +| `k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-dfxgiyy.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20` | OK: `{ owner = "k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae"; subaccount = opt blob "\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f\20" }` | | + +## Libraries + +* [`ic-js`](https://github.com/dfinity/ic-js/tree/main/packages/ledger-icrc#gear-encodeicrcaccount) (JavaScript). +* [`icrc-ledger-types`](https://docs.rs/icrc-ledger-types/0.1.2/icrc_ledger_types/icrc1/account/struct.Account.html) version `0.1.2` and higher (Rust). +* [`Account.mo`](https://github.com/dfinity/ICRC-1/blob/main/ref/Account.mo) (Motoko)