From 874e51dbbfdc97cb7ff5df801fae92f367f798ce Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 12 Mar 2025 11:49:41 +0100 Subject: [PATCH 01/19] initial dir and file for ICRC-123 freeze/unfreeze account --- ICRCs/ICRC-123/ICRC-123.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ICRCs/ICRC-123/ICRC-123.md diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md new file mode 100644 index 00000000..e69de29b From 8595bd53065ebfb4360e26a1eb5215709b9e2fca Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 12 Mar 2025 11:51:30 +0100 Subject: [PATCH 02/19] draft block format --- ICRCs/ICRC-123/ICRC-123.md | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index e69de29b..3620ba35 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -0,0 +1,86 @@ +# ICRC-123: Account Freezing & Unfreezing + +## Account Representation +ICRC-1 Accounts are represented as an `Array` containing two `Blob` values: +- The first `Blob` is the `owner` principal. +- The second `Blob` is the `subaccount`, which is optional. If no subaccount is specified, this field MUST be an empty `Blob`. + +## Block Types +- **Freeze Account**: `123freeze` +- **Unfreeze Account**: `123unfreeze` + +## Block Schema +### 123freeze Block +Each `123freeze` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +|----------------|----------------------|-------------| +| `btype` | `Text` | MUST be `123freeze` | +| `account` | `Array(vec { Blob, Blob })` | The account being frozen. The first `Blob` is the owner principal, and the second `Blob` is the subaccount. | +| `authorizer` | `Blob` | The principal who authorized the freeze. | +| `metadata` | `Map(Text, Blob)` | Optional metadata for additional details. | + +### 123unfreeze Block +Each `123unfreeze` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +|----------------|----------------------|-------------| +| `btype` | `Text` | MUST be `123unfreeze` | +| `account` | `Array(vec { Blob, Blob })` | The account being unfrozen. | +| `authorizer` | `Blob` | The principal who authorized the unfreeze. | +| `metadata` | `Map(Text, Blob)` | Optional metadata for additional details. | + +### Interesting Aspects +- Accounts are represented using an **array of two blobs**: the first blob is the owner principal, and the second blob is the subaccount. +- The `authorizer` field records the principal who authorized the operation. + +## Expected Semantics +### Freeze +- When `123freeze` is recorded, the specified `account` MUST be prevented from initiating or receiving transfers. +- Transactions involving a frozen account MUST return an error. +- The `123freeze` block MUST be permanently recorded in the ledger. + +### Unfreeze +- When `123unfreeze` is recorded, the specified `account` MUST regain full transfer functionality. +- The `123unfreeze` block MUST be permanently recorded in the ledger. + +## Example Blocks +### 123freeze Example +``` +variant { Map = vec { + record { "btype"; variant { Text = "123freeze" }}; + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; + }} }; + record { "authorizer"; variant { Blob = blob "\b1\a2\c3\d4\e5\f6" }}; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; +}}; +``` + +### 123unfreeze Example +``` +variant { Map = vec { + record { "btype"; variant { Text = "123unfreeze" }}; + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; + }} }; + record { "authorizer"; variant { Blob = blob "\b1\a2\c3\d4\e5\f6" }}; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; +}}; +``` + +## Compliance Reporting +Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: + +``` +vec { + variant { Record = vec { + record { "btype"; variant { Text = "123freeze" }}; + record { "url"; variant { Text = "https://example.com/icrc-123" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreeze" }}; + record { "url"; variant { Text = "https://example.com/icrc-123" }}; + }}; +} +``` From 1775f5067d1ec6405f81fdba7cd26a93ddff89fe Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 13 Mar 2025 15:36:56 +0100 Subject: [PATCH 03/19] version with freeze/unfreeze principal --- ICRCs/ICRC-123/ICRC-123.md | 180 +++++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 55 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 3620ba35..3b204aa1 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -1,86 +1,156 @@ -# ICRC-123: Account Freezing & Unfreezing - -## Account Representation -ICRC-1 Accounts are represented as an `Array` containing two `Blob` values: -- The first `Blob` is the `owner` principal. -- The second `Blob` is the `subaccount`, which is optional. If no subaccount is specified, this field MUST be an empty `Blob`. - -## Block Types -- **Freeze Account**: `123freeze` -- **Unfreeze Account**: `123unfreeze` - -## Block Schema -### 123freeze Block -Each `123freeze` block MUST include the following fields: - -| Field | Type (ICRC-3 `Value`) | Description | -|----------------|----------------------|-------------| -| `btype` | `Text` | MUST be `123freeze` | -| `account` | `Array(vec { Blob, Blob })` | The account being frozen. The first `Blob` is the owner principal, and the second `Blob` is the subaccount. | -| `authorizer` | `Blob` | The principal who authorized the freeze. | -| `metadata` | `Map(Text, Blob)` | Optional metadata for additional details. | - -### 123unfreeze Block -Each `123unfreeze` block MUST include the following fields: - -| Field | Type (ICRC-3 `Value`) | Description | -|----------------|----------------------|-------------| -| `btype` | `Text` | MUST be `123unfreeze` | -| `account` | `Array(vec { Blob, Blob })` | The account being unfrozen. | -| `authorizer` | `Blob` | The principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Blob)` | Optional metadata for additional details. | - -### Interesting Aspects -- Accounts are represented using an **array of two blobs**: the first blob is the owner principal, and the second blob is the subaccount. -- The `authorizer` field records the principal who authorized the operation. - -## Expected Semantics -### Freeze -- When `123freeze` is recorded, the specified `account` MUST be prevented from initiating or receiving transfers. -- Transactions involving a frozen account MUST return an error. -- The `123freeze` block MUST be permanently recorded in the ledger. - -### Unfreeze -- When `123unfreeze` is recorded, the specified `account` MUST regain full transfer functionality. -- The `123unfreeze` block MUST be permanently recorded in the ledger. +# ICRC-123: Account and Principal Freezing & Unfreezing + +ICRC-123 introduces new block types for recording account and unfreezing events in ICRC-compliant ledgers. These blocks provide a standardized way to document administrative actions restricting or restoring account activity. The `123freezeaccount` block records an account freeze event, while the `123unfreezeaccount` block documents the removal of such restrictions. Additionally, the `123freezeprincipal` block records the freezing of all accounts belonging to a principal, and the `123unfreezeprincipal` block records the unfreezing of all accounts belonging to a principal. + +## Common Elements + +This standard follows the conventions set by ICRC-3, inheriting key structural components. Accounts are recorded as an `Array` of two `Value` variants, where: + +- The first element is a `variant { Blob = }`, representing the account owner. +- The second element is a `variant { Blob = }`, representing the subaccount. If no subaccount is specified, this field MUST be an empty `Blob`. + +Additionally, each block includes: + +- `phash`: a `Blob` representing the hash of the parent block. +- `ts`: a `Nat` representing the timestamp of the block. + +## Block Types & Schema + +This standard introduces four new block types: + +- **Freeze Account**: `123freezeaccount` +- **Unfreeze Account**: `123unfreezeaccount` +- **Freeze Principal**: `123freezeprincipal` +- **Unfreeze Principal**: `123unfreezeprincipal` + +## Block Types & Schema + +This standard introduces four new block types: + +- **Freeze Account**: `123freezeaccount` +- **Unfreeze Account**: `123unfreezeaccount` +- **Freeze Principal**: `123freezeprincipal` +- **Unfreeze Principal**: `123unfreezeprincipal` + +### 123freezeaccount Block +Each `123freezeaccount` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +| ------------ | --------------------- | ------------------------------------------------ | +| `btype` | `Text` | MUST be `123freezeaccount` | +| `account` | `Array(vec { Blob, Blob })` | The account being frozen. | +| `authorizer` | `Blob` | The principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | + +### 123unfreezeaccount Block +Each `123unfreezeaccount` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +| ------------ | --------------------- | ------------------------------------------------ | +| `btype` | `Text` | MUST be `123unfreezeaccount` | +| `account` | `Array(vec { Blob, Blob })` | The account being unfrozen. | +| `authorizer` | `Blob` | The principal who authorized the unfreeze. | +| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | + +### 123freezeprincipal Block +Each `123freezeprincipal` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +| ------------ | --------------------- | ------------------------------------------------ | +| `btype` | `Text` | MUST be `123freezeprincipal` | +| `principal` | `Blob` | The principal whose accounts are being frozen. | +| `authorizer` | `Blob` | The principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | + +### 123unfreezeprincipal Block +Each `123unfreezeprincipal` block MUST include the following fields: + +| Field | Type (ICRC-3 `Value`) | Description | +| ------------ | --------------------- | ------------------------------------------------ | +| `btype` | `Text` | MUST be `123unfreezeprincipal` | +| `principal` | `Blob` | The principal whose accounts are being unfrozen. | +| `authorizer` | `Blob` | The principal who authorized the unfreeze. | +| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | + + + +## Semantics + +- When a `123freezeaccount` block is recorded, the ledger MUST ensure that the specified `account` is prevented from initiating or receiving transfers. +- When a `123freezeprincipal` block is recorded, **all accounts (including subaccounts) belonging to the specified principal MUST be frozen**. +- Any attempt to transact with a frozen account MUST return an error. +- When a `123unfreezeaccount` block is recorded, the ledger MUST restore the ability of the specified `account` to initiate and receive transactions. +- When a `123unfreezeprincipal` block is recorded, **all accounts (including subaccounts) belonging to the specified principal MUST be unfrozen**. +- All freeze and unfreeze blocks MUST be permanently recorded in the ledger. +- Querying an account's status SHOULD indicate whether it is currently frozen. ## Example Blocks -### 123freeze Example + +### 123freezeaccount Example ``` variant { Map = vec { - record { "btype"; variant { Text = "123freeze" }}; + record { "btype"; variant { Text = "123freezeaccount" }}; record { "account"; variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; }} }; - record { "authorizer"; variant { Blob = blob "\b1\a2\c3\d4\e5\f6" }}; + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; }}; ``` -### 123unfreeze Example +### 123unfreezeaccount Example ``` variant { Map = vec { - record { "btype"; variant { Text = "123unfreeze" }}; + record { "btype"; variant { Text = "123unfreezeaccount" }}; record { "account"; variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; }} }; - record { "authorizer"; variant { Blob = blob "\b1\a2\c3\d4\e5\f6" }}; + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; +}}; +``` + +### 123freezeprincipal Example +``` +variant { Map = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; +}}; +``` + +### 123unfreezeprincipal Example +``` +variant { Map = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; }}; ``` ## Compliance Reporting + Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: ``` vec { variant { Record = vec { - record { "btype"; variant { Text = "123freeze" }}; - record { "url"; variant { Text = "https://example.com/icrc-123" }}; + record { "btype"; variant { Text = "123freezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; }}; variant { Record = vec { - record { "btype"; variant { Text = "123unfreeze" }}; - record { "url"; variant { Text = "https://example.com/icrc-123" }}; + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; }}; } ``` From 4b827cf809feafce09e7b13f674217ded0fbe06d Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 1 Apr 2025 11:23:22 +0200 Subject: [PATCH 04/19] semantics section --- ICRCs/ICRC-123/ICRC-123.md | 153 ++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 3b204aa1..56989227 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -14,14 +14,6 @@ Additionally, each block includes: - `phash`: a `Blob` representing the hash of the parent block. - `ts`: a `Nat` representing the timestamp of the block. -## Block Types & Schema - -This standard introduces four new block types: - -- **Freeze Account**: `123freezeaccount` -- **Unfreeze Account**: `123unfreezeaccount` -- **Freeze Principal**: `123freezeprincipal` -- **Unfreeze Principal**: `123unfreezeprincipal` ## Block Types & Schema @@ -76,37 +68,119 @@ Each `123unfreezeprincipal` block MUST include the following fields: ## Semantics -- When a `123freezeaccount` block is recorded, the ledger MUST ensure that the specified `account` is prevented from initiating or receiving transfers. -- When a `123freezeprincipal` block is recorded, **all accounts (including subaccounts) belonging to the specified principal MUST be frozen**. -- Any attempt to transact with a frozen account MUST return an error. -- When a `123unfreezeaccount` block is recorded, the ledger MUST restore the ability of the specified `account` to initiate and receive transactions. -- When a `123unfreezeprincipal` block is recorded, **all accounts (including subaccounts) belonging to the specified principal MUST be unfrozen**. -- All freeze and unfreeze blocks MUST be permanently recorded in the ledger. -- Querying an account's status SHOULD indicate whether it is currently frozen. +This section defines the semantics of the freeze and unfreeze block types introduced by this standard. + +### Account Status + +Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: Blob)` is considered **RESTRICTED** if and only if the most recent freeze or unfreeze block at or before height `h` that affects `acc` is a freeze. + +A block is considered to affect an account if it satisfies one of the following: + +- It is a `123freezeaccount` or `123unfreezeaccount` block whose `account` field matches `acc`. +- It is a `123freezeprincipal` or `123unfreezeprincipal` block whose `principal` field matches the `owner` of `acc`. + +To determine whether an account is RESTRICTED, ledgers MUST identify the most recent block at or before height `h` that affects the account, and check whether it is of type `123freezeaccount` or `123freezeprincipal`. + +This means: + +- A freeze of an account (`123freezeaccount`) can be lifted by a later unfreeze of the same account (`123unfreezeaccount`) **or** by a later unfreeze of the owning principal (`123unfreezeprincipal`). +- A freeze of a principal (`123freezeprincipal`) can be lifted by a later unfreeze of that principal (`123unfreezeprincipal`). +- An unfreeze block always overrides any earlier freeze affecting the same account or principal, regardless of whether the freeze was explicit (account-level) or implicit (principal-level). + +Otherwise, the account is considered **NON-RESTRICTED**. + + +### Ledger Enforcement Rules + +- A ledger **MUST reject** any transfer transaction (`icrc1_transfer` or `icrc2_trasfer_from`) where the **sender or recipient account is currently RESTRICTED**. +- Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions **at or after** the block height at which the freeze/unfreeze block is recorded. +- Freeze and unfreeze blocks MUST be **permanently recorded** and included in the block hash chain. + +### Authorization + +- Each freeze and unfreeze block includes an `authorizer` field, which records the principal who authorized the action. +- This standard does **not prescribe** how the ledger verifies that the `authorizer` is permitted to freeze or unfreeze. Ledger implementations MAY use governance mechanisms, access control lists, or DAO-based authorization. + +### Idempotency and Redundancy + +- A ledger MAY reject freeze or unfreeze blocks that would have **no effect** (e.g., freezing an already frozen account), or MAY choose to **record them anyway** for auditability. +- Clients interpreting freeze status MUST follow a **"latest-action-wins" rule**: the most recent freeze or unfreeze block affecting an account or principal determines its effective status. + +### Querying Freeze Status + +Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_account_frozen(account)`) that returns whether an account is currently restricted. This serves as a convenience layer and does not replace auditing based on block history. + + + + +## Compliance Reporting + +Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: + +``` +vec { + variant { Record = vec { + record { "btype"; variant { Text = "123freezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; +} +``` ## Example Blocks ### 123freezeaccount Example ``` variant { Map = vec { + // The block type record { "btype"; variant { Text = "123freezeaccount" }}; - record { "account"; variant { Array = vec { + // The account that is frozen + record { "account"; + variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; }} }; - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // The principal that has authorized freezing the account + record { "authorizer"; + variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + // The time (in nanoseconds) when the block was appended to the ledger + record { "ts"; + variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // Parent hash: hash of the previous block; links this block to the previous block in the chain + record { "phash"; + variant { + Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; }}; ``` ### 123unfreezeaccount Example ``` variant { Map = vec { + // The block type record { "btype"; variant { Text = "123unfreezeaccount" }}; + // The account that is unfrozen record { "account"; variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; }} }; + // The principal that has authorized unfreezing the account record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // Parent hash: links this block to the previous block in the chain + record { "phash"; + variant { + Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; }}; ``` @@ -114,9 +188,17 @@ variant { Map = vec { ``` variant { Map = vec { record { "btype"; variant { Text = "123freezeprincipal" }}; + // The principal whose accounts are all frozen record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + // The principal that has authorized freezing the principal record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + // The time (in nanoseconds) when the block was appended to the ledger record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // Parent hash: links this block to the previous block in the chain + record { "phash"; + variant { + Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; }}; ``` @@ -124,33 +206,18 @@ variant { Map = vec { ``` variant { Map = vec { record { "btype"; variant { Text = "123unfreezeprincipal" }}; + // The principal whose accounts are all unfrozen record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + // The principal that has authorized unfreezing the principal record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + // The time (in nanoseconds) when the block was appended to the ledger + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // The time (in nanoseconds) when the block was appended to the ledger record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + // Parent hash: links this block to the previous block in the chain + record { "phash"; + variant { + Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; }}; ``` - -## Compliance Reporting - -Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: - -``` -vec { - variant { Record = vec { - record { "btype"; variant { Text = "123freezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; -} -``` From 8c18497bb68ced29b198b236854a85ffe1683820 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 1 Apr 2025 15:36:17 +0200 Subject: [PATCH 05/19] added a tx record to the blocks --- ICRCs/ICRC-123/ICRC-123.md | 204 +++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 86 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 56989227..2bcca23b 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -24,45 +24,55 @@ This standard introduces four new block types: - **Freeze Principal**: `123freezeprincipal` - **Unfreeze Principal**: `123unfreezeprincipal` -### 123freezeaccount Block -Each `123freezeaccount` block MUST include the following fields: +## Block Types & Schema + +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze instruction, similarly to how transaction blocks defined in ICRC-1 and ICRC-2 include the submitted transaction. + +This enables canister clients, indexers, and auditors to reconstruct the exact instruction that led to the block being appended to the ledger. + +Each block consists of the following top-level fields: + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|----------|------------------------|----------|-------------| +| `btype` | `Text` | Yes | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, or `"123unfreezeprincipal"`. | +| `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 | Contains the freeze or unfreeze instruction payload. | + +### `tx` Field Schemas + +#### For `123freezeaccount` + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|-------------------------------|----------|-------------| +| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to freeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | -| Field | Type (ICRC-3 `Value`) | Description | -| ------------ | --------------------- | ------------------------------------------------ | -| `btype` | `Text` | MUST be `123freezeaccount` | -| `account` | `Array(vec { Blob, Blob })` | The account being frozen. | -| `authorizer` | `Blob` | The principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | +#### For `123unfreezeaccount` -### 123unfreezeaccount Block -Each `123unfreezeaccount` block MUST include the following fields: -| Field | Type (ICRC-3 `Value`) | Description | -| ------------ | --------------------- | ------------------------------------------------ | -| `btype` | `Text` | MUST be `123unfreezeaccount` | -| `account` | `Array(vec { Blob, Blob })` | The account being unfrozen. | -| `authorizer` | `Blob` | The principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | +|Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|-------------------------------|----------|-------------| +| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to unfreeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | -### 123freezeprincipal Block -Each `123freezeprincipal` block MUST include the following fields: +#### For `123freezeprincipal` -| Field | Type (ICRC-3 `Value`) | Description | -| ------------ | --------------------- | ------------------------------------------------ | -| `btype` | `Text` | MUST be `123freezeprincipal` | -| `principal` | `Blob` | The principal whose accounts are being frozen. | -| `authorizer` | `Blob` | The principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|------------------------|----------|-------------| +| `principal` | `Blob` | Yes | The principal to freeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | -### 123unfreezeprincipal Block -Each `123unfreezeprincipal` block MUST include the following fields: +#### For `123unfreezeprincipal` -| Field | Type (ICRC-3 `Value`) | Description | -| ------------ | --------------------- | ------------------------------------------------ | -| `btype` | `Text` | MUST be `123unfreezeprincipal` | -| `principal` | `Blob` | The principal whose accounts are being unfrozen. | -| `authorizer` | `Blob` | The principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional metadata for additional details. | +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|------------------------|----------|-------------| +| `principal` | `Blob` | Yes | The principal to unfreeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | @@ -143,81 +153,103 @@ vec { ### 123freezeaccount Example ``` variant { Map = vec { - // The block type + // Block type identifier record { "btype"; variant { Text = "123freezeaccount" }}; - // The account that is frozen - record { "account"; - variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; - }} }; - // The principal that has authorized freezing the account - record { "authorizer"; - variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - // The time (in nanoseconds) when the block was appended to the ledger - record { "ts"; - variant { Nat = 1_741_312_737_184_874_392 : nat } }; - // Parent hash: hash of the previous block; links this block to the previous block in the chain - record { "phash"; - variant { - Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; + + // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; + + // The freeze instruction payload + record { "tx"; variant { Map = vec { + // The account to be frozen (only owner principal specified here) + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } + }}}; + + // The principal that authorized the freeze operation + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + }}}; }}; ``` + ### 123unfreezeaccount Example + ``` variant { Map = vec { - // The block type + // Block type identifier record { "btype"; variant { Text = "123unfreezeaccount" }}; - // The account that is unfrozen - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; - }} }; - // The principal that has authorized unfreezing the account - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; - // Parent hash: links this block to the previous block in the chain - record { "phash"; - variant { - Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; + + // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; + + // The unfreeze instruction payload + record { "tx"; variant { Map = vec { + // The account to be unfrozen (only owner principal specified here) + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } + }}}; + + // The principal that authorized the unfreeze operation + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + }}}; }}; ``` ### 123freezeprincipal Example ``` variant { Map = vec { + // Block type identifier record { "btype"; variant { Text = "123freezeprincipal" }}; - // The principal whose accounts are all frozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; - // The principal that has authorized freezing the principal - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - // The time (in nanoseconds) when the block was appended to the ledger - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; - // Parent hash: links this block to the previous block in the chain - record { "phash"; - variant { - Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; + + // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; + + // The freeze instruction payload + record { "tx"; variant { Map = vec { + // The principal whose accounts are to be frozen + record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + + // The principal who authorized the freeze operation + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + }}}; }}; + ``` ### 123unfreezeprincipal Example ``` variant { Map = vec { + // Block type identifier record { "btype"; variant { Text = "123unfreezeprincipal" }}; - // The principal whose accounts are all unfrozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; - // The principal that has authorized unfreezing the principal - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - // The time (in nanoseconds) when the block was appended to the ledger - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; - // The time (in nanoseconds) when the block was appended to the ledger - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; - // Parent hash: links this block to the previous block in the chain - record { "phash"; - variant { - Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; + + // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" + }}; + + // The unfreeze instruction payload + record { "tx"; variant { Map = vec { + // The principal whose accounts are to be unfrozen + record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + + // The principal who authorized the unfreeze operation + record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; + }}}; }}; + ``` From 49fec16ec1afda8bd7bf29b7d5549fcb5a41d000 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 1 Apr 2025 15:41:33 +0200 Subject: [PATCH 06/19] wording --- ICRCs/ICRC-123/ICRC-123.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 2bcca23b..a77eef92 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -26,7 +26,7 @@ This standard introduces four new block types: ## Block Types & Schema -Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze instruction, similarly to how transaction blocks defined in ICRC-1 and ICRC-2 include the submitted transaction. +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze transaction submitted to the ledger that caused this block to be created, similarly to how transaction blocks defined in ICRC-1 and ICRC-2 include the submitted transaction. This enables canister clients, indexers, and auditors to reconstruct the exact instruction that led to the block being appended to the ledger. @@ -37,7 +37,7 @@ Each block consists of the following top-level fields: | `btype` | `Text` | Yes | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, or `"123unfreezeprincipal"`. | | `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 | Contains the freeze or unfreeze instruction payload. | +| `tx` | `Map(Text, Value)` | Yes | Encodes the specific transaction that was submitted to the ledger and which resulted in the block being created. | ### `tx` Field Schemas From c706c1a297d571cf4e9d50e09908c003a3f339f9 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:14:25 +0200 Subject: [PATCH 07/19] consistency --- ICRCs/ICRC-123/ICRC-123.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index a77eef92..f316cc3e 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -4,10 +4,10 @@ ICRC-123 introduces new block types for recording account and unfreezing events ## Common Elements -This standard follows the conventions set by ICRC-3, inheriting key structural components. Accounts are recorded as an `Array` of two `Value` variants, where: +This standard follows the conventions set by ICRC-3, inheriting key structural components. Accounts are recorded as an `Array` of one or two `Value` variants, where: - The first element is a `variant { Blob = }`, representing the account owner. -- The second element is a `variant { Blob = }`, representing the subaccount. If no subaccount is specified, this field MUST be an empty `Blob`. +- The second element is a `variant { Blob = }`, representing the subaccount. If no subaccount is specified, only the owner is included. Additionally, each block includes: From 44811ff5b7f209c4d2e3b155427ca45bd391e4bb Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:23:57 +0200 Subject: [PATCH 08/19] consistency --- ICRCs/ICRC-123/ICRC-123.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index f316cc3e..bbdd636d 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -55,7 +55,7 @@ Each block consists of the following top-level fields: |Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|-------------------------------|----------|-------------| | `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | | `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | #### For `123freezeprincipal` @@ -71,7 +71,7 @@ Each block consists of the following top-level fields: | Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|------------------------|----------|-------------| | `principal` | `Blob` | Yes | The principal to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | +| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | | `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | @@ -102,7 +102,7 @@ Otherwise, the account is considered **NON-RESTRICTED**. ### Ledger Enforcement Rules -- A ledger **MUST reject** any transfer transaction (`icrc1_transfer` or `icrc2_trasfer_from`) where the **sender or recipient account is currently RESTRICTED**. +- A ledger **MUST reject** any transfer transaction (`icrc1_transfer` or `icrc2_transfer_from`) where the **sender or recipient account is currently RESTRICTED**. - Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions **at or after** the block height at which the freeze/unfreeze block is recorded. - Freeze and unfreeze blocks MUST be **permanently recorded** and included in the block hash chain. From 0351ee9f3f32175d0f83ec94ec699669fbce517c Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:24:38 +0200 Subject: [PATCH 09/19] removed a duplicate --- ICRCs/ICRC-123/ICRC-123.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index bbdd636d..397ef3f6 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -24,9 +24,8 @@ This standard introduces four new block types: - **Freeze Principal**: `123freezeprincipal` - **Unfreeze Principal**: `123unfreezeprincipal` -## Block Types & Schema -Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze transaction submitted to the ledger that caused this block to be created, similarly to how transaction blocks defined in ICRC-1 and ICRC-2 include the submitted transaction. +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze transaction submitted to the ledger that caused this block to be created, similarly to how blocks that hold ICRC-1 and ICRC-2 transactions include, explicitly, the transaction that was submitted. This enables canister clients, indexers, and auditors to reconstruct the exact instruction that led to the block being appended to the ledger. From ea923738542be6a7372c7822ee72552cbed0801d Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 11 Apr 2025 13:33:19 +0200 Subject: [PATCH 10/19] examples wip --- ICRCs/ICRC-123/ICRC-123.md | 72 ++++++++++---------------------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 397ef3f6..ee3c9d22 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -45,36 +45,28 @@ Each block consists of the following top-level fields: | Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|-------------------------------|----------|-------------| | `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to freeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | +| `reason` | `Text` | Yes | Reason for freezing the account. | #### For `123unfreezeaccount` - |Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|-------------------------------|----------|-------------| | `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | +| `reason` | `Text` | Yes | Reason for unfreezing the account. | #### For `123freezeprincipal` | Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|------------------------|----------|-------------| | `principal` | `Blob` | Yes | The principal to freeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | +| `reason` | `Text` | Yes | Reason for freezing the principal. | #### For `123unfreezeprincipal` | Field | Type (ICRC-3 `Value`) | Required | Description | |--------------|------------------------|----------|-------------| | `principal` | `Blob` | Yes | The principal to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | - - - +| `reason` | `Text` | Yes | Reason for unfreezing the principal. | ## Semantics This section defines the semantics of the freeze and unfreeze block types introduced by this standard. @@ -126,6 +118,8 @@ Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_ac Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: + + ``` vec { variant { Record = vec { @@ -149,56 +143,26 @@ vec { ## Example Blocks -### 123freezeaccount Example -``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123freezeaccount" }}; - - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; - - // The freeze instruction payload - record { "tx"; variant { Map = vec { - // The account to be frozen (only owner principal specified here) - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } - }}}; - // The principal that authorized the freeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; -}}; -``` ### 123unfreezeaccount Example ``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123unfreezeaccount" }}; - - // Timestamp when the block was appended (nanoseconds since epoch) - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; +variant { + Map = vec { + record { "btype"; variant { Text = "123unfreezeaccount" }}; // Block type identifier + record { "ts"; variant { Nat = 1741312737184874392 }}; // Timestamp when the block was appended (nanoseconds since epoch) + record { "phash"; variant { Blob = blob "d5c7eb57a24efa8bd1cc549e49c69fd1938de802d46065e2f23c00043b2e51" }}; // Hash of the previous block in the ledger chain + record { "tx"; variant { Map = vec { + record { "account"; variant { Array = vec { + record { ""account"; variant { Blob = blob "000000000200010d0101" }}; // The account to be unfrozen (only owner principal specified here) + }}}; + record { "reason"; variant { Text = "Legal case resolved" }}; // The reason for the unfreeze operation + }}}; - // Hash of the previous block in the ledger chain - record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; - - // The unfreeze instruction payload - record { "tx"; variant { Map = vec { - // The account to be unfrozen (only owner principal specified here) - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } }}}; - - // The principal that authorized the unfreeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; -}}; + }}; ``` ### 123freezeprincipal Example From 891e5e837cb91cf210cbd813428514a2c8f8df85 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 11 Apr 2025 13:47:39 +0200 Subject: [PATCH 11/19] cleaned up examples --- ICRCs/ICRC-123/ICRC-123.md | 82 ++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index ee3c9d22..35fac2a4 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -146,6 +146,25 @@ vec { +### 123freezeaccount Example + +``` +variant { + Map = vec { + record { "btype"; variant { Text = "123freezeaccount" }}; // Block type identifier + record { "ts"; variant { Nat = 1741312737184874393 }}; // Timestamp when the block was appended (nanoseconds since epoch) + record { "phash"; variant { Blob = blob "\9a\6f\bd\5b\18\65\2c\fa\6d\20\de\4d\fa\43\fc\96\33\e5\6a\1b" }}; // Hash of the previous block in the ledger chain + record { "tx"; variant { Map = vec { + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Account to be frozen (owner principal) + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; // Another account to be frozen + }}}; + record { "reason"; variant { Text = "Security breach" }}; // The reason for the freeze operation + }}}; + }}; +``` + + ### 123unfreezeaccount Example ``` @@ -153,66 +172,51 @@ variant { Map = vec { record { "btype"; variant { Text = "123unfreezeaccount" }}; // Block type identifier record { "ts"; variant { Nat = 1741312737184874392 }}; // Timestamp when the block was appended (nanoseconds since epoch) - record { "phash"; variant { Blob = blob "d5c7eb57a24efa8bd1cc549e49c69fd1938de802d46065e2f23c00043b2e51" }}; // Hash of the previous block in the ledger chain + record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; // Hash of the previous block in the ledger chain record { "tx"; variant { Map = vec { record { "account"; variant { Array = vec { - record { ""account"; variant { Blob = blob "000000000200010d0101" }}; // The account to be unfrozen (only owner principal specified here) + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Account to be unfrozen (owner principal) + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; // Another account to be unfrozen }}}; record { "reason"; variant { Text = "Legal case resolved" }}; // The reason for the unfreeze operation }}}; - }}}; }}; ``` + ### 123freezeprincipal Example ``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123freezeprincipal" }}; - - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; - - // The freeze instruction payload +variant { + Map = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; // Block type identifier + record { "ts"; variant { Nat = 1741312737184874393 }}; // Timestamp when the block was appended (nanoseconds since epoch) + record { "phash"; variant { Blob = blob "\9a\6f\bd\5b\18\65\2c\fa\6d\20\de\4d\fa\43\fc\96\33\e5\6a\1b" }}; // Hash of the previous block in the ledger chain record { "tx"; variant { Map = vec { - // The principal whose accounts are to be frozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + record { "principal"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Principal to be frozen + }}}; + record { "reason"; variant { Text = "Suspicion of illicit activity" }}; // Reason for freezing the principal + }}}; - // The principal who authorized the freeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; }}; ``` ### 123unfreezeprincipal Example ``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123unfreezeprincipal" }}; - - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; - - // The unfreeze instruction payload +variant { + Map = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}; // Block type identifier + record { "ts"; variant { Nat = 1741312737184874392 }}; // Timestamp when the block was appended (nanoseconds since epoch) + record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; // Hash of the previous block in the ledger chain record { "tx"; variant { Map = vec { - // The principal whose accounts are to be unfrozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; + record { "principal"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Principal to be unfrozen + }}}; + record { "reason"; variant { Text = "Court order" }}; // Reason for unfreezing the principal + }}}; - // The principal who authorized the unfreeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; }}; ``` From 6c5cfb355cb1dbee8ac20ee5dd806ee851e866a8 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:11:45 +0200 Subject: [PATCH 12/19] version consistent in terminology and structure with icrc-122 --- ICRCs/ICRC-123/ICRC-123.md | 301 +++++++++++-------------------------- 1 file changed, 85 insertions(+), 216 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 397ef3f6..6acd2172 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -1,254 +1,123 @@ -# ICRC-123: Account and Principal Freezing & Unfreezing +# ICRC-123: Freezing and Unfreezing Accounts and Principals -ICRC-123 introduces new block types for recording account and unfreezing events in ICRC-compliant ledgers. These blocks provide a standardized way to document administrative actions restricting or restoring account activity. The `123freezeaccount` block records an account freeze event, while the `123unfreezeaccount` block documents the removal of such restrictions. Additionally, the `123freezeprincipal` block records the freezing of all accounts belonging to a principal, and the `123unfreezeprincipal` block records the unfreezing of all accounts belonging to a principal. +## Status -## Common Elements +Draft -This standard follows the conventions set by ICRC-3, inheriting key structural components. Accounts are recorded as an `Array` of one or two `Value` variants, where: +## Introduction -- The first element is a `variant { Blob = }`, representing the account owner. -- The second element is a `variant { Blob = }`, representing the subaccount. If no subaccount is specified, only the owner is included. +This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are relevant in regulatory contexts or under legal obligations where temporarily or permanently disabling interactions with certain accounts or identities is necessary. -Additionally, each block includes: +## Motivation -- `phash`: a `Blob` representing the hash of the parent block. -- `ts`: a `Nat` representing the timestamp of the block. +Freezing an account or principal can be a regulatory requirement in certain jurisdictions, or be required by platform policy. These operations must be reflected transparently on-chain, and must be designed to minimize ambiguity and maximize auditability. This standard provides a minimal yet extensible structure for such blocks. +## Block Types -## Block Types & Schema +This standard introduces the following block types: -This standard introduces four new block types: +- `123freezeaccount`: Freezes an account, preventing it from initiating transfers. +- `123unfreezeaccount`: Unfreezes a previously frozen account. +- `123freezeprincipal`: Freezes a principal, disabling interactions from any account controlled by this principal. +- `123unfreezeprincipal`: Unfreezes a previously frozen principal. -- **Freeze Account**: `123freezeaccount` -- **Unfreeze Account**: `123unfreezeaccount` -- **Freeze Principal**: `123freezeprincipal` -- **Unfreeze Principal**: `123unfreezeprincipal` +Each block contains a `tx` field with minimal information about the entity affected, but can be extended with additional fields to include more context. +## Role of `tx` -Each block introduced by this standard MUST include a `tx` field containing a map that encodes the freeze or unfreeze transaction submitted to the ledger that caused this block to be created, similarly to how blocks that hold ICRC-1 and ICRC-2 transactions include, explicitly, the transaction that was submitted. +The `tx` field in each of the above block types captures the payload of the operation that triggered the block. This field is structured as a map (a vector of key-value records) and is intentionally minimal. It typically contains only the identity of the account or principal that is being frozen or unfrozen. -This enables canister clients, indexers, and auditors to reconstruct the exact instruction that led to the block being appended to the ledger. +The field is designed to be extensible. Implementations are free to include additional keys in the `tx` map to provide extra context, such as: -Each block consists of the following top-level fields: +- The principal that initiated the operation (e.g., a governance canister or privileged controller). +- The method that was invoked to trigger the operation. +- The reason for freezing or unfreezing. -| Field | Type (ICRC-3 `Value`) | Required | Description | -|----------|------------------------|----------|-------------| -| `btype` | `Text` | Yes | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, or `"123unfreezeprincipal"`. | -| `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 the specific transaction that was submitted to the ledger and which resulted in the block being created. | +Including such fields improves auditability and can support more nuanced policies in the ledger's business logic. -### `tx` Field Schemas +### Example: Extending the `tx` field -#### For `123freezeaccount` - -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|-------------------------------|----------|-------------| -| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to freeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | - -#### For `123unfreezeaccount` - - -|Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|-------------------------------|----------|-------------| -| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | - -#### For `123freezeprincipal` - -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|------------------------|----------|-------------| -| `principal` | `Blob` | Yes | The principal to freeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the freeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | - -#### For `123unfreezeprincipal` - -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|------------------------|----------|-------------| -| `principal` | `Blob` | Yes | The principal to unfreeze. | -| `authorizer` | `Blob` | Yes | Principal who authorized the unfreeze. | -| `metadata` | `Map(Text, Value)` | Optional | Additional metadata. | - - - -## Semantics - -This section defines the semantics of the freeze and unfreeze block types introduced by this standard. - -### Account Status - -Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: Blob)` is considered **RESTRICTED** if and only if the most recent freeze or unfreeze block at or before height `h` that affects `acc` is a freeze. - -A block is considered to affect an account if it satisfies one of the following: - -- It is a `123freezeaccount` or `123unfreezeaccount` block whose `account` field matches `acc`. -- It is a `123freezeprincipal` or `123unfreezeprincipal` block whose `principal` field matches the `owner` of `acc`. - -To determine whether an account is RESTRICTED, ledgers MUST identify the most recent block at or before height `h` that affects the account, and check whether it is of type `123freezeaccount` or `123freezeprincipal`. - -This means: - -- A freeze of an account (`123freezeaccount`) can be lifted by a later unfreeze of the same account (`123unfreezeaccount`) **or** by a later unfreeze of the owning principal (`123unfreezeprincipal`). -- A freeze of a principal (`123freezeprincipal`) can be lifted by a later unfreeze of that principal (`123unfreezeprincipal`). -- An unfreeze block always overrides any earlier freeze affecting the same account or principal, regardless of whether the freeze was explicit (account-level) or implicit (principal-level). - -Otherwise, the account is considered **NON-RESTRICTED**. - - -### Ledger Enforcement Rules - -- A ledger **MUST reject** any transfer transaction (`icrc1_transfer` or `icrc2_transfer_from`) where the **sender or recipient account is currently RESTRICTED**. -- Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions **at or after** the block height at which the freeze/unfreeze block is recorded. -- Freeze and unfreeze blocks MUST be **permanently recorded** and included in the block hash chain. - -### Authorization - -- Each freeze and unfreeze block includes an `authorizer` field, which records the principal who authorized the action. -- This standard does **not prescribe** how the ledger verifies that the `authorizer` is permitted to freeze or unfreeze. Ledger implementations MAY use governance mechanisms, access control lists, or DAO-based authorization. - -### Idempotency and Redundancy - -- A ledger MAY reject freeze or unfreeze blocks that would have **no effect** (e.g., freezing an already frozen account), or MAY choose to **record them anyway** for auditability. -- Clients interpreting freeze status MUST follow a **"latest-action-wins" rule**: the most recent freeze or unfreeze block affecting an account or principal determines its effective status. - -### Querying Freeze Status - -Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_account_frozen(account)`) that returns whether an account is currently restricted. This serves as a convenience layer and does not replace auditing based on block history. - - - - -## Compliance Reporting - -Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: - -``` -vec { - variant { Record = vec { - record { "btype"; variant { Text = "123freezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; -} +```motoko +variant { Map = vec { + record { "btype"; variant { Text = "123freezeaccount" }}, + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "tx"; variant { Map = vec { + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } + }}}, + record { "caller"; variant { Blob = blob "\94\85\a4\06..." }}, + record { "method"; variant { Text = "freeze_account" }}, + record { "reason"; variant { Text = "Account involved in suspicious activity" }} + }}} +}}; ``` -## Example Blocks - -### 123freezeaccount Example -``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123freezeaccount" }}; +This example illustrates a `freezeaccount` block where the `tx` field includes more than just the `account`. By including fields like `caller`, `method`, and `reason`, the ledger provides greater transparency and traceability. - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; +## Block Examples - // The freeze instruction payload - record { "tx"; variant { Map = vec { - // The account to be frozen (only owner principal specified here) - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } - }}}; +### Freeze Account Block - // The principal that authorized the freeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; +```motoko +variant { Map = vec { + record { "btype"; variant { Text = "123freezeaccount" }}, + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "tx"; variant { Map = vec { + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } + }}}, + record { "reason"; variant { Text = "Violation of terms" }} + }}} }}; ``` +### Unfreeze Account Block -### 123unfreezeaccount Example - -``` +```motoko variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123unfreezeaccount" }}; - - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; - - // The unfreeze instruction payload - record { "tx"; variant { Map = vec { - // The account to be unfrozen (only owner principal specified here) - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" } - }}}; - - // The principal that authorized the unfreeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; + record { "btype"; variant { Text = "123unfreezeaccount" }}, + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "tx"; variant { Map = vec { + record { "account"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } + }}}, + record { "reason"; variant { Text = "Cleared by compliance team" }} + }}} }}; ``` -### 123freezeprincipal Example -``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123freezeprincipal" }}; - - // Timestamp when the block was appended (nanoseconds since epoch) - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; +### Freeze Principal Block - // Hash of the previous block in the ledger chain - record { "phash"; variant { - Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; - - // The freeze instruction payload - record { "tx"; variant { Map = vec { - // The principal whose accounts are to be frozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; - - // The principal who authorized the freeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; +```motoko +variant { Map = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}, + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "tx"; variant { Map = vec { + record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, + record { "reason"; variant { Text = "Violation of platform policy" }} + }}} }}; - ``` -### 123unfreezeprincipal Example -``` -variant { Map = vec { - // Block type identifier - record { "btype"; variant { Text = "123unfreezeprincipal" }}; - - // 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 "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" - }}; +### Unfreeze Principal Block - // The unfreeze instruction payload - record { "tx"; variant { Map = vec { - // The principal whose accounts are to be unfrozen - record { "principal"; variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }}; - - // The principal who authorized the unfreeze operation - record { "authorizer"; variant { Blob = blob "\94\85\a4\06\ba\33\de\19\f8\ad\b1\ee\3d\07\9e\63\1d\7f\59\43\57\bc\dd\98\56\63\83\96\02" }}; - }}}; +```motoko +variant { Map = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}, + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "tx"; variant { Map = vec { + record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, + record { "reason"; variant { Text = "Review complete, reinstated" }} + }}} }}; - ``` From 3d3bb35562243022e81072b4a06580255e7f8e55 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Tue, 15 Apr 2025 10:23:37 +0200 Subject: [PATCH 13/19] conflicts --- ICRCs/ICRC-123/ICRC-123.md | 82 -------------------------------------- 1 file changed, 82 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 88dfb47a..c3bcae41 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -37,7 +37,6 @@ Including such fields improves auditability and can support more nuanced policie ### Example: Extending the `tx` field -<<<<<<< HEAD #### For `123freezeaccount` | Field | Type (ICRC-3 `Value`) | Required | Description | @@ -160,30 +159,12 @@ variant { record { "reason"; variant { Text = "Security breach" }}; // The reason for the freeze operation }}}; }}; -======= -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123freezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "caller"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "method"; variant { Text = "freeze_account" }}, - record { "reason"; variant { Text = "Account involved in suspicious activity" }} - }}} -}}; ->>>>>>> 6c5cfb355cb1dbee8ac20ee5dd806ee851e866a8 ``` This example illustrates a `freezeaccount` block where the `tx` field includes more than just the `account`. By including fields like `caller`, `method`, and `reason`, the ledger provides greater transparency and traceability. --- -<<<<<<< HEAD ``` variant { Map = vec { @@ -216,45 +197,9 @@ variant { record { "reason"; variant { Text = "Suspicion of illicit activity" }}; // Reason for freezing the principal }}}; -======= -## Block Examples - -### Freeze Account Block - -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123freezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "reason"; variant { Text = "Violation of terms" }} - }}} -}}; -``` - -### Unfreeze Account Block - -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "reason"; variant { Text = "Cleared by compliance team" }} - }}} ->>>>>>> 6c5cfb355cb1dbee8ac20ee5dd806ee851e866a8 }}; ``` -<<<<<<< HEAD ### 123unfreezeprincipal Example ``` variant { @@ -269,32 +214,5 @@ variant { record { "reason"; variant { Text = "Court order" }}; // Reason for unfreezing the principal }}}; -======= -### Freeze Principal Block - -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "reason"; variant { Text = "Violation of platform policy" }} - }}} -}}; -``` - -### Unfreeze Principal Block - -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "reason"; variant { Text = "Review complete, reinstated" }} - }}} ->>>>>>> 6c5cfb355cb1dbee8ac20ee5dd806ee851e866a8 }}; ``` From e0fa18afd5297705b0e26b90896c414c64d627bd Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:37:10 +0200 Subject: [PATCH 14/19] cleanup --- ICRCs/ICRC-123/ICRC-123.md | 214 +++++++++++++++++++++++++------------ 1 file changed, 143 insertions(+), 71 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 6acd2172..924c7303 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -6,118 +6,190 @@ Draft ## Introduction -This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are relevant in regulatory contexts or under legal obligations where temporarily or permanently disabling interactions with certain accounts or identities is necessary. +This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. ## Motivation -Freezing an account or principal can be a regulatory requirement in certain jurisdictions, or be required by platform policy. These operations must be reflected transparently on-chain, and must be designed to minimize ambiguity and maximize auditability. This standard provides a minimal yet extensible structure for such blocks. +Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines a minimal block structure sufficient for recording the action while relying on the ledger implementation to provide access to the full invocation context for auditability. -## Block Types +## Common Elements +This standard follows the conventions set by ICRC-3, inheriting key structural components. +- **Accounts** are represented using the ICRC-3 `Value` type, specifically as a `variant { Array = vec { V1 [, V2] } }` where `V1` is `variant { Blob = }` representing the account owner, and `V2` is `variant { Blob = }` representing the subaccount. If no subaccount is specified, the `Array` MUST contain only one element (`V1`). +- **Principals** 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 introduces the following block types: +## Block Types & Schema -- `123freezeaccount`: Freezes an account, preventing it from initiating transfers. -- `123unfreezeaccount`: Unfreezes a previously frozen account. -- `123freezeprincipal`: Freezes a principal, disabling interactions from any account controlled by this principal. -- `123unfreezeprincipal`: Unfreezes a previously frozen principal. +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the minimal information about the freeze/unfreeze operation required for identifying the target and providing basic context. -Each block contains a `tx` field with minimal information about the entity affected, but can be extended with additional fields to include more context. +**Important Note on Transaction Recoverability:** The `tx` field defined below is intentionally minimal, containing only the data strictly necessary to identify the target entity (account or principal) and an optional reason. For full auditability and transparency, ledger implementations compliant with ICRC-123 **MUST** ensure that the complete details of the original transaction invocation that led to the freeze/unfreeze 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., `freeze_account`), 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. -## Role of `tx` +Each block defined by this standard consists of the following top-level fields: -The `tx` field in each of the above block types captures the payload of the operation that triggered the block. This field is structured as a map (a vector of key-value records) and is intentionally minimal. It typically contains only the identity of the account or principal that is being frozen or unfrozen. +| Field | Type (ICRC-3 `Value`) | Required | Description | +|----------|------------------------|----------|-------------| +| `btype` | `Text` | Yes | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | +| `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 freeze/unfreeze operation. See schemas below. | -The field is designed to be extensible. Implementations are free to include additional keys in the `tx` map to provide extra context, such as: +### `tx` Field Schemas -- The principal that initiated the operation (e.g., a governance canister or privileged controller). -- The method that was invoked to trigger the operation. -- The reason for freezing or unfreezing. +#### For `123freezeaccount` -Including such fields improves auditability and can support more nuanced policies in the ledger's business logic. +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|--------------------------------------------------------------|----------|-------------| +| `account` | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes | The account being frozen. | +| `reason` | `Text` | Optional | Human-readable reason for freezing the account. | -### Example: Extending the `tx` field +#### For `123unfreezeaccount` -```motoko -variant { Map = vec { - record { "btype"; variant { Text = "123freezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "caller"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "method"; variant { Text = "freeze_account" }}, - record { "reason"; variant { Text = "Account involved in suspicious activity" }} - }}} -}}; -``` +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|--------------------------------------------------------------|----------|-------------| +| `account` | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes | The account being unfrozen. | +| `reason` | `Text` | Optional | Human-readable reason for unfreezing the account. | + +#### For `123freezeprincipal` + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|----------------------------------------------------------|----------|-------------| +| `principal` | `Value` (Must be `variant { Blob = }`) | Yes | The principal being frozen. | +| `reason` | `Text` | Optional | Human-readable reason for freezing the principal. | + +#### For `123unfreezeprincipal` + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------------|----------------------------------------------------------|----------|-------------| +| `principal` | `Value` (Must be `variant { Blob = }`) | Yes | The principal being unfrozen. | +| `reason` | `Text` | Optional | Human-readable reason for unfreezing the principal. | + +¹ Where `V1` is `variant { Blob = }` and `V2` is `variant { Blob = }`. If no subaccount exists, the `Array` contains only `V1`. + +## Semantics -This example illustrates a `freezeaccount` block where the `tx` field includes more than just the `account`. By including fields like `caller`, `method`, and `reason`, the ledger provides greater transparency and traceability. +The recording of these blocks MUST influence the behavior of the ledger according to the following semantics. Implementations MUST clearly define how these states affect ledger operations (e.g., `icrc1_transfer`, `icrc2_approve`). ---- +### Freeze Account (`123freezeaccount`) + +- When a block of type `123freezeaccount` is recorded for a given `tx.account`, the ledger MUST enter a state where that specific account is considered "frozen". +- A frozen account MUST be prevented from being the initiator of operations that would typically require the account owner's control, such as: + - Being the `from` account in an `icrc1_transfer`. + - Being the `spender` account in an `icrc2_approve` (unless ledger policy allows approving while frozen). +- The ledger MAY allow incoming transfers (`icrc1_transfer` where the frozen account is the `to` account), depending on policy. +- The frozen status applies only to the specific account (owner + subaccount combination). + +### Unfreeze Account (`123unfreezeaccount`) + +- When a block of type `123unfreezeaccount` is recorded for a given `tx.account`, the ledger MUST reverse the "frozen" state for that specific account. +- The account MUST subsequently be allowed to initiate operations as normal (subject to other standard conditions like sufficient balance). + +### Freeze Principal (`123freezeprincipal`) + +- When a block of type `123freezeprincipal` is recorded for a given `tx.principal`, the ledger MUST enter a state where that principal is considered "frozen". +- A frozen principal MUST be prevented from initiating operations from *any* account where they are the owner (i.e., any account `{ owner = tx.principal; subaccount = * }`). This includes, but may not be limited to: + - Preventing `icrc1_transfer` where the `from.owner` is the frozen principal. + - Preventing `icrc2_approve` where the `spender.owner` is the frozen principal. +- The ledger MAY allow incoming transfers to accounts owned by the frozen principal, depending on policy. + +### Unfreeze Principal (`123unfreezeprincipal`) + +- When a block of type `123unfreezeprincipal` is recorded for a given `tx.principal`, the ledger MUST reverse the "frozen" state for that principal. +- Accounts owned by the principal MUST subsequently be allowed to initiate operations as normal (subject to other standard conditions). + +## Compliance Reporting + +Ledgers implementing this standard MUST return the following response (including entries for other supported types like ICRC-1) to `icrc3_supported_block_types`, with URLs pointing to the standards defining each block type: + +```candid +vec { + // ... other supported types like ICRC-1 ... + variant { Record = vec { + record { "btype"; variant { Text = "123freezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; // Placeholder URL + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; // Placeholder URL + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; // Placeholder URL + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; // Placeholder URL + }}; +} +``` ## Block Examples -### Freeze Account Block +### Freeze Account Block Example -```motoko +```candid variant { Map = vec { - record { "btype"; variant { Text = "123freezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "btype"; variant { Text = "123freezeaccount" }}; + record { "ts"; variant { Nat = 1_741_319_263_000_000_000 : nat }}; // Approx 2025-04-14T15:07:43Z + record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}; // Example parent hash record { "tx"; variant { Map = vec { + // The account being frozen (owner + subaccount) record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "reason"; variant { Text = "Violation of terms" }} - }}} + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Example owner principal + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." }; // Example subaccount + }}}; + // Optional reason + record { "reason"; variant { Text = "Violation of terms" }}; + }}}; }}; ``` -### Unfreeze Account Block +### Unfreeze Account Block Example -```motoko +```candid variant { Map = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "btype"; variant { Text = "123unfreezeaccount" }}; + record { "ts"; variant { Nat = 1_741_319_263_000_000_000 : nat }}; // Approx 2025-04-14T15:07:43Z + record { "phash"; variant { Blob = blob "\e8\a1\03\ff..." }}; // Example parent hash record { "tx"; variant { Map = vec { + // The account being unfrozen record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }, - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." } - }}}, - record { "reason"; variant { Text = "Cleared by compliance team" }} - }}} + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Example owner principal + variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f..." }; // Example subaccount + }}}; + // Optional reason + record { "reason"; variant { Text = "Cleared by compliance team" }}; + }}}; }}; ``` -### Freeze Principal Block +### Freeze Principal Block Example -```motoko +```candid variant { Map = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "ts"; variant { Nat = 1_741_319_263_000_000_000 : nat }}; // Approx 2025-04-14T15:07:43Z + record { "phash"; variant { Blob = blob "\f0\1d\9b\2a..." }}; // Example parent hash record { "tx"; variant { Map = vec { - record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "reason"; variant { Text = "Violation of platform policy" }} - }}} + // The principal being frozen + record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}; // Example principal + // Optional reason + record { "reason"; variant { Text = "Violation of platform policy" }}; + }}}; }}; ``` -### Unfreeze Principal Block +### Unfreeze Principal Block Example -```motoko +```candid variant { Map = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}, - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 }}, - record { "phash"; variant { Blob = blob "\d5\c7\eb\57..." }}, + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "ts"; variant { Nat = 1_741_319_263_000_000_000 : nat }}; // Approx 2025-04-14T15:07:43Z + record { "phash"; variant { Blob = blob "\c3\45\e6\b9..." }}; // Example parent hash record { "tx"; variant { Map = vec { - record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}, - record { "reason"; variant { Text = "Review complete, reinstated" }} - }}} + // The principal being unfrozen + record { "principal"; variant { Blob = blob "\94\85\a4\06..." }}; // Example principal + // Optional reason + record { "reason"; variant { Text = "Review complete, reinstated" }}; + }}}; }}; ``` From 8081f7af202d8b4bdd2abb5fd888a661ca3ab0e7 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 15 May 2025 12:04:47 +0200 Subject: [PATCH 15/19] fix conflict --- ICRCs/ICRC-123/ICRC-123.md | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index c3bcae41..769c43de 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -6,36 +6,35 @@ Draft ## Introduction -This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are relevant in regulatory contexts or under legal obligations where temporarily or permanently disabling interactions with certain accounts or identities is necessary. +This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. ## Motivation -Freezing an account or principal can be a regulatory requirement in certain jurisdictions, or be required by platform policy. These operations must be reflected transparently on-chain, and must be designed to minimize ambiguity and maximize auditability. This standard provides a minimal yet extensible structure for such blocks. +Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines a minimal block structure sufficient for recording the action while relying on the ledger implementation to provide access to the full invocation context for auditability. -## Block Types +## Common Elements +This standard follows the conventions set by ICRC-3, inheriting key structural components. +- **Accounts** are represented using the ICRC-3 `Value` type, specifically as a `variant { Array = vec { V1 [, V2] } }` where `V1` is `variant { Blob = }` representing the account owner, and `V2` is `variant { Blob = }` representing the subaccount. If no subaccount is specified, the `Array` MUST contain only one element (`V1`). +- **Principals** 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 introduces the following block types: +## Block Types & Schema -- `123freezeaccount`: Freezes an account, preventing it from initiating transfers. -- `123unfreezeaccount`: Unfreezes a previously frozen account. -- `123freezeprincipal`: Freezes a principal, disabling interactions from any account controlled by this principal. -- `123unfreezeprincipal`: Unfreezes a previously frozen principal. +Each block introduced by this standard MUST include a `tx` field containing a map that encodes the minimal information about the freeze/unfreeze operation required for identifying the target and providing basic context. -Each block contains a `tx` field with minimal information about the entity affected, but can be extended with additional fields to include more context. +**Important Note on Transaction Recoverability:** The `tx` field defined below is intentionally minimal, containing only the data strictly necessary to identify the target entity (account or principal) and an optional reason. For full auditability and transparency, ledger implementations compliant with ICRC-123 **MUST** ensure that the complete details of the original transaction invocation that led to the freeze/unfreeze 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., `freeze_account`), 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. -## Role of `tx` +Each block defined by this standard consists of the following top-level fields: -The `tx` field in each of the above block types captures the payload of the operation that triggered the block. This field is structured as a map (a vector of key-value records) and is intentionally minimal. It typically contains only the identity of the account or principal that is being frozen or unfrozen. +| Field | Type (ICRC-3 `Value`) | Required | Description | +|----------|------------------------|----------|-------------| +| `btype` | `Text` | Yes | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | +| `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 freeze/unfreeze operation. See schemas below. | -The field is designed to be extensible. Implementations are free to include additional keys in the `tx` map to provide extra context, such as: +### `tx` Field Schemas -- The principal that initiated the operation (e.g., a governance canister or privileged controller). -- The method that was invoked to trigger the operation. -- The reason for freezing or unfreezing. - -Including such fields improves auditability and can support more nuanced policies in the ledger's business logic. - -### Example: Extending the `tx` field #### For `123freezeaccount` From 13b64a9effbb4ff6ea7c835ef8dbe5b623f2ec40 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 19 May 2025 15:29:20 +0200 Subject: [PATCH 16/19] added caller to the tx, and clarified that a frozen principal cannot make approvals --- ICRCs/ICRC-123/ICRC-123.md | 206 +++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 101 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 769c43de..9f6f57a0 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -6,135 +6,133 @@ Draft ## Introduction -This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. +This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. The transaction details (`tx`) within each block explicitly include the `caller` principal that authorized the operation. ## Motivation -Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines a minimal block structure sufficient for recording the action while relying on the ledger implementation to provide access to the full invocation context for auditability. +Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines 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. - **Accounts** are represented using the ICRC-3 `Value` type, specifically as a `variant { Array = vec { V1 [, V2] } }` where `V1` is `variant { Blob = }` representing the account owner, and `V2` is `variant { Blob = }` representing the subaccount. If no subaccount is specified, the `Array` MUST contain only one element (`V1`). -- **Principals** 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 the minimal information about the freeze/unfreeze operation required for identifying the target and providing basic context. +Each block introduced by this standard MUST include a `tx` field containing a map. This map encodes information about the freeze/unfreeze operation, including the `caller` principal, the target entity, and basic context. -**Important Note on Transaction Recoverability:** The `tx` field defined below is intentionally minimal, containing only the data strictly necessary to identify the target entity (account or principal) and an optional reason. For full auditability and transparency, ledger implementations compliant with ICRC-123 **MUST** ensure that the complete details of the original transaction invocation that led to the freeze/unfreeze 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., `freeze_account`), 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-123 **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 freeze/unfreeze 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: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | -| `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 freeze/unfreeze operation. See schemas below. | +| `btype`  | `Text`                 | Yes      | MUST be one of: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | +| `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 freeze/unfreeze operation, including the caller. See schemas below. | ### `tx` Field Schemas - #### For `123freezeaccount` -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|-------------------------------|----------|-------------| -| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to freeze. | -| `reason` | `Text` | Yes | Reason for freezing the account. | +| Field        | Type (ICRC-3 `Value`)                                        | Required | Description | +|--------------|--------------------------------------------------------------|----------|-------------| +| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | +| `account`    | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes      | The account being frozen. | +| `reason`     | `Text`                                                       | Optional | Human-readable reason for freezing the account. | #### For `123unfreezeaccount` -|Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|-------------------------------|----------|-------------| -| `account` | `Array(vec { Blob [, Blob] })` | Yes | The account to unfreeze. | -| `reason` | `Text` | Yes | Reason for unfreezing the account. | +| Field        | Type (ICRC-3 `Value`)                                        | Required | Description | +|--------------|--------------------------------------------------------------|----------|-------------| +| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | +| `account`    | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes      | The account being unfrozen. | +| `reason`     | `Text`                                                       | Optional | Human-readable reason for unfreezing the account. | #### For `123freezeprincipal` -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|------------------------|----------|-------------| -| `principal` | `Blob` | Yes | The principal to freeze. | -| `reason` | `Text` | Yes | Reason for freezing the principal. | +| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | +|--------------|----------------------------------------------------------|----------|-------------| +| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | +| `principal`  | `Value` (Must be `variant { Blob = }`) | Yes      | The principal being frozen. | +| `reason`     | `Text`                                                   | Optional | Human-readable reason for freezing the principal. | #### For `123unfreezeprincipal` -| Field | Type (ICRC-3 `Value`) | Required | Description | -|--------------|------------------------|----------|-------------| -| `principal` | `Blob` | Yes | The principal to unfreeze. | -| `reason` | `Text` | Yes | Reason for unfreezing the principal. | +| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | +|--------------|----------------------------------------------------------|----------|-------------| +| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | +| `principal`  | `Value` (Must be `variant { Blob = }`) | Yes      | The principal being unfrozen. | +| `reason`     | `Text`                                                   | Optional | Human-readable reason for unfreezing the principal. | + +¹ Where `V1` is `variant { Blob = }` and `V2` is `variant { Blob = }`. If no subaccount exists, the `Array` contains only `V1`. + ## Semantics This section defines the semantics of the freeze and unfreeze block types introduced by this standard. ### Account Status -Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: Blob)` is considered **RESTRICTED** if and only if the most recent freeze or unfreeze block at or before height `h` that affects `acc` is a freeze. - -A block is considered to affect an account if it satisfies one of the following: - -- It is a `123freezeaccount` or `123unfreezeaccount` block whose `account` field matches `acc`. -- It is a `123freezeprincipal` or `123unfreezeprincipal` block whose `principal` field matches the `owner` of `acc`. - -To determine whether an account is RESTRICTED, ledgers MUST identify the most recent block at or before height `h` that affects the account, and check whether it is of type `123freezeaccount` or `123freezeprincipal`. +Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: opt Blob)` is considered **RESTRICTED** if and only if the most recent freeze or unfreeze block at or before height `h` that affects `acc` is a freeze block (either `123freezeaccount` or `123freezeprincipal`). -This means: +A block is considered to affect an account `acc` if it satisfies one of the following: +- It is a `123freezeaccount` or `123unfreezeaccount` block where the `tx.account` field matches `acc`. +- It is a `123freezeprincipal` or `123unfreezeprincipal` block where the `tx.principal` field matches the `owner` of `acc`. -- A freeze of an account (`123freezeaccount`) can be lifted by a later unfreeze of the same account (`123unfreezeaccount`) **or** by a later unfreeze of the owning principal (`123unfreezeprincipal`). -- A freeze of a principal (`123freezeprincipal`) can be lifted by a later unfreeze of that principal (`123unfreezeprincipal`). -- An unfreeze block always overrides any earlier freeze affecting the same account or principal, regardless of whether the freeze was explicit (account-level) or implicit (principal-level). - -Otherwise, the account is considered **NON-RESTRICTED**. +To determine whether an account is RESTRICTED, ledgers MUST identify the most recent block at or before height `h` that affects the account, and check whether its `btype` is `123freezeaccount` or `123freezeprincipal`. If the most recent affecting block is an unfreeze block (`123unfreezeaccount` or `123unfreezeprincipal`), or if no such affecting blocks exist, the account is **NON-RESTRICTED**. +This "latest-action-wins" rule implies: +- A freeze of an account (`123freezeaccount`) can be lifted by a later unfreeze of the same account (`123unfreezeaccount`) or by a later unfreeze of the owning principal (`123unfreezeprincipal`). +- A freeze of a principal (`123freezeprincipal`) can be lifted by a later unfreeze of that principal (`123unfreezeprincipal`). It also implicitly unfreezes all accounts owned by that principal unless a more recent, specific `123freezeaccount` block targets one of those accounts. ### Ledger Enforcement Rules -- A ledger **MUST reject** any transfer transaction (`icrc1_transfer` or `icrc2_transfer_from`) where the **sender or recipient account is currently RESTRICTED**. -- Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions **at or after** the block height at which the freeze/unfreeze block is recorded. +- **Transfers:** + - A ledger **MUST reject** any `icrc1_transfer` or `icrc2_transfer_from` transaction where the **sender** account (the `from` account in the operation) is currently RESTRICTED. + - The ledger **MAY**, according to its policy, also reject `icrc1_transfer` or `icrc2_transfer_from` transactions if the **recipient** account (the `to` account in the operation) is RESTRICTED, or it MAY allow incoming funds to a RESTRICTED recipient. +- **ICRC-2 Operations:** + - **`icrc2_approve` (Granting Approval):** If an account is RESTRICTED, its owner **MUST NOT** be able to authorize an `icrc2_approve` transaction where this restricted account is the one granting the approval (i.e., the `account` argument in `icrc2_approve` which specifies the owner of the funds being approved for spending). + - **`icrc2_approve` (Receiving Approval):** The ledger's policy SHOULD define whether an approval can be granted *to* a RESTRICTED account (i.e., a RESTRICTED account being the `spender` argument in an `icrc2_approve` call initiated by an unrestricted account owner). Even if an approval is granted to a RESTRICTED account, that account **MUST NOT** be able to use this approval (e.g., by calling `icrc2_transfer_from`) while it remains RESTRICTED. + - **`icrc2_transfer_from` (Acting as Spender):** An account that is RESTRICTED **MUST NOT** be able to initiate an `icrc2_transfer_from` call (i.e., act as an approved spender), even if it holds a valid approval for another account. +- Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions attempted **at or after** the block height at which the freeze/unfreeze block is recorded and its state change takes effect. - Freeze and unfreeze blocks MUST be **permanently recorded** and included in the block hash chain. -### Authorization - -- Each freeze and unfreeze block includes an `authorizer` field, which records the principal who authorized the action. -- This standard does **not prescribe** how the ledger verifies that the `authorizer` is permitted to freeze or unfreeze. Ledger implementations MAY use governance mechanisms, access control lists, or DAO-based authorization. - ### Idempotency and Redundancy -- A ledger MAY reject freeze or unfreeze blocks that would have **no effect** (e.g., freezing an already frozen account), or MAY choose to **record them anyway** for auditability. -- Clients interpreting freeze status MUST follow a **"latest-action-wins" rule**: the most recent freeze or unfreeze block affecting an account or principal determines its effective status. +- A ledger MAY reject freeze or unfreeze blocks that would have **no effect** on the current RESTRICTED status of the target account or principal (e.g., freezing an already frozen account via the same mechanism), or MAY choose to **record them anyway** for auditability. +- Clients interpreting freeze status MUST follow the **"latest-action-wins" rule** as defined in "Account Status": the most recent freeze or unfreeze block affecting an account or principal determines its effective status. ### Querying Freeze Status -Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_account_frozen(account)`) that returns whether an account is currently restricted. This serves as a convenience layer and does not replace auditing based on block history. - - - +Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_account_restricted(account): bool`) that returns whether an account is currently RESTRICTED according to the rules defined in "Account Status". This serves as a convenience layer and does not replace auditing based on block history. ## Compliance Reporting Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: - - -``` +```candid vec { - variant { Record = vec { - record { "btype"; variant { Text = "123freezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; - variant { Record = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}; - record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; - }}; +    // ... other supported types like ICRC-1 ... +    variant { Record = vec { +        record { "btype"; variant { Text = "123freezeaccount" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL +    }}; +    variant { Record = vec { +        record { "btype"; variant { Text = "123unfreezeaccount" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL +    }}; +    variant { Record = vec { +        record { "btype"; variant { Text = "123freezeprincipal" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL +    }}; +    variant { Record = vec { +        record { "btype"; variant { Text = "123unfreezeprincipal" }}; +        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL +    }}; } + ``` ## Example Blocks @@ -145,40 +143,46 @@ vec { ### 123freezeaccount Example ``` -variant { - Map = vec { - record { "btype"; variant { Text = "123freezeaccount" }}; // Block type identifier - record { "ts"; variant { Nat = 1741312737184874393 }}; // Timestamp when the block was appended (nanoseconds since epoch) - record { "phash"; variant { Blob = blob "\9a\6f\bd\5b\18\65\2c\fa\6d\20\de\4d\fa\43\fc\96\33\e5\6a\1b" }}; // Hash of the previous block in the ledger chain - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Account to be frozen (owner principal) - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; // Another account to be frozen - }}}; - record { "reason"; variant { Text = "Security breach" }}; // The reason for the freeze operation - }}}; - }}; +variant { Map = vec { +  record { "btype"; variant { Text = "123freezeaccount" }}; +  record { "ts"; variant { Nat = 1_747_773_480_000_000_000 : nat }}; // Example: 2025-05-19T12:38:00Z +  record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8" }}; // Example parent hash +  record { "tx"; variant { Map = vec { + // The principal that invoked the freeze_account operation +    record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\01" }}; // Example caller principal (e.g., a compliance officer canister) +    // The account being frozen (owner + subaccount) +    record { "account"; variant { Array = vec { +      variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Example owner principal of the account +      variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; // Example subaccount +    }}}; +    // Optional reason +    record { "reason"; variant { Text = "Regulatory compliance order #REG-1138" }}; +  }}}; +}}; ``` -This example illustrates a `freezeaccount` block where the `tx` field includes more than just the `account`. By including fields like `caller`, `method`, and `reason`, the ledger provides greater transparency and traceability. ---- + +### 123unfreezeaccount Example ``` -variant { - Map = vec { - record { "btype"; variant { Text = "123unfreezeaccount" }}; // Block type identifier - record { "ts"; variant { Nat = 1741312737184874392 }}; // Timestamp when the block was appended (nanoseconds since epoch) - record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; // Hash of the previous block in the ledger chain - record { "tx"; variant { Map = vec { - record { "account"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Account to be unfrozen (owner principal) - variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; // Another account to be unfrozen - }}}; - record { "reason"; variant { Text = "Legal case resolved" }}; // The reason for the unfreeze operation - }}}; +variant { Map = vec { +  record { "btype"; variant { Text = "123unfreezeaccount" }}; +  record { "ts"; variant { Nat = 1_747_773_540_000_000_000 : nat }}; // Example: 2025-05-19T12:39:00Z +  record { "phash"; variant { Blob = blob "\e8\a1\03\ff\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff" }}; // Example parent hash +  record { "tx"; variant { Map = vec { + // The principal that invoked the unfreeze_account operation +    record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\01" }}; // Example caller principal +    // The account being unfrozen +    record { "account"; variant { Array = vec { +      variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; +      variant { Blob = blob "\06\ec\cd\3a\97\fb\a8\5f\bc\8d\a3\3e\5d\ba\bc\2f\38\69\60\5d\c7\a1\c9\53\1f\70\a3\66\c5\a7\e4\21" }; +    }}}; +    // Optional reason +    record { "reason"; variant { Text = "Compliance review complete. Order #REG-1138 lifted." }}; +  }}}; +}}; - }}; ``` From 6b954f9dd444f7854c6071920705e578c80479d6 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 19 May 2025 15:30:14 +0200 Subject: [PATCH 17/19] fix examples --- ICRCs/ICRC-123/ICRC-123.md | 50 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 9f6f57a0..77326e60 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -188,34 +188,36 @@ variant { Map = vec { ### 123freezeprincipal Example ``` -variant { - Map = vec { - record { "btype"; variant { Text = "123freezeprincipal" }}; // Block type identifier - record { "ts"; variant { Nat = 1741312737184874393 }}; // Timestamp when the block was appended (nanoseconds since epoch) - record { "phash"; variant { Blob = blob "\9a\6f\bd\5b\18\65\2c\fa\6d\20\de\4d\fa\43\fc\96\33\e5\6a\1b" }}; // Hash of the previous block in the ledger chain - record { "tx"; variant { Map = vec { - record { "principal"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Principal to be frozen - }}}; - record { "reason"; variant { Text = "Suspicion of illicit activity" }}; // Reason for freezing the principal - }}}; - +variant { Map = vec { +  record { "btype"; variant { Text = "123freezeprincipal" }}; +  record { "ts"; variant { Nat = 1_747_773_600_000_000_000 : nat }}; // Example: 2025-05-19T12:40:00Z +  record { "phash"; variant { Blob = blob "\f0\1d\9b\2a\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00" }}; // Example parent hash +  record { "tx"; variant { Map = vec { + // The principal that invoked the freeze_principal operation +    record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\02" }}; // Example caller (e.g., DAO canister) +    // The principal being frozen +    record { "principal"; variant { Blob = blob "\94\85\a4\06\ef\cd\ab\01\23\45\67\89\12\34\56\78\90\ab\cd\ef" }}; // Example principal to freeze +    // Optional reason +    record { "reason"; variant { Text = "Platform terms of service violation." }}; +  }}}; }}; + ``` ### 123unfreezeprincipal Example ``` -variant { - Map = vec { - record { "btype"; variant { Text = "123unfreezeprincipal" }}; // Block type identifier - record { "ts"; variant { Nat = 1741312737184874392 }}; // Timestamp when the block was appended (nanoseconds since epoch) - record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8\02\d4\60\65\e2\f2\3c\00\04\3b\2e\51" }}; // Hash of the previous block in the ledger chain - record { "tx"; variant { Map = vec { - record { "principal"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // Principal to be unfrozen - }}}; - record { "reason"; variant { Text = "Court order" }}; // Reason for unfreezing the principal - }}}; - +variant { Map = vec { +  record { "btype"; variant { Text = "123unfreezeprincipal" }}; +  record { "ts"; variant { Nat = 1_747_773_660_000_000_000 : nat }}; // Example: 2025-05-19T12:41:00Z +  record { "phash"; variant { Blob = blob "\c3\45\e6\b9\fe\dc\ba\98\76\54\32\10\00\00\00\00\00\00\00\00" }}; // Example parent hash +  record { "tx"; variant { Map = vec { + // The principal that invoked the unfreeze_principal operation +    record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\02" }}; // Example caller +    // The principal being unfrozen +    record { "principal"; variant { Blob = blob "\94\85\a4\06\ef\cd\ab\01\23\45\67\89\12\34\56\78\90\ab\cd\ef" }}; +    // Optional reason (example of omission for brevity, or if not applicable) +    // record { "reason"; variant { Text = "Appeal successful." }}; +  }}}; }}; + ``` From 9b525cf2df107f7eae7f1e5199ae75739ab372aa Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 12 Sep 2025 12:29:48 +0200 Subject: [PATCH 18/19] alignment with the new icrc-3 --- ICRCs/ICRC-123/ICRC-123.md | 229 +++++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 83 deletions(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 77326e60..5b8d80b8 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -6,98 +6,131 @@ Draft ## Introduction -This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. The transaction details (`tx`) within each block explicitly include the `caller` principal that authorized the operation. +This standard defines new block types for ICRC-compliant ledgers that enable freezing and unfreezing of accounts and principals. These operations are primarily relevant in regulatory contexts or under specific legal or platform policy obligations where temporarily restricting interactions with certain accounts or identities is necessary. Freezing an account or principal must be reflected transparently on-chain, using a format designed for auditability and clear semantics. ## Motivation -Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines a block structure that includes the initiator (`caller`) and essential details for the operation, enhancing on-chain auditability. +Regulatory requirements or platform policies may necessitate the ability to freeze accounts or principals. This standard provides explicit block types (`123freezeaccount`, `123unfreezeaccount`, `123freezeprincipal`, `123unfreezeprincipal`) to record these actions transparently on the ledger, distinct from standard transactional blocks. It defines a block structure with a minimal `tx` sufficient to determine semantics; additional provenance MAY be included to enhance auditability. ## Common Elements + This standard follows the conventions set by ICRC-3, inheriting key structural components. -- **Accounts** are represented using the ICRC-3 `Value` type, specifically as a `variant { Array = vec { V1 [, V2] } }` where `V1` is `variant { Blob = }` representing the account owner, and `V2` is `variant { Blob = }` representing the subaccount. If no subaccount is specified, the `Array` MUST contain only one element (`V1`). -- **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. + +- **Accounts** are represented using the ICRC-3 `Value` type, specifically as a `variant { Array = vec { V1 [, V2] } }` where `V1` is `variant { Blob = }` representing the account owner, and `V2` is `variant { Blob = }` representing the subaccount. If no subaccount is specified, the `Array` MUST contain only one element (`V1`). If present, the subaccount **MUST** be exactly 32 bytes. +- **Principals** are represented as `variant { Blob = }`. +- **Timestamps:** `ts` (and any optional `created_at_time` if included) 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 freeze/unfreeze operation, including the `caller` principal, the target entity, and basic context. +Each block introduced by this standard MUST include a `tx` field containing a map. This map encodes the **minimal information** about the freeze/unfreeze operation sufficient to determine its semantic effect. Additional provenance (e.g., `caller`, `reason`, `created_at_time`) 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-123 **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 freeze/unfreeze 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: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | +| `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: `"123freezeaccount"`, `"123unfreezeaccount"`, `"123freezeprincipal"`, `"123unfreezeprincipal"`. | -| `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 freeze/unfreeze operation, including the caller. See schemas below. | +### `tx` Field Schemas (minimal) -### `tx` Field Schemas +#### `123freezeaccount` -#### For `123freezeaccount` +| Field | Type (ICRC-3 `Value`) | Req | Description | +|---------|------------------------------------------------------|-----|------------------------| +| `account` | `variant { Array = vec { V1 [, V2] } }`¹ | Yes | The account being frozen. | -| Field        | Type (ICRC-3 `Value`)                                        | Required | Description | -|--------------|--------------------------------------------------------------|----------|-------------| -| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | -| `account`    | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes      | The account being frozen. | -| `reason`     | `Text`                                                       | Optional | Human-readable reason for freezing the account. | +#### `123unfreezeaccount` -#### For `123unfreezeaccount` +| Field | Type (ICRC-3 `Value`) | Req | Description | +|---------|------------------------------------------------------|-----|--------------------------| +| `account` | `variant { Array = vec { V1 [, V2] } }`¹ | Yes | The account being unfrozen. | -| Field        | Type (ICRC-3 `Value`)                                        | Required | Description | -|--------------|--------------------------------------------------------------|----------|-------------| -| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | -| `account`    | `Value` (Must be `variant { Array = vec { V1 [, V2] } }`)¹ | Yes      | The account being unfrozen. | -| `reason`     | `Text`                                                       | Optional | Human-readable reason for unfreezing the account. | +#### `123freezeprincipal` -#### For `123freezeprincipal` +| Field | Type (ICRC-3 `Value`) | Req | Description | +|------------|----------------------------------------|-----|-------------------------| +| `principal` | `variant { Blob = }` | Yes | The principal being frozen. | -| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | -|--------------|----------------------------------------------------------|----------|-------------| -| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | -| `principal`  | `Value` (Must be `variant { Blob = }`) | Yes      | The principal being frozen. | -| `reason`     | `Text`                                                   | Optional | Human-readable reason for freezing the principal. | +#### `123unfreezeprincipal` -#### For `123unfreezeprincipal` +| Field | Type (ICRC-3 `Value`) | Req | Description | +|------------|----------------------------------------|-----|---------------------------| +| `principal` | `variant { Blob = }` | Yes | The principal being unfrozen. | -| Field        | Type (ICRC-3 `Value`)                                    | Required | Description | -|--------------|----------------------------------------------------------|----------|-------------| -| `caller` | `Value` (Must be `variant { Blob = }`) | Yes | The principal that invoked the ledger method causing this block. | -| `principal`  | `Value` (Must be `variant { Blob = }`) | Yes      | The principal being unfrozen. | -| `reason`     | `Text`                                                   | Optional | Human-readable reason for unfreezing the principal. | +¹ `V1 = variant { Blob = }`; optional `V2 = variant { Blob = }` (exactly 32 bytes). -¹ Where `V1` is `variant { Blob = }` and `V2` is `variant { Blob = }`. If no subaccount exists, the `Array` contains only `V1`. +### Optional Provenance (non-semantic) -## Semantics +Producers MAY include non-semantic provenance fields within `tx`, such as: + +- `caller : Blob` — principal that initiated the action (when applicable). +- `reason : Text` — human-readable context for the action. +- `created_at_time : Nat` — caller-supplied timestamp in nanoseconds (MUST fit in `nat64`). +- `policy_ref : Text` — identifier for the policy/order/proposal under which the action occurred. +- `op : Text` — the logical operation or method that produced the block. This field is **optional** in ICRC-123, but when a separate standard defines methods that create ICRC-123 blocks, that standard **SHOULD** include `tx.op` to make the call uniquely identifiable from `tx`. + + **Namespacing rule (from ICRC-3):** `tx.op` values SHOULD be namespaced by the standard that defines the method to avoid collisions. Use a numeric ICRC prefix and a lowercase op name: + + - Format: `` + - Examples: `147freeze_principal`, `147unfreeze_account` + + **Alternative (descriptive) form:** Implementations MAY also include a fully-qualified method name for readability, e.g. `icrc147_freeze_principal`. The numeric-namespaced form above is preferred for compactness and collision-avoidance. + + `tx.op` is **provenance only**; it MUST NOT affect block semantics or verification. + + + + +> **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. + + +### Guidance for Standards That Define Methods + +A standard that defines ledger methods which produce ICRC-123 blocks (e.g., “freeze principal” or “unfreeze account”) SHOULD: + +1. **Include `tx.op`** in the resulting block’s `tx` map. + - Use a namespaced value per ICRC-3: `` (e.g., `147freeze_principal`). + - 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: + - `123freezeaccount` / `123unfreezeaccount`: map the account argument to `tx.account`. + - `123freezeprincipal` / `123unfreezeprincipal`: map the principal argument to `tx.principal`. + - Do **not** add extra semantic fields; provenance such as `caller`, `reason`, `created_at_time`, `policy_ref` MAY be included but MUST NOT affect semantics. -This section defines the semantics of the freeze and unfreeze block types introduced by this standard. +3. **Document deduplication inputs** (if any). If the method uses a caller-supplied timestamp, put it in `tx.created_at_time` (ns; MUST fit `nat64`). + + + +## Semantics ### Account Status -Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: opt Blob)` is considered **RESTRICTED** if and only if the most recent freeze or unfreeze block at or before height `h` that affects `acc` is a freeze block (either `123freezeaccount` or `123freezeprincipal`). +Given the state of the ledger at a particular block height `h`, an account `acc = (owner: Principal, subaccount: opt Blob)` is considered **RESTRICTED** iff the most recent freeze/unfreeze block at or before `h` that affects `acc` is a freeze block (`123freezeaccount` or `123freezeprincipal`). -A block is considered to affect an account `acc` if it satisfies one of the following: -- It is a `123freezeaccount` or `123unfreezeaccount` block where the `tx.account` field matches `acc`. -- It is a `123freezeprincipal` or `123unfreezeprincipal` block where the `tx.principal` field matches the `owner` of `acc`. +A block affects `acc` if it satisfies one of the following: +- It is a `123freezeaccount` or `123unfreezeaccount` block where `tx.account` matches `acc`. +- It is a `123freezeprincipal` or `123unfreezeprincipal` block where `tx.principal` equals `owner` of `acc`. -To determine whether an account is RESTRICTED, ledgers MUST identify the most recent block at or before height `h` that affects the account, and check whether its `btype` is `123freezeaccount` or `123freezeprincipal`. If the most recent affecting block is an unfreeze block (`123unfreezeaccount` or `123unfreezeprincipal`), or if no such affecting blocks exist, the account is **NON-RESTRICTED**. +If the most recent affecting block is an unfreeze block (`123unfreezeaccount` or `123unfreezeprincipal`), or if none exist, the account is **NON-RESTRICTED**. -This "latest-action-wins" rule implies: -- A freeze of an account (`123freezeaccount`) can be lifted by a later unfreeze of the same account (`123unfreezeaccount`) or by a later unfreeze of the owning principal (`123unfreezeprincipal`). -- A freeze of a principal (`123freezeprincipal`) can be lifted by a later unfreeze of that principal (`123unfreezeprincipal`). It also implicitly unfreezes all accounts owned by that principal unless a more recent, specific `123freezeaccount` block targets one of those accounts. +Implications: +- An account freeze can be lifted by a later unfreeze of the **same account** or a later unfreeze of the **owning principal**. +- A principal freeze applies to all accounts owned by that principal until lifted, unless a more recent specific `123freezeaccount` targets an account after the principal is unfrozen. ### Ledger Enforcement Rules -- **Transfers:** - - A ledger **MUST reject** any `icrc1_transfer` or `icrc2_transfer_from` transaction where the **sender** account (the `from` account in the operation) is currently RESTRICTED. - - The ledger **MAY**, according to its policy, also reject `icrc1_transfer` or `icrc2_transfer_from` transactions if the **recipient** account (the `to` account in the operation) is RESTRICTED, or it MAY allow incoming funds to a RESTRICTED recipient. -- **ICRC-2 Operations:** - - **`icrc2_approve` (Granting Approval):** If an account is RESTRICTED, its owner **MUST NOT** be able to authorize an `icrc2_approve` transaction where this restricted account is the one granting the approval (i.e., the `account` argument in `icrc2_approve` which specifies the owner of the funds being approved for spending). - - **`icrc2_approve` (Receiving Approval):** The ledger's policy SHOULD define whether an approval can be granted *to* a RESTRICTED account (i.e., a RESTRICTED account being the `spender` argument in an `icrc2_approve` call initiated by an unrestricted account owner). Even if an approval is granted to a RESTRICTED account, that account **MUST NOT** be able to use this approval (e.g., by calling `icrc2_transfer_from`) while it remains RESTRICTED. - - **`icrc2_transfer_from` (Acting as Spender):** An account that is RESTRICTED **MUST NOT** be able to initiate an `icrc2_transfer_from` call (i.e., act as an approved spender), even if it holds a valid approval for another account. -- Freeze and unfreeze blocks do **not** modify or invalidate previous transactions. They apply only to transactions attempted **at or after** the block height at which the freeze/unfreeze block is recorded and its state change takes effect. -- Freeze and unfreeze blocks MUST be **permanently recorded** and included in the block hash chain. +- **Transfers:** + - MUST reject any `icrc1_transfer` / `icrc2_transfer_from` where the **sender** (`from`) is RESTRICTED. + - MAY reject or allow incoming transfers to a RESTRICTED **recipient** (`to`) per ledger policy. +- **ICRC-2 Operations:** + - `icrc2_approve` (granting approval): a RESTRICTED account MUST NOT grant approvals. + - `icrc2_approve` (receiving approval): policy-defined; even if granted, a RESTRICTED spender MUST NOT use it while restricted. + - `icrc2_transfer_from` (acting as spender): a RESTRICTED account MUST NOT act as spender. +- Freeze/unfreeze blocks do not retroactively modify prior transactions; they apply to transactions attempted **at or after** their block height. +- Freeze/unfreeze blocks MUST be permanently recorded and included in the hash chain. ### Idempotency and Redundancy @@ -106,35 +139,34 @@ This "latest-action-wins" rule implies: ### Querying Freeze Status -Ledgers implementing this standard SHOULD expose a query interface (e.g., `is_account_restricted(account): bool`) that returns whether an account is currently RESTRICTED according to the rules defined in "Account Status". This serves as a convenience layer and does not replace auditing based on block history. +Ledgers implementing this standard SHOULD expose a query (e.g., `is_account_restricted(account): bool`) that returns whether an account is currently RESTRICTED per the rules above. This is a convenience and does not replace auditing from history. ## Compliance Reporting -Ledgers implementing this standard MUST return the following response to `icrc3_supported_block_types` with a URL pointing to the standard defining each block type: +Ledgers implementing this standard MUST report supported block types via `icrc3_supported_block_types`: ```candid vec { -    // ... other supported types like ICRC-1 ... -    variant { Record = vec { -        record { "btype"; variant { Text = "123freezeaccount" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL -    }}; -    variant { Record = vec { -        record { "btype"; variant { Text = "123unfreezeaccount" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL -    }}; -    variant { Record = vec { -        record { "btype"; variant { Text = "123freezeprincipal" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL -    }}; -    variant { Record = vec { -        record { "btype"; variant { Text = "123unfreezeprincipal" }}; -        record { "url"; variant { Text = "[https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md)" }}; // Placeholder URL -    }}; + variant { Record = vec { + record { "btype"; variant { Text = "123freezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeaccount" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; + variant { Record = vec { + record { "btype"; variant { Text = "123unfreezeprincipal" }}; + record { "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-123.md" }}; + }}; } - ``` + ## Example Blocks @@ -148,7 +180,7 @@ variant { Map = vec {   record { "ts"; variant { Nat = 1_747_773_480_000_000_000 : nat }}; // Example: 2025-05-19T12:38:00Z   record { "phash"; variant { Blob = blob "\d5\c7\eb\57\a2\4e\fa\d4\8b\d1\cc\54\9e\49\c6\9f\d1\93\8d\e8" }}; // Example parent hash   record { "tx"; variant { Map = vec { - // The principal that invoked the freeze_account operation + // Optional provenance: the principal that invoked the operation     record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\01" }}; // Example caller principal (e.g., a compliance officer canister)     // The account being frozen (owner + subaccount)     record { "account"; variant { Array = vec { @@ -171,7 +203,7 @@ variant { Map = vec {   record { "ts"; variant { Nat = 1_747_773_540_000_000_000 : nat }}; // Example: 2025-05-19T12:39:00Z   record { "phash"; variant { Blob = blob "\e8\a1\03\ff\00\11\22\33\44\55\66\77\88\99\aa\bb\cc\dd\ee\ff" }}; // Example parent hash   record { "tx"; variant { Map = vec { - // The principal that invoked the unfreeze_account operation + // Optional provenance: the principal that invoked the operation     record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\01" }}; // Example caller principal     // The account being unfrozen     record { "account"; variant { Array = vec { @@ -193,7 +225,7 @@ variant { Map = vec {   record { "ts"; variant { Nat = 1_747_773_600_000_000_000 : nat }}; // Example: 2025-05-19T12:40:00Z   record { "phash"; variant { Blob = blob "\f0\1d\9b\2a\10\20\30\40\50\60\70\80\90\a0\b0\c0\d0\e0\f0\00" }}; // Example parent hash   record { "tx"; variant { Map = vec { - // The principal that invoked the freeze_principal operation + // Optional provenance: the principal that invoked the operation     record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\02" }}; // Example caller (e.g., DAO canister)     // The principal being frozen     record { "principal"; variant { Blob = blob "\94\85\a4\06\ef\cd\ab\01\23\45\67\89\12\34\56\78\90\ab\cd\ef" }}; // Example principal to freeze @@ -211,7 +243,7 @@ variant { Map = vec {   record { "ts"; variant { Nat = 1_747_773_660_000_000_000 : nat }}; // Example: 2025-05-19T12:41:00Z   record { "phash"; variant { Blob = blob "\c3\45\e6\b9\fe\dc\ba\98\76\54\32\10\00\00\00\00\00\00\00\00" }}; // Example parent hash   record { "tx"; variant { Map = vec { - // The principal that invoked the unfreeze_principal operation + // Optional provenance: the principal that invoked the operation     record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\02" }}; // Example caller     // The principal being unfrozen     record { "principal"; variant { Blob = blob "\94\85\a4\06\ef\cd\ab\01\23\45\67\89\12\34\56\78\90\ab\cd\ef" }}; @@ -221,3 +253,34 @@ variant { Map = vec { }}; ``` + +### Informative Example: Integration with a Standardized Method + +ICRC-123 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-147) introduces the method: + +``` +icrc147_freeze_principal : (principal, opt text) -> result nat +``` + +Invoking this method with a target principal and an optional reason could produce a +`123freezeprincipal` block on-chain. A possible encoding is shown below: + +``` +variant { Map = vec { + record { "btype"; variant { Text = "123freezeprincipal" }}; + record { "ts"; variant { Nat = 1_747_800_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 { + // Namespaced op from the method-defining standard (ICRC-147) + record { "op"; variant { Text = "147freeze_principal" }}; + // Optional provenance (non-semantic) + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\f0\0d\01\02" }}; + record { "principal"; variant { Blob = blob "\94\85\a4\06\ef\cd\ab\01\23\45\67\89\12\34\56\78\90\ab\cd\ef" }}; + record { "reason"; variant { Text = "Sanctions order #147-2025" }}; + }}}; +}} + +``` +This example is non-normative and illustrates how a standardized method can map into the ICRC-123 block structure while using a namespaced `tx.op` for unambiguous identification. The authoritative semantics remain defined by the ICRC-123 block types. \ No newline at end of file From f539783c02e5245db33dfc626b5e989121349976 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 26 Sep 2025 10:14:04 +0200 Subject: [PATCH 19/19] clarify it's blocks that are standardised --- ICRCs/ICRC-123/ICRC-123.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-123/ICRC-123.md b/ICRCs/ICRC-123/ICRC-123.md index 5b8d80b8..0b129204 100644 --- a/ICRCs/ICRC-123/ICRC-123.md +++ b/ICRCs/ICRC-123/ICRC-123.md @@ -1,4 +1,4 @@ -# ICRC-123: Freezing and Unfreezing Accounts and Principals +# ICRC-123: Freeze and Unfreeze Blocks ## Status