From fdd05370f456b19071875573e6eabd964e194bb5 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 23 Sep 2025 15:46:31 +0200 Subject: [PATCH 1/3] initial file --- ICRCs/ICRC-154/ICRC-154.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ICRCs/ICRC-154/ICRC-154.md diff --git a/ICRCs/ICRC-154/ICRC-154.md b/ICRCs/ICRC-154/ICRC-154.md new file mode 100644 index 00000000..e69de29b From f37d8adb0d55dc0d53a4fcc86d3ec3d3c0433b2b Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 23 Sep 2025 16:01:23 +0200 Subject: [PATCH 2/3] initial version --- ICRCs/ICRC-154/ICRC-154.md | 230 +++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/ICRCs/ICRC-154/ICRC-154.md b/ICRCs/ICRC-154/ICRC-154.md index e69de29b..ec637f43 100644 --- a/ICRCs/ICRC-154/ICRC-154.md +++ b/ICRCs/ICRC-154/ICRC-154.md @@ -0,0 +1,230 @@ +# `ICRC-154`: Privileged Pause, Unpause & Deactivate API + +| Status | +|:------:| +| Draft | + +## Introduction & Motivation + +For operational safety, incident response, and regulatory compliance, ledgers often need a way to **pause** (temporarily disable critical state transitions) or **deactivate** (enter a long-lived/terminal safe state) the system. Without a standard interface and canonical on-chain recording, integrators cannot reliably determine the current operational state or attribute transitions to authorized actors. + +**ICRC-154** standardizes three privileged methods—`pause`, `unpause`, and `deactivate`—and defines the canonical mapping from method inputs to the `tx` field of the corresponding typed blocks (as defined in **ICRC-124**). It also includes lightweight query methods for current status. + +- Privileged methods (authorized principals only): + - `icrc154_pause`, `icrc154_unpause`, `icrc154_deactivate` +- Canonical `tx` mapping with namespaced `op` values (`"154..."`), caller identity, and optional human-readable `reason`. +- Recording uses **ICRC-124** block kinds (this standard **does not** add new block types). +- Read-only queries: `icrc154_is_paused`, `icrc154_is_deactivated`. + +## Overview + +ICRC-154 standardizes privileged **pause/unpause/deactivate** controls for ICRC ledgers. + +Specifically, it defines: + +- **APIs** to pause, unpause, and deactivate the ledger (privileged only). +- **Canonical `tx` mapping** rules ensuring deterministic, auditable block content. +- **Use of ICRC-124 block kinds** to record these actions: + - `btype = "124pause"`, `btype = "124unpause"`, `btype = "124deactivate"` +- **Status queries** for quick integration. + +This enables wallets, explorers, and auditors to: + +- Determine, on-chain, whether the ledger is currently **paused** or **deactivated**. +- Attribute actions to a specific authorized caller with an optional `reason`. +- Interoperate across ledgers that implement the same API and block semantics. + +## Dependencies + +This standard does not introduce new block kinds. + +- **ICRC-3** — Block log format, hashing, certification, and canonical `tx` mapping rules. +- **ICRC-124** — Defines the typed block kinds that ICRC-154 uses: + - `btype = "124pause"` + - `btype = "124unpause"` + - `btype = "124deactivate"` + +A ledger implementing ICRC-154 MUST: +- Emit the appropriate **ICRC-124** block on each successful call. +- Populate `tx.op` with namespaced values **introduced by this standard**: + `"154pause"`, `"154unpause"`, `"154deactivate"`. + +### `icrc154_pause` + +Temporarily move the ledger into a **paused** state (semantics per ICRC-124). + +#### Arguments + +``` +type PauseArgs = record { + reason : opt text; + created_at_time : nat64; +}; + +type PauseError = variant { + Unauthorized : text; // caller not permitted + AlreadyPaused : text; // ledger is already paused + AlreadyDeactivated : text; // cannot pause when deactivated + Duplicate : record { duplicate_of : nat }; + GenericError : record { error_code : nat; message : text }; +}; + +icrc154_pause : (PauseArgs) -> (variant { Ok : nat; Err : PauseError }); +``` + +#### Semantics +- Transitions the ledger into **paused** state (per ICRC-124’s operational rules). +- Appends a block of type `124pause`. +- On success, returns the **index of the created block**. +- On failure, returns an appropriate error. +- Semantics are consistent with the pause semantics defined by ICRC-124. + +#### Return Values +- Success: `variant { Ok : nat }` — created block index. +- Failure: `variant { Err : PauseError }`. + +#### Canonical `tx` Mapping +A successful call to `icrc154_pause` produces a `124pause` block. The `tx` field: + +- `op = "154pause"` +- `ts = PauseArgs.created_at_time` +- `caller = caller_principal (as Blob)` +- `reason = PauseArgs.reason` (if provided) + +Optional fields MUST be omitted if not supplied. + +### `icrc154_unpause` + +Return the ledger to **unpaused** operation (semantics per ICRC-124). + +#### Arguments +``` +type UnpauseArgs = record { + reason : opt text; + created_at_time : nat64; +}; + +type UnpauseError = variant { + Unauthorized : text; + NotPaused : text; // ledger is not currently paused + AlreadyDeactivated : text; // cannot unpause when deactivated + Duplicate : record { duplicate_of : nat }; + GenericError : record { error_code : nat; message : text }; +}; + +icrc154_unpause : (UnpauseArgs) -> (variant { Ok : nat; Err : UnpauseError }); +``` + +#### Semantics +- Transitions the ledger from **paused** to **unpaused** (per ICRC-124). +- Appends a block of type `124unpause`. +- On success, returns the **index of the created block**. +- On failure, returns an appropriate error. +- Semantics are consistent with the unpause semantics defined by ICRC-124. + +#### Return Values +- Success: `variant { Ok : nat }` — created block index. +- Failure: `variant { Err : UnpauseError }`. + +#### Canonical `tx` Mapping +A successful call to `icrc154_unpause` produces a `124unpause` block. The `tx` field: + +- `op = "154unpause"` +- `ts = UnpauseArgs.created_at_time` +- `caller = caller_principal (as Blob)` +- `reason = UnpauseArgs.reason` (if provided) + +Optional fields MUST be omitted if not supplied. + +### `icrc154_deactivate` + +Move the ledger into a **deactivated** state (long-lived/terminal safe state as per ICRC-124). + +#### Arguments +``` +type DeactivateArgs = record { + reason : opt text; + created_at_time : nat64; +}; + +type DeactivateError = variant { + Unauthorized : text; + AlreadyDeactivated : text; // ledger already deactivated + Duplicate : record { duplicate_of : nat }; + GenericError : record { error_code : nat; message : text }; +}; + +icrc154_deactivate : (DeactivateArgs) -> (variant { Ok : nat; Err : DeactivateError }); +``` + +#### Semantics +- Transitions the ledger into **deactivated** state (per ICRC-124). +- Appends a block of type `124deactivate`. +- On success, returns the **index of the created block**. +- On failure, returns an appropriate error. +- Semantics are consistent with the deactivate semantics defined by ICRC-124. + +#### Return Values +- Success: `variant { Ok : nat }` — created block index. +- Failure: `variant { Err : DeactivateError }`. + +#### Canonical `tx` Mapping +A successful call to `icrc154_deactivate` produces a `124deactivate` block. The `tx` field: + +- `op = "154deactivate"` +- `ts = DeactivateArgs.created_at_time` +- `caller = caller_principal (as Blob)` +- `reason = DeactivateArgs.reason` (if provided) + +Optional fields MUST be omitted if not supplied. + +## Query & Introspection Methods + +These read-only methods expose the current operational state. They **do not** produce blocks. + +### `icrc154_is_paused` +``` +icrc154_is_paused : () -> (bool) query; +``` +- Returns `true` iff the ledger is currently **paused** (per ICRC-124). +- Returns `false` otherwise. + +### `icrc154_is_deactivated` +``` +icrc154_is_deactivated : () -> (bool) query; +``` + +- Returns `true` iff the ledger is currently **deactivated** (per ICRC-124). +- Returns `false` otherwise. + +**Notes:** +- If the ledger is **deactivated**, it is implementation-defined in ICRC-124 whether certain operations are permanently disabled; `icrc154_unpause` MUST return `AlreadyDeactivated` in that state. +- Answers reflect the ledger state **at query time**. + +## Notes on Semantics & Scope + +- **Authorizations**: Methods are **privileged**; ledgers MUST restrict calls to authorized principals (policy/RBAC is ledger-defined or standardized separately). +- **Deduplication**: `created_at_time` is used for replay protection/deduplication (as in ICRC-1). Duplicate calls MUST return `Duplicate { duplicate_of = }` and MUST NOT produce a new block. +- **Operational semantics**: The precise operational effects of pause/unpause/deactivate (which operations are blocked, permanence of deactivation, etc.) are **entirely defined by ICRC-124**. ICRC-154 only defines the interface and canonical `tx` mapping. +- **No fees**: ICRC-154 calls do not involve fees; ledgers MUST NOT include a top-level `fee` field for these blocks. + +## Reporting Compliance + +### Supported Standards + +Ledgers implementing ICRC-154 MUST indicate compliance through the +`icrc1_supported_standards` and `icrc10_supported_standards` methods by including: +``` +variant { Vec = vec { + record { + "name"; variant { Text = "ICRC-154" }; + "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-154.md" }; + } +}}; +``` +### Supported Block Types + +ICRC-154 extends **ICRC-124** and does not introduce new block kinds. +Accordingly, ledgers implementing ICRC-154 MUST already advertise support for the +relevant ICRC-124 block kinds (`124pause`, `124unpause`, `124deactivate`) as required by ICRC-124. +No additional block types need to be reported. From 35ed67ce8e0c17243e10f34c13417e79859210b1 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 6 Oct 2025 12:41:42 +0200 Subject: [PATCH 3/3] align with presentation in 107 --- ICRCs/ICRC-154/ICRC-154.md | 304 +++++++++++++++++++++++++++++++------ 1 file changed, 256 insertions(+), 48 deletions(-) diff --git a/ICRCs/ICRC-154/ICRC-154.md b/ICRCs/ICRC-154/ICRC-154.md index ec637f43..5a3c2b87 100644 --- a/ICRCs/ICRC-154/ICRC-154.md +++ b/ICRCs/ICRC-154/ICRC-154.md @@ -49,6 +49,32 @@ A ledger implementing ICRC-154 MUST: - Populate `tx.op` with namespaced values **introduced by this standard**: `"154pause"`, `"154unpause"`, `"154deactivate"`. + +## Common Elements + +This standard follows the conventions defined in ICRC-3 and used across +ICRC-122, 123, and 124. + +- **Principals** + Represented as `variant { Blob = }`. + +- **Timestamps** + The `ts` value in the transaction (`tx`) corresponds to the + caller-supplied `created_at_time` argument. + Timestamps are measured in **nanoseconds since the Unix epoch** and + encoded as `Nat` (MUST fit into `nat64`). + +- **Parent Hash** + Each block includes `phash : Blob`, the hash of its parent block, unless it + is the genesis block (where `phash` is omitted). + +- **Caller Tracking** + Each block includes `tx.caller`, encoded as + `variant { Blob = }`, to identify the authorized + entity performing the privileged operation. + + + ### `icrc154_pause` Temporarily move the ledger into a **paused** state (semantics per ICRC-124). @@ -73,25 +99,55 @@ icrc154_pause : (PauseArgs) -> (variant { Ok : nat; Err : PauseError }); ``` #### Semantics -- Transitions the ledger into **paused** state (per ICRC-124’s operational rules). -- Appends a block of type `124pause`. -- On success, returns the **index of the created block**. -- On failure, returns an appropriate error. -- Semantics are consistent with the pause semantics defined by ICRC-124. - -#### Return Values -- Success: `variant { Ok : nat }` — created block index. -- Failure: `variant { Err : PauseError }`. -#### Canonical `tx` Mapping -A successful call to `icrc154_pause` produces a `124pause` block. The `tx` field: +**Authorization** +- The method **MUST** be callable only by a **controller** of the ledger or + other explicitly authorized principals. +- Unauthorized calls **MUST** fail with `Unauthorized`. + +**Effect (on success, non-retroactive)** +- Transition the ledger into the **paused** state, following the semantics of + ICRC-124. +- Append a new block with `btype = "124pause"`. +- The block’s `tx` field **MUST** be constructed **exactly** as defined in + **Canonical `tx` Mapping** (same keys, types, and encodings), with `ts` + derived from the `created_at_time` argument. +- On success, return the index of the newly appended block. + +**Return value** +- On success: `variant { Ok : nat }`, where `nat` is the block index. +- On failure: `variant { Err : PauseError }`. + +**Deduplication & idempotency** +- The ledger **MUST** perform deduplication using `created_at_time`. +- If a duplicate is detected, the ledger **MUST NOT** append a new block and + **MUST** return + `Err(Duplicate { duplicate_of = })`. + +**Error cases (normative)** +- `Unauthorized` — caller not permitted. +- `AlreadyPaused` — ledger already paused. +- `AlreadyDeactivated` — cannot pause when deactivated. +- `Duplicate { duplicate_of }` — duplicate transaction detected. +- `GenericError { error_code, message }` — any other failure. + +**Clarifications** +- The `tx` field uses **`ts`** for the caller-supplied timestamp + (`created_at_time`). +- Optional fields **MUST** be omitted from `tx` if not supplied. +- Representation-independent hashing (ICRC-3) applies; field presence and value + determine the hash, not field order. + +#### Canonical `tx` Mapping (normative) + +| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | +|----------|-----------------------|-------------------------| +| `op` | `Text` | Constant `"154pause"`. | +| `ts` | `Nat` | From `created_at_time` (ns since Unix epoch; MUST fit in `nat64`). | +| `caller` | `Blob` | Principal of the caller. | +| `reason` | `Text` (optional) | From `reason` argument; omit if absent. | -- `op = "154pause"` -- `ts = PauseArgs.created_at_time` -- `caller = caller_principal (as Blob)` -- `reason = PauseArgs.reason` (if provided) -Optional fields MUST be omitted if not supplied. ### `icrc154_unpause` @@ -116,25 +172,50 @@ icrc154_unpause : (UnpauseArgs) -> (variant { Ok : nat; Err : UnpauseError }); ``` #### Semantics -- Transitions the ledger from **paused** to **unpaused** (per ICRC-124). -- Appends a block of type `124unpause`. -- On success, returns the **index of the created block**. -- On failure, returns an appropriate error. -- Semantics are consistent with the unpause semantics defined by ICRC-124. -#### Return Values -- Success: `variant { Ok : nat }` — created block index. -- Failure: `variant { Err : UnpauseError }`. +**Authorization** +- The method **MUST** be callable only by a **controller** of the ledger or + other explicitly authorized principals. +- Unauthorized calls **MUST** fail with `Unauthorized`. + +**Effect (on success, non-retroactive)** +- Transition the ledger from **paused** to **unpaused** (per ICRC-124). +- Append a new block with `btype = "124unpause"`. +- The block’s `tx` field **MUST** be constructed **exactly** as defined in + **Canonical `tx` Mapping** (same keys, types, and encodings), with `ts` + derived from the `created_at_time` argument. +- On success, return the index of the newly appended block. + +**Return value** +- On success: `variant { Ok : nat }` where `nat` is the block index. +- On failure: `variant { Err : UnpauseError }`. + +**Deduplication & idempotency** +- The ledger **MUST** perform deduplication (e.g., using `created_at_time`). +- If a duplicate is detected, the ledger **MUST NOT** append a new block and + **MUST** return `Err(Duplicate { duplicate_of = })`. + +**Error cases (normative)** +- `Unauthorized` — caller not permitted. +- `NotPaused` — ledger is not currently paused. +- `AlreadyDeactivated` — cannot unpause when deactivated. +- `Duplicate { duplicate_of }` — semantically identical transaction already accepted. +- `GenericError { error_code, message }` — any other failure preventing a valid block. + +**Clarifications** +- The `tx` field uses **`ts`** for the caller-supplied timestamp (`created_at_time`). +- Optional fields **MUST** be omitted from `tx` if not supplied. +- Representation-independent hashing (ICRC-3) + +#### Canonical `tx` Mapping (normative) + +| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | +|---------|------------------------|-------------------------| +| `op` | `Text` | **Constant** `"154unpause"`. | +| `ts` | `Nat` | From `created_at_time`. | +| `caller`| `Blob` | Principal of the caller. | +| `reason`| `Text` *(optional)* | From `reason` if provided; **omit** if absent. | -#### Canonical `tx` Mapping -A successful call to `icrc154_unpause` produces a `124unpause` block. The `tx` field: - -- `op = "154unpause"` -- `ts = UnpauseArgs.created_at_time` -- `caller = caller_principal (as Blob)` -- `reason = UnpauseArgs.reason` (if provided) - -Optional fields MUST be omitted if not supplied. ### `icrc154_deactivate` @@ -158,25 +239,50 @@ icrc154_deactivate : (DeactivateArgs) -> (variant { Ok : nat; Err : DeactivateEr ``` #### Semantics -- Transitions the ledger into **deactivated** state (per ICRC-124). -- Appends a block of type `124deactivate`. -- On success, returns the **index of the created block**. -- On failure, returns an appropriate error. -- Semantics are consistent with the deactivate semantics defined by ICRC-124. -#### Return Values -- Success: `variant { Ok : nat }` — created block index. -- Failure: `variant { Err : DeactivateError }`. +**Authorization** +- The method **MUST** be callable only by a **controller** of the ledger or + other explicitly authorized principals. +- Unauthorized calls **MUST** fail with `Unauthorized`. + +**Effect (on success, non-retroactive)** +- Transition the ledger into the **deactivated** state (per ICRC-124). +- Append a new block with `btype = "124deactivate"`. +- The block’s `tx` field **MUST** be constructed **exactly** as defined in + **Canonical `tx` Mapping** (same keys, types, and encodings), with `ts` + derived from the `created_at_time` argument. +- On success, return the index of the newly appended block. -#### Canonical `tx` Mapping -A successful call to `icrc154_deactivate` produces a `124deactivate` block. The `tx` field: +**Return value** +- On success: `variant { Ok : nat }` where `nat` is the block index. +- On failure: `variant { Err : DeactivateError }`. + +**Deduplication & idempotency** +- The ledger **MUST** perform deduplication (e.g., using `created_at_time`). +- If a duplicate is detected, the ledger **MUST NOT** append a new block and + **MUST** return `Err(Duplicate { duplicate_of = })`. + +**Error cases (normative)** +- `Unauthorized` — caller not permitted. +- `AlreadyDeactivated` — ledger is already deactivated. +- `Duplicate { duplicate_of }` — semantically identical transaction already accepted. +- `GenericError { error_code, message }` — any other failure preventing a valid block. + +**Clarifications** +- The `tx` field uses **`ts`** for the caller-supplied timestamp (`created_at_time`). +- Optional fields **MUST** be omitted from `tx` if not supplied. +- Representation-independent hashing (ICRC-3) applies; field presence and value determine the hash, not field order. + +#### Canonical `tx` Mapping (normative) + +| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | +|---------|------------------------|-------------------------| +| `op` | `Text` | **Constant** `"154deactivate"`. | +| `ts` | `Nat` | From `created_at_time`. | +| `caller`| `Blob` | Principal of the caller. | +| `reason`| `Text` *(optional)* | From `reason` if provided; **omit** if absent. | -- `op = "154deactivate"` -- `ts = DeactivateArgs.created_at_time` -- `caller = caller_principal (as Blob)` -- `reason = DeactivateArgs.reason` (if provided) -Optional fields MUST be omitted if not supplied. ## Query & Introspection Methods @@ -228,3 +334,105 @@ ICRC-154 extends **ICRC-124** and does not introduce new block kinds. Accordingly, ledgers implementing ICRC-154 MUST already advertise support for the relevant ICRC-124 block kinds (`124pause`, `124unpause`, `124deactivate`) as required by ICRC-124. No additional block types need to be reported. + + + +### Example calls and resulting blocks + +#### Call +``` +icrc154_pause({ + created_at_time = 1_753_700_000_000_000_000 : nat64; + reason = ?"System maintenance window"; +}) +``` + + +#### Resulting Block```variant { + Map = vec { + record { "btype"; variant { Text = "124pause" } }; + record { "phash"; variant { Blob = blob "\aa\bb\cc\dd\ee\ff\00\11\22\33\44\55\66\77\88\99\01\23\45\67\89\ab\cd\ef\10\32\54\76\98\ba\dc\fe" } }; // illustrative + record { "ts"; variant { Nat = 1_753_700_001_000_000_000 : Nat } }; + record { + "tx"; + variant { + Map = vec { + record { "op"; variant { Text = "154pause" } }; + record { "ts"; variant { Nat = 1_753_700_000_000_000_000 : Nat } }; + record { "caller"; variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } }; + record { "reason"; variant { Text = "System maintenance window" } }; + } + }; + }; + } +}; +``` + + + +### Example: `icrc154_unpause` + +**Call with parameters** +```motoko +icrc154_unpause({ + created_at_time = 1_753_700_500_000_000_000 : nat64; + reason = ?"Maintenance completed"; +}) + +``` + +#### Resulting Block +``` +variant { + Map = vec { + record { "btype"; variant { Text = "124pause" } }; + record { "phash"; variant { Blob = blob "\aa\bb\cc\dd\ee\ff\00\11\22\33\44\55\66\77\88\99\01\23\45\67\89\ab\cd\ef\10\32\54\76\98\ba\dc\fe" } }; // illustrative + record { "ts"; variant { Nat = 1_753_700_001_000_000_000 : Nat } }; + record { + "tx"; + variant { + Map = vec { + record { "op"; variant { Text = "154pause" } }; + record { "ts"; variant { Nat = 1_753_700_000_000_000_000 : Nat } }; + record { "caller"; variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } }; + record { "reason"; variant { Text = "System maintenance window" } }; + } + }; + }; + } +}; +``` + + +### Example: `icrc154_deactivate` + + +``` +icrc154_deactivate({ + created_at_time = 1_753_800_000_000_000_000 : nat64; + reason = ?"Regulatory directive"; +}) +``` + +#### Resulting Block +``` +variant { + Map = vec { + record { "btype"; variant { Text = "124deactivate" } }; + record { "phash"; variant { Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" } }; // illustrative + record { "ts"; variant { Nat = 1_753_800_001_000_000_000 : Nat } }; + record { + "tx"; + variant { + Map = vec { + record { "op"; variant { Text = "154deactivate" } }; + record { "ts"; variant { Nat = 1_753_800_000_000_000_000 : Nat } }; + record { "caller"; variant { Blob = blob "\00\00\00\00\02\30\02\17\01\01" } }; + record { "reason"; variant { Text = "Regulatory directive" } }; + } + }; + }; + } +}; +``` +